blob: 2056091b1f1b967d4700aaf8d251f9bd0d6aebc2 [file] [log] [blame]
// Copyright 2017 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_client_socket.h"
#include <cstdio>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/values.h"
#include "net/base/proxy_delegate.h"
#include "net/http/http_auth_controller.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/quic/quic_http_utils.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
namespace net {
QuicProxyClientSocket::QuicProxyClientSocket(
std::unique_ptr<QuicChromiumClientStream::Handle> stream,
std::unique_ptr<QuicChromiumClientSession::Handle> session,
const ProxyServer& proxy_server,
const std::string& user_agent,
const HostPortPair& endpoint,
const NetLogWithSource& net_log,
scoped_refptr<HttpAuthController> auth_controller,
ProxyDelegate* proxy_delegate)
: stream_(std::move(stream)),
session_(std::move(session)),
endpoint_(endpoint),
auth_(std::move(auth_controller)),
proxy_server_(proxy_server),
proxy_delegate_(proxy_delegate),
user_agent_(user_agent),
net_log_(net_log) {
DCHECK(stream_->IsOpen());
request_.method = "CONNECT";
request_.url = GURL("https://" + endpoint.ToString());
net_log_.BeginEventReferencingSource(NetLogEventType::SOCKET_ALIVE,
net_log_.source());
net_log_.AddEventReferencingSource(
NetLogEventType::HTTP2_PROXY_CLIENT_SESSION, stream_->net_log().source());
}
QuicProxyClientSocket::~QuicProxyClientSocket() {
Disconnect();
net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE);
}
const HttpResponseInfo* QuicProxyClientSocket::GetConnectResponseInfo() const {
return response_.headers.get() ? &response_ : nullptr;
}
const scoped_refptr<HttpAuthController>&
QuicProxyClientSocket::GetAuthController() const {
return auth_;
}
int QuicProxyClientSocket::RestartWithAuth(CompletionOnceCallback callback) {
// A QUIC Stream can only handle a single request, so the underlying
// stream may not be reused and a new QuicProxyClientSocket must be
// created (possibly on top of the same QUIC Session).
next_state_ = STATE_DISCONNECTED;
return ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
}
// Ignore priority changes, just use priority of initial request. Since multiple
// requests are pooled on the QuicProxyClientSocket, reprioritization doesn't
// really work.
//
// TODO(mmenke): Use a single priority value for all QuicProxyClientSockets,
// regardless of what priority they're created with.
void QuicProxyClientSocket::SetStreamPriority(RequestPriority priority) {}
// Sends a HEADERS frame to the proxy with a CONNECT request
// for the specified endpoint. Waits for the server to send back
// a HEADERS frame. OK will be returned if the status is 200.
// ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status.
// In any of these cases, Read() may be called to retrieve the HTTP
// response body. Any other return values should be considered fatal.
int QuicProxyClientSocket::Connect(CompletionOnceCallback callback) {
DCHECK(connect_callback_.is_null());
if (!stream_->IsOpen())
return ERR_CONNECTION_CLOSED;
DCHECK_EQ(STATE_DISCONNECTED, next_state_);
next_state_ = STATE_GENERATE_AUTH_TOKEN;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
connect_callback_ = std::move(callback);
return rv;
}
void QuicProxyClientSocket::Disconnect() {
connect_callback_.Reset();
read_callback_.Reset();
read_buf_ = nullptr;
write_callback_.Reset();
write_buf_len_ = 0;
next_state_ = STATE_DISCONNECTED;
stream_->Reset(quic::QUIC_STREAM_CANCELLED);
}
bool QuicProxyClientSocket::IsConnected() const {
return next_state_ == STATE_CONNECT_COMPLETE && stream_->IsOpen();
}
bool QuicProxyClientSocket::IsConnectedAndIdle() const {
return IsConnected() && !stream_->HasBytesToRead();
}
const NetLogWithSource& QuicProxyClientSocket::NetLog() const {
return net_log_;
}
bool QuicProxyClientSocket::WasEverUsed() const {
return session_->WasEverUsed();
}
bool QuicProxyClientSocket::WasAlpnNegotiated() const {
// Do not delegate to `session_`. While `session_` negotiates ALPN with the
// proxy, this object represents the tunneled TCP connection to the origin.
return false;
}
NextProto QuicProxyClientSocket::GetNegotiatedProtocol() const {
// Do not delegate to `session_`. While `session_` negotiates ALPN with the
// proxy, this object represents the tunneled TCP connection to the origin.
return kProtoUnknown;
}
bool QuicProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
// Do not delegate to `session_`. While `session_` has a secure channel to the
// proxy, this object represents the tunneled TCP connection to the origin.
return false;
}
int64_t QuicProxyClientSocket::GetTotalReceivedBytes() const {
return stream_->NumBytesConsumed();
}
void QuicProxyClientSocket::ApplySocketTag(const SocketTag& tag) {
// In the case of a connection to the proxy using HTTP/2 or HTTP/3 where the
// underlying socket may multiplex multiple streams, applying this request's
// socket tag to the multiplexed session would incorrectly apply the socket
// tag to all mutliplexed streams. Fortunately socket tagging is only
// supported on Android without the data reduction proxy, so only simple HTTP
// proxies are supported, so proxies won't be using HTTP/2 or HTTP/3. Enforce
// that a specific (non-default) tag isn't being applied.
CHECK(tag == SocketTag());
}
int QuicProxyClientSocket::Read(IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback) {
DCHECK(connect_callback_.is_null());
DCHECK(read_callback_.is_null());
DCHECK(!read_buf_);
if (next_state_ == STATE_DISCONNECTED)
return ERR_SOCKET_NOT_CONNECTED;
if (!stream_->IsOpen()) {
return 0;
}
int rv =
stream_->ReadBody(buf, buf_len,
base::BindOnce(&QuicProxyClientSocket::OnReadComplete,
weak_factory_.GetWeakPtr()));
if (rv == ERR_IO_PENDING) {
read_callback_ = std::move(callback);
read_buf_ = buf;
} else if (rv == 0) {
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, 0,
nullptr);
} else if (rv > 0) {
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, rv,
buf->data());
}
return rv;
}
void QuicProxyClientSocket::OnReadComplete(int rv) {
if (!stream_->IsOpen())
rv = 0;
if (!read_callback_.is_null()) {
DCHECK(read_buf_);
if (rv >= 0) {
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, rv,
read_buf_->data());
}
read_buf_ = nullptr;
std::move(read_callback_).Run(rv);
}
}
int QuicProxyClientSocket::Write(
IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback,
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(connect_callback_.is_null());
DCHECK(write_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());
int rv = stream_->WriteStreamData(
base::StringPiece(buf->data(), buf_len), false,
base::BindOnce(&QuicProxyClientSocket::OnWriteComplete,
weak_factory_.GetWeakPtr()));
if (rv == OK)
return buf_len;
if (rv == ERR_IO_PENDING) {
write_callback_ = std::move(callback);
write_buf_len_ = buf_len;
}
return rv;
}
void QuicProxyClientSocket::OnWriteComplete(int rv) {
if (!write_callback_.is_null()) {
if (rv == OK)
rv = write_buf_len_;
write_buf_len_ = 0;
std::move(write_callback_).Run(rv);
}
}
int QuicProxyClientSocket::SetReceiveBufferSize(int32_t size) {
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyClientSocket::SetSendBufferSize(int32_t size) {
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyClientSocket::GetPeerAddress(IPEndPoint* address) const {
return IsConnected() ? session_->GetPeerAddress(address)
: ERR_SOCKET_NOT_CONNECTED;
}
int QuicProxyClientSocket::GetLocalAddress(IPEndPoint* address) const {
return IsConnected() ? session_->GetSelfAddress(address)
: ERR_SOCKET_NOT_CONNECTED;
}
void QuicProxyClientSocket::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 QuicProxyClientSocket::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;
switch (state) {
case STATE_GENERATE_AUTH_TOKEN:
DCHECK_EQ(OK, rv);
rv = DoGenerateAuthToken();
break;
case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
rv = DoGenerateAuthTokenComplete(rv);
break;
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 QuicProxyClientSocket::DoGenerateAuthToken() {
next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
return auth_->MaybeGenerateAuthToken(
&request_,
base::BindOnce(&QuicProxyClientSocket::OnIOComplete,
weak_factory_.GetWeakPtr()),
net_log_);
}
int QuicProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
if (result == OK)
next_state_ = STATE_SEND_REQUEST;
return result;
}
int QuicProxyClientSocket::DoSendRequest() {
next_state_ = STATE_SEND_REQUEST_COMPLETE;
// Add Proxy-Authentication header if necessary.
HttpRequestHeaders authorization_headers;
if (auth_->HaveAuth()) {
auth_->AddAuthorizationHeader(&authorization_headers);
}
if (proxy_delegate_) {
HttpRequestHeaders proxy_delegate_headers;
proxy_delegate_->OnBeforeTunnelRequest(proxy_server_,
&proxy_delegate_headers);
request_.extra_headers.MergeFrom(proxy_delegate_headers);
}
std::string request_line;
BuildTunnelRequest(endpoint_, authorization_headers, user_agent_,
&request_line, &request_.extra_headers);
NetLogRequestHeaders(net_log_,
NetLogEventType::HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
request_line, &request_.extra_headers);
spdy::Http2HeaderBlock headers;
CreateSpdyHeadersFromHttpRequest(request_, request_.extra_headers, &headers);
return stream_->WriteHeaders(std::move(headers), false, nullptr);
}
int QuicProxyClientSocket::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 QuicProxyClientSocket::DoReadReply() {
next_state_ = STATE_READ_REPLY_COMPLETE;
int rv = stream_->ReadInitialHeaders(
&response_header_block_,
base::BindOnce(&QuicProxyClientSocket::OnReadResponseHeadersComplete,
weak_factory_.GetWeakPtr()));
if (rv == ERR_IO_PENDING)
return ERR_IO_PENDING;
if (rv < 0)
return rv;
return ProcessResponseHeaders(response_header_block_);
}
int QuicProxyClientSocket::DoReadReplyComplete(int result) {
if (result < 0)
return result;
// Require the "HTTP/1.x" status line for SSL CONNECT.
if (response_.headers->GetHttpVersion() < HttpVersion(1, 0))
return ERR_TUNNEL_CONNECTION_FAILED;
NetLogResponseHeaders(
net_log_, NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
response_.headers.get());
if (proxy_delegate_) {
int rv = proxy_delegate_->OnTunnelHeadersReceived(proxy_server_,
*response_.headers);
if (rv != OK) {
DCHECK_NE(ERR_IO_PENDING, rv);
return rv;
}
}
switch (response_.headers->response_code()) {
case 200: // OK
next_state_ = STATE_CONNECT_COMPLETE;
return OK;
case 407: // Proxy Authentication Required
next_state_ = STATE_CONNECT_COMPLETE;
SanitizeProxyAuth(response_);
return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_);
default:
// Ignore response to avoid letting the proxy impersonate the target
// server. (See http://crbug.com/137891.)
return ERR_TUNNEL_CONNECTION_FAILED;
}
}
void QuicProxyClientSocket::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 QuicProxyClientSocket::ProcessResponseHeaders(
const spdy::Http2HeaderBlock& headers) {
if (SpdyHeadersToHttpResponse(headers, &response_) != OK) {
DLOG(WARNING) << "Invalid headers";
return ERR_QUIC_PROTOCOL_ERROR;
}
return OK;
}
} // namespace net