blob: a08647f8bf2f03db6e24a6541101496fc30f8263 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/proxy_resolving_client_socket.h"
#include <stdint.h>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_address.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_auth_controller.h"
#include "net/http/http_network_session.h"
#include "net/http/http_transaction_factory.h"
#include "net/http/proxy_client_socket.h"
#include "net/log/net_log_source_type.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/client_socket_pool_manager.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
namespace network {
ProxyResolvingClientSocket::ProxyResolvingClientSocket(
net::HttpNetworkSession* network_session,
const net::SSLConfig& ssl_config,
const GURL& url)
: network_session_(network_session),
ssl_config_(ssl_config),
proxy_resolve_request_(nullptr),
url_(url),
net_log_(net::NetLogWithSource::Make(network_session_->net_log(),
net::NetLogSourceType::SOCKET)),
weak_factory_(this) {
// TODO(xunjieli): Handle invalid URLs more gracefully (at mojo API layer
// or when the request is created).
DCHECK(url_.is_valid());
}
ProxyResolvingClientSocket::~ProxyResolvingClientSocket() {
Disconnect();
}
int ProxyResolvingClientSocket::Read(net::IOBuffer* buf,
int buf_len,
const net::CompletionCallback& callback) {
if (transport_.get() && transport_->socket())
return transport_->socket()->Read(buf, buf_len, callback);
return net::ERR_SOCKET_NOT_CONNECTED;
}
int ProxyResolvingClientSocket::Write(
net::IOBuffer* buf,
int buf_len,
const net::CompletionCallback& callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
if (transport_.get() && transport_->socket())
return transport_->socket()->Write(buf, buf_len, callback,
traffic_annotation);
return net::ERR_SOCKET_NOT_CONNECTED;
}
int ProxyResolvingClientSocket::SetReceiveBufferSize(int32_t size) {
if (transport_.get() && transport_->socket())
return transport_->socket()->SetReceiveBufferSize(size);
return net::ERR_SOCKET_NOT_CONNECTED;
}
int ProxyResolvingClientSocket::SetSendBufferSize(int32_t size) {
if (transport_.get() && transport_->socket())
return transport_->socket()->SetSendBufferSize(size);
return net::ERR_SOCKET_NOT_CONNECTED;
}
int ProxyResolvingClientSocket::Connect(
const net::CompletionCallback& callback) {
DCHECK(user_connect_callback_.is_null());
// First try to resolve the proxy.
// TODO(xunjieli): Having a null ProxyDelegate is bad. Figure out how to
// interact with the new interface for proxy delegate.
// https://crbug.com/793071.
int net_error = network_session_->proxy_resolution_service()->ResolveProxy(
url_, "POST", &proxy_info_,
base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxy,
base::Unretained(this)),
&proxy_resolve_request_, nullptr /*proxy_delegate*/, net_log_);
if (net_error != net::ERR_IO_PENDING) {
// Defer execution of ConnectToProxy instead of calling it
// directly here for simplicity. From the caller's point of view,
// the connect always happens asynchronously.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ProxyResolvingClientSocket::ConnectToProxy,
weak_factory_.GetWeakPtr(), net_error));
}
user_connect_callback_ = callback;
return net::ERR_IO_PENDING;
}
void ProxyResolvingClientSocket::Disconnect() {
CloseTransportSocket();
if (proxy_resolve_request_) {
network_session_->proxy_resolution_service()->CancelRequest(
proxy_resolve_request_);
proxy_resolve_request_ = nullptr;
}
user_connect_callback_.Reset();
}
bool ProxyResolvingClientSocket::IsConnected() const {
if (!transport_.get() || !transport_->socket())
return false;
return transport_->socket()->IsConnected();
}
bool ProxyResolvingClientSocket::IsConnectedAndIdle() const {
if (!transport_.get() || !transport_->socket())
return false;
return transport_->socket()->IsConnectedAndIdle();
}
int ProxyResolvingClientSocket::GetPeerAddress(net::IPEndPoint* address) const {
if (!transport_.get() || !transport_->socket()) {
return net::ERR_SOCKET_NOT_CONNECTED;
}
if (proxy_info_.is_direct())
return transport_->socket()->GetPeerAddress(address);
net::IPAddress ip_address;
if (!ip_address.AssignFromIPLiteral(url_.HostNoBrackets())) {
// Do not expose the proxy IP address to the caller.
return net::ERR_NAME_NOT_RESOLVED;
}
*address = net::IPEndPoint(ip_address, url_.EffectiveIntPort());
return net::OK;
}
int ProxyResolvingClientSocket::GetLocalAddress(
net::IPEndPoint* address) const {
if (transport_.get() && transport_->socket())
return transport_->socket()->GetLocalAddress(address);
return net::ERR_SOCKET_NOT_CONNECTED;
}
const net::NetLogWithSource& ProxyResolvingClientSocket::NetLog() const {
if (transport_.get() && transport_->socket())
return transport_->socket()->NetLog();
return net_log_;
}
void ProxyResolvingClientSocket::SetSubresourceSpeculation() {
if (transport_.get() && transport_->socket())
transport_->socket()->SetSubresourceSpeculation();
}
void ProxyResolvingClientSocket::SetOmniboxSpeculation() {
if (transport_.get() && transport_->socket())
transport_->socket()->SetOmniboxSpeculation();
}
bool ProxyResolvingClientSocket::WasEverUsed() const {
if (transport_.get() && transport_->socket())
return transport_->socket()->WasEverUsed();
return false;
}
bool ProxyResolvingClientSocket::WasAlpnNegotiated() const {
return false;
}
net::NextProto ProxyResolvingClientSocket::GetNegotiatedProtocol() const {
if (transport_.get() && transport_->socket())
return transport_->socket()->GetNegotiatedProtocol();
return net::kProtoUnknown;
}
bool ProxyResolvingClientSocket::GetSSLInfo(net::SSLInfo* ssl_info) {
return false;
}
void ProxyResolvingClientSocket::GetConnectionAttempts(
net::ConnectionAttempts* out) const {
out->clear();
}
int64_t ProxyResolvingClientSocket::GetTotalReceivedBytes() const {
NOTIMPLEMENTED();
return 0;
}
void ProxyResolvingClientSocket::ApplySocketTag(const net::SocketTag& tag) {
NOTIMPLEMENTED();
}
void ProxyResolvingClientSocket::ConnectToProxy(int net_error) {
proxy_resolve_request_ = nullptr;
DCHECK_NE(net_error, net::ERR_IO_PENDING);
if (net_error == net::OK) {
// Removes unsupported proxies from the list. Currently, this removes
// just the SCHEME_QUIC proxy, which doesn't yet support tunneling.
// TODO(xunjieli): Allow QUIC proxy once it supports tunneling.
proxy_info_.RemoveProxiesWithoutScheme(
net::ProxyServer::SCHEME_DIRECT | net::ProxyServer::SCHEME_HTTP |
net::ProxyServer::SCHEME_HTTPS | net::ProxyServer::SCHEME_SOCKS4 |
net::ProxyServer::SCHEME_SOCKS5);
if (proxy_info_.is_empty()) {
// No proxies/direct to choose from. This happens when we don't support
// any of the proxies in the returned list.
net_error = net::ERR_NO_SUPPORTED_PROXIES;
}
}
if (net_error != net::OK) {
CloseTransportSocket();
base::ResetAndReturn(&user_connect_callback_).Run(net_error);
return;
}
transport_.reset(new net::ClientSocketHandle);
// Now that the proxy is resolved, issue a socket connect.
net::HostPortPair host_port_pair = net::HostPortPair::FromURL(url_);
// Ignore socket limit set by socket pool for this type of socket.
int request_load_flags = net::LOAD_IGNORE_LIMITS;
net::RequestPriority request_priority = net::MAXIMUM_PRIORITY;
net_error = net::InitSocketHandleForRawConnect(
host_port_pair, network_session_, request_load_flags, request_priority,
proxy_info_, ssl_config_, ssl_config_, net::PRIVACY_MODE_DISABLED,
net_log_, transport_.get(),
base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxyDone,
base::Unretained(this)));
if (net_error != net::ERR_IO_PENDING) {
// Since this method is always called asynchronously. it is OK to call
// ConnectToProxyDone synchronously.
ConnectToProxyDone(net_error);
}
}
void ProxyResolvingClientSocket::ConnectToProxyDone(int net_error) {
if (net_error != net::OK) {
// If the connection fails, try another proxy.
net_error = ReconsiderProxyAfterError(net_error);
// ReconsiderProxyAfterError either returns an error (in which case it is
// not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering
// another proxy.
DCHECK_NE(net_error, net::OK);
if (net_error == net::ERR_IO_PENDING) {
// Proxy reconsideration pending. Return.
return;
}
CloseTransportSocket();
} else {
network_session_->proxy_resolution_service()->ReportSuccess(proxy_info_,
nullptr);
}
base::ResetAndReturn(&user_connect_callback_).Run(net_error);
}
void ProxyResolvingClientSocket::CloseTransportSocket() {
if (transport_.get() && transport_->socket())
transport_->socket()->Disconnect();
transport_.reset();
}
// TODO(xunjieli): This following method is out of sync with
// HttpStreamFactoryImpl::JobController. The logic should be refactored into a
// common place.
// This method reconsiders the proxy on certain errors. If it does
// reconsider a proxy it always returns ERR_IO_PENDING and posts a call to
// ConnectToProxy with the result of the reconsideration.
int ProxyResolvingClientSocket::ReconsiderProxyAfterError(int error) {
DCHECK(!proxy_resolve_request_);
DCHECK_NE(error, net::OK);
DCHECK_NE(error, net::ERR_IO_PENDING);
// A failure to resolve the hostname or any error related to establishing a
// TCP connection could be grounds for trying a new proxy configuration.
//
// Why do this when a hostname cannot be resolved? Some URLs only make sense
// to proxy servers. The hostname in those URLs might fail to resolve if we
// are still using a non-proxy config. We need to check if a proxy config
// now exists that corresponds to a proxy server that could load the URL.
//
switch (error) {
case net::ERR_PROXY_CONNECTION_FAILED:
case net::ERR_NAME_NOT_RESOLVED:
case net::ERR_INTERNET_DISCONNECTED:
case net::ERR_ADDRESS_UNREACHABLE:
case net::ERR_CONNECTION_CLOSED:
case net::ERR_CONNECTION_RESET:
case net::ERR_CONNECTION_REFUSED:
case net::ERR_CONNECTION_ABORTED:
case net::ERR_TIMED_OUT:
case net::ERR_TUNNEL_CONNECTION_FAILED:
case net::ERR_SOCKS_CONNECTION_FAILED:
break;
case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
// Remap the SOCKS-specific "host unreachable" error to a more
// generic error code (this way consumers like the link doctor
// know to substitute their error page).
//
// Note that if the host resolving was done by the SOCSK5 proxy, we can't
// differentiate between a proxy-side "host not found" versus a proxy-side
// "address unreachable" error, and will report both of these failures as
// ERR_ADDRESS_UNREACHABLE.
return net::ERR_ADDRESS_UNREACHABLE;
case net::ERR_PROXY_AUTH_REQUESTED: {
net::ProxyClientSocket* proxy_socket =
static_cast<net::ProxyClientSocket*>(transport_->socket());
if (proxy_socket->GetAuthController()->HaveAuth()) {
return proxy_socket->RestartWithAuth(
base::BindRepeating(&ProxyResolvingClientSocket::ConnectToProxyDone,
base::Unretained(this)));
}
return error;
}
default:
return error;
}
if (proxy_info_.is_https() && ssl_config_.send_client_cert) {
network_session_->ssl_client_auth_cache()->Remove(
proxy_info_.proxy_server().host_port_pair());
}
// There was nothing left to fall-back to, so fail the transaction
// with the last connection error we got.
if (!proxy_info_.Fallback(error, net_log_))
return error;
CloseTransportSocket();
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ProxyResolvingClientSocket::ConnectToProxy,
weak_factory_.GetWeakPtr(), net::OK));
// Since we potentially have another try to go, set the return code code to
// ERR_IO_PENDING.
return net::ERR_IO_PENDING;
}
} // namespace network