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. // found in the LICENSE file.
#include "net/tools/naive/naive_config.h" #include "net/tools/naive/naive_config.h"
#include <algorithm>
#include <iostream> #include <iostream>
#include "base/strings/escape.h" #include "base/strings/escape.h"
#include "base/strings/string_number_conversions.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 "net/base/url_util.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace net { 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() = default;
NaiveListenConfig::NaiveListenConfig(const 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 base::Value* v = value.Find("proxy")) {
if (const std::string* str = v->GetIfString(); str && !str->empty()) { if (const std::string* str = v->GetIfString(); str && !str->empty()) {
GURL url(*str); base::StringTokenizer proxy_uri_list(*str, ",");
net::GetIdentityFromURL(url, &proxy_user, &proxy_pass); std::vector<ProxyServer> proxy_servers;
bool seen_tcp = false;
while (proxy_uri_list.GetNext()) {
std::string token(proxy_uri_list.token());
GURL url(token);
std::u16string proxy_user;
std::u16string proxy_pass;
net::GetIdentityFromURL(url, &proxy_user, &proxy_pass);
GURL::Replacements remove_auth; GURL::Replacements remove_auth;
remove_auth.ClearUsername(); remove_auth.ClearUsername();
remove_auth.ClearPassword(); remove_auth.ClearPassword();
GURL url_no_auth = url.ReplaceComponents(remove_auth); GURL url_no_auth = url.ReplaceComponents(remove_auth);
proxy_url = url_no_auth.GetWithEmptyPath().spec(); std::string proxy_uri = url_no_auth.GetWithEmptyPath().spec();
if (proxy_url.empty()) { if (proxy_uri.back() == '/') {
std::cerr << "Invalid proxy" << std::endl; 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; return false;
} else if (proxy_url.back() == '/') {
proxy_url.pop_back();
} }
} else { } else {
std::cerr << "Invalid proxy" << std::endl; std::cerr << "Invalid proxy argument" << std::endl;
return false; return false;
} }
} }

View File

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

View File

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

View File

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

View File

@ -56,7 +56,7 @@ class NaiveProxyDelegate : public ProxyDelegate {
ProxyResolutionService* proxy_resolution_service) override {} ProxyResolutionService* proxy_resolution_service) override {}
// Returns empty if the padding type has not been negotiated. // Returns empty if the padding type has not been negotiated.
std::optional<PaddingType> GetProxyServerPaddingType( std::optional<PaddingType> GetProxyChainPaddingType(
const ProxyChain& proxy_chain); const ProxyChain& proxy_chain);
private: private:
@ -66,7 +66,7 @@ class NaiveProxyDelegate : public ProxyDelegate {
HttpRequestHeaders extra_headers_; HttpRequestHeaders extra_headers_;
// Empty value means padding type has not been negotiated. // 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 { class ClientPaddingDetectorDelegate {