453 lines
14 KiB
C++
453 lines
14 KiB
C++
// 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
|