Support proxy chaining

This commit is contained in:
klzgrad 2024-07-22 00:37:22 +08:00
parent 9dc63fbb8c
commit f8c564d3a0
5 changed files with 133 additions and 63 deletions

View File

@ -3,14 +3,27 @@
// found in the LICENSE file.
#include "net/tools/naive/naive_config.h"
#include <algorithm>
#include <iostream>
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "net/base/proxy_server.h"
#include "net/base/proxy_string_util.h"
#include "net/base/url_util.h"
#include "url/gurl.h"
namespace net {
namespace {
ProxyServer MyProxyUriToProxyServer(std::string_view uri) {
if (uri.compare(0, 7, "quic://") == 0) {
return ProxySchemeHostAndPortToProxyServer(ProxyServer::SCHEME_QUIC,
uri.substr(7));
}
return ProxyUriToProxyServer(uri, ProxyServer::SCHEME_INVALID);
}
} // namespace
NaiveListenConfig::NaiveListenConfig() = default;
NaiveListenConfig::NaiveListenConfig(const NaiveListenConfig&) = default;
@ -114,22 +127,79 @@ bool NaiveConfig::Parse(const base::Value::Dict& value) {
if (const base::Value* v = value.Find("proxy")) {
if (const std::string* str = v->GetIfString(); str && !str->empty()) {
GURL url(*str);
net::GetIdentityFromURL(url, &proxy_user, &proxy_pass);
base::StringTokenizer proxy_uri_list(*str, ",");
std::vector<ProxyServer> proxy_servers;
bool seen_tcp = false;
while (proxy_uri_list.GetNext()) {
std::string token(proxy_uri_list.token());
GURL url(token);
GURL::Replacements remove_auth;
remove_auth.ClearUsername();
remove_auth.ClearPassword();
GURL url_no_auth = url.ReplaceComponents(remove_auth);
proxy_url = url_no_auth.GetWithEmptyPath().spec();
if (proxy_url.empty()) {
std::cerr << "Invalid proxy" << std::endl;
std::u16string proxy_user;
std::u16string proxy_pass;
net::GetIdentityFromURL(url, &proxy_user, &proxy_pass);
GURL::Replacements remove_auth;
remove_auth.ClearUsername();
remove_auth.ClearPassword();
GURL url_no_auth = url.ReplaceComponents(remove_auth);
std::string proxy_uri = url_no_auth.GetWithEmptyPath().spec();
if (proxy_uri.back() == '/') {
proxy_uri.pop_back();
}
proxy_servers.emplace_back(MyProxyUriToProxyServer(proxy_uri));
const ProxyServer& last = proxy_servers.back();
if (last.is_quic()) {
if (seen_tcp) {
std::cerr << "QUIC proxy cannot follow TCP-based proxies"
<< std::endl;
return false;
}
origins_to_force_quic_on.insert(HostPortPair::FromURL(url));
} else if (last.is_https() || last.is_http()) {
seen_tcp = true;
} else {
std::cerr << "Invalid proxy scheme" << std::endl;
return false;
}
AuthCredentials auth(proxy_user, proxy_pass);
if (!auth.Empty()) {
if (last.is_socks()) {
std::cerr << "SOCKS proxy with auth is not supported" << std::endl;
} else {
std::string proxy_url(token);
if (proxy_url.compare(0, 7, "quic://") == 0) {
proxy_url.replace(0, 4, "https");
}
auth_store[url::SchemeHostPort{GURL{proxy_url}}] = auth;
}
}
}
if (proxy_servers.size() > 1 &&
std::any_of(proxy_servers.begin(), proxy_servers.end(),
[](const ProxyServer& s) { return s.is_socks(); })) {
// See net/socket/connect_job_params_factory.cc
// DCHECK(proxy_server.is_socks());
// DCHECK_EQ(1u, proxy_chain.length());
std::cerr
<< "Multi-proxy chain containing SOCKS proxies is not supported."
<< std::endl;
return false;
}
if (std::any_of(proxy_servers.begin(), proxy_servers.end(),
[](const ProxyServer& s) { return s.is_quic(); })) {
proxy_chain = ProxyChain::ForIpProtection(proxy_servers);
} else {
proxy_chain = ProxyChain(proxy_servers);
}
if (!proxy_chain.IsValid()) {
std::cerr << "Invalid proxy chain" << std::endl;
return false;
} else if (proxy_url.back() == '/') {
proxy_url.pop_back();
}
} else {
std::cerr << "Invalid proxy" << std::endl;
std::cerr << "Invalid proxy argument" << std::endl;
return false;
}
}

View File

@ -4,16 +4,22 @@
#ifndef NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
#define NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/values.h"
#include "net/base/auth.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/proxy_chain.h"
#include "net/http/http_request_headers.h"
#include "net/tools/naive/naive_protocol.h"
#include "url/scheme_host_port.h"
namespace net {
@ -37,10 +43,10 @@ struct NaiveConfig {
HttpRequestHeaders extra_headers;
std::string proxy_url = "direct://";
std::u16string proxy_user;
std::u16string proxy_pass;
// The last server is assumed to be Naive.
ProxyChain proxy_chain = ProxyChain::Direct();
std::set<HostPortPair> origins_to_force_quic_on;
std::map<url::SchemeHostPort, AuthCredentials> auth_store;
std::string host_resolver_rules;

View File

@ -175,22 +175,11 @@ std::unique_ptr<URLRequestContext> BuildURLRequestContext(
builder.DisableHttpCache();
builder.set_net_log(net_log);
std::string proxy_url = config.proxy_url;
bool force_quic = false;
if (proxy_url.compare(0, 7, "quic://") == 0) {
proxy_url.replace(0, 4, "https");
force_quic = true;
}
ProxyConfig proxy_config;
proxy_config.proxy_rules().ParseFromString(proxy_url);
if (force_quic) {
const ProxyServer& proxy_server =
proxy_config.proxy_rules().single_proxies.First().First();
proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
ProxyChain::ForIpProtection({ProxyServer(
ProxyServer::Scheme::SCHEME_QUIC, proxy_server.host_port_pair())}));
}
proxy_config.proxy_rules().type =
net::ProxyConfig::ProxyRules::Type::PROXY_LIST;
proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
config.proxy_chain);
LOG(INFO) << "Proxying via "
<< proxy_config.proxy_rules().single_proxies.ToDebugString();
auto proxy_service =
@ -229,22 +218,20 @@ std::unique_ptr<URLRequestContext> BuildURLRequestContext(
auto context = builder.Build();
if (!config.proxy_url.empty() && !config.proxy_user.empty() &&
!config.proxy_pass.empty()) {
if (!config.origins_to_force_quic_on.empty()) {
auto* quic = context->quic_context()->params();
quic->supported_versions = {quic::ParsedQuicVersion::RFCv1()};
quic->origins_to_force_quic_on.insert(
config.origins_to_force_quic_on.begin(),
config.origins_to_force_quic_on.end());
}
for (const auto& [k, v] : config.auth_store) {
auto* session = context->http_transaction_factory()->GetSession();
auto* auth_cache = session->http_auth_cache();
GURL proxy_gurl(proxy_url);
if (force_quic) {
auto* quic = context->quic_context()->params();
quic->supported_versions = {quic::ParsedQuicVersion::RFCv1()};
quic->origins_to_force_quic_on.insert(
net::HostPortPair::FromURL(proxy_gurl));
}
url::SchemeHostPort auth_origin(proxy_gurl);
AuthCredentials credentials(config.proxy_user, config.proxy_pass);
auth_cache->Add(auth_origin, HttpAuth::AUTH_PROXY,
auth_cache->Add(k, HttpAuth::AUTH_PROXY,
/*realm=*/{}, HttpAuth::AUTH_SCHEME_BASIC, {},
/*challenge=*/"Basic", credentials, /*path=*/"/");
/*challenge=*/"Basic", v, /*path=*/"/");
}
return context;

View File

@ -72,8 +72,13 @@ Error NaiveProxyDelegate::OnBeforeTunnelRequest(
// protocols.
if (proxy_chain.is_direct())
return OK;
CHECK_EQ(proxy_chain.length(), 1u) << "Multi-hop proxy not supported";
if (proxy_chain.GetProxyServer(chain_index).is_socks())
const ProxyServer& proxy_server = proxy_chain.GetProxyServer(chain_index);
if (proxy_server.is_socks())
return OK;
// Only the last server is attempted for padding
// because proxy chaining will corrupt the padding.
if (chain_index != proxy_chain.length() - 1)
return OK;
// Sends client-side padding header regardless of server support
@ -83,7 +88,7 @@ Error NaiveProxyDelegate::OnBeforeTunnelRequest(
// Enables Fast Open in H2/H3 proxy client socket once the state of server
// padding support is known.
if (padding_type_by_server_[proxy_chain].has_value()) {
if (padding_type_by_server_[proxy_server].has_value()) {
extra_headers->SetHeader("fastopen", "1");
}
extra_headers->MergeFrom(extra_headers_);
@ -123,8 +128,13 @@ Error NaiveProxyDelegate::OnTunnelHeadersReceived(
// protocols.
if (proxy_chain.is_direct())
return OK;
CHECK_EQ(proxy_chain.length(), 1u) << "Multi-hop proxy not supported";
if (proxy_chain.GetProxyServer(chain_index).is_socks())
const ProxyServer& proxy_server = proxy_chain.GetProxyServer(chain_index);
if (proxy_server.is_socks())
return OK;
// Only the last server is attempted for padding
// because proxy chaining will corrupt the padding.
if (chain_index != proxy_chain.length() - 1)
return OK;
// Detects server padding support, even if it changes dynamically.
@ -134,26 +144,23 @@ Error NaiveProxyDelegate::OnTunnelHeadersReceived(
return ERR_INVALID_RESPONSE;
}
std::optional<PaddingType>& padding_type =
padding_type_by_server_[proxy_chain];
padding_type_by_server_[proxy_server];
if (!padding_type.has_value() || padding_type != new_padding_type) {
LOG(INFO) << proxy_chain.ToDebugString() << " negotiated padding type: "
LOG(INFO) << ProxyServerToProxyUri(proxy_server)
<< " negotiated padding type: "
<< ToReadableString(*new_padding_type);
padding_type = new_padding_type;
}
return OK;
}
std::optional<PaddingType> NaiveProxyDelegate::GetProxyServerPaddingType(
std::optional<PaddingType> NaiveProxyDelegate::GetProxyChainPaddingType(
const ProxyChain& proxy_chain) {
// Not possible to negotiate padding capability given the underlying
// protocols.
if (proxy_chain.is_direct())
return PaddingType::kNone;
CHECK_EQ(proxy_chain.length(), 1u) << "Multi-hop proxy not supported";
if (proxy_chain.GetProxyServer(0).is_socks())
return PaddingType::kNone;
return padding_type_by_server_[proxy_chain];
return padding_type_by_server_[proxy_chain.Last()];
}
PaddingDetectorDelegate::PaddingDetectorDelegate(
@ -186,7 +193,7 @@ std::optional<PaddingType> PaddingDetectorDelegate::GetServerPaddingType() {
if (cached_server_padding_type_.has_value())
return cached_server_padding_type_;
cached_server_padding_type_ =
naive_proxy_delegate_->GetProxyServerPaddingType(proxy_chain_);
naive_proxy_delegate_->GetProxyChainPaddingType(proxy_chain_);
return cached_server_padding_type_;
}

View File

@ -44,8 +44,8 @@ class NaiveProxyDelegate : public ProxyDelegate {
// This only affects h2 proxy client socket.
Error OnBeforeTunnelRequest(const ProxyChain& proxy_chain,
size_t chain_index,
HttpRequestHeaders* extra_headers) override;
size_t chain_index,
HttpRequestHeaders* extra_headers) override;
Error OnTunnelHeadersReceived(
const ProxyChain& proxy_chain,
@ -56,7 +56,7 @@ class NaiveProxyDelegate : public ProxyDelegate {
ProxyResolutionService* proxy_resolution_service) override {}
// Returns empty if the padding type has not been negotiated.
std::optional<PaddingType> GetProxyServerPaddingType(
std::optional<PaddingType> GetProxyChainPaddingType(
const ProxyChain& proxy_chain);
private:
@ -66,7 +66,7 @@ class NaiveProxyDelegate : public ProxyDelegate {
HttpRequestHeaders extra_headers_;
// Empty value means padding type has not been negotiated.
std::map<ProxyChain, std::optional<PaddingType>> padding_type_by_server_;
std::map<ProxyServer, std::optional<PaddingType>> padding_type_by_server_;
};
class ClientPaddingDetectorDelegate {