Support specifying multiple proxies

This commit is contained in:
klzgrad
2025-09-24 20:37:32 +08:00
parent eb81e5d573
commit 20ce2c897c
5 changed files with 74 additions and 20 deletions

View File

@ -64,8 +64,12 @@ Options:
SOCKS-PROXY = "socks://"<HOSTNAME>[":"<PORT>]
Routes traffic via the proxy chain.
The default proxy is directly connection without proxying.
The default proxy is a direct connection without proxying.
The last PROXY-URI is negotiated automatically for Naive padding.
If multiple proxies are specified, they must match the number of specified
LISTEN-URIs, and each LISTEN-URI is routed to the PROXY matched by position.
Limitations:
* QUIC proxies cannot follow TCP-based proxies in a proxy chain.
* The user needs to ensure there is no loop in the proxy chain.

View File

@ -76,17 +76,13 @@ NaiveConfig::~NaiveConfig() = default;
bool NaiveConfig::Parse(const base::Value::Dict& value) {
if (const base::Value* v = value.Find("listen")) {
listen.clear();
std::vector<std::string> listen_strs;
if (const std::string* str = v->GetIfString()) {
if (!listen.emplace_back().Parse(*str)) {
return false;
}
listen_strs.push_back(*str);
} else if (const base::Value::List* strs = v->GetIfList()) {
for (const auto& str_e : *strs) {
if (const std::string* s = str_e.GetIfString()) {
if (!listen.emplace_back().Parse(*s)) {
return false;
}
listen_strs.push_back(*s);
} else {
std::cerr << "Invalid listen element" << std::endl;
return false;
@ -96,6 +92,14 @@ bool NaiveConfig::Parse(const base::Value::Dict& value) {
std::cerr << "Invalid listen" << std::endl;
return false;
}
if (!listen_strs.empty()) {
listen.clear();
}
for (const std::string& str : listen_strs) {
if (!listen.emplace_back().Parse(str)) {
return false;
}
}
}
if (const base::Value* v = value.Find("insecure-concurrency")) {
@ -126,8 +130,24 @@ bool NaiveConfig::Parse(const base::Value::Dict& value) {
}
if (const base::Value* v = value.Find("proxy")) {
std::vector<std::string> proxy_strs;
if (const std::string* str = v->GetIfString(); str && !str->empty()) {
base::StringTokenizer proxy_uri_list(*str, ",");
proxy_strs.push_back(*str);
} else if (const base::Value::List* strs = v->GetIfList()) {
for (const auto& str_e : *strs) {
if (const std::string* s = str_e.GetIfString(); s && !s->empty()) {
proxy_strs.push_back(*s);
} else {
std::cerr << "Invalid proxy element" << std::endl;
return false;
}
}
} else {
std::cerr << "Invalid proxy argument" << std::endl;
return false;
}
for (const std::string& str : proxy_strs) {
base::StringTokenizer proxy_uri_list(str, ",");
std::vector<ProxyServer> proxy_servers;
bool seen_tcp = false;
while (proxy_uri_list.GetNext()) {
@ -187,6 +207,7 @@ bool NaiveConfig::Parse(const base::Value::Dict& value) {
<< std::endl;
return false;
}
ProxyChain proxy_chain;
if (std::any_of(proxy_servers.begin(), proxy_servers.end(),
[](const ProxyServer& s) { return s.is_quic(); })) {
proxy_chain = ProxyChain::ForIpProtection(proxy_servers);
@ -198,9 +219,7 @@ bool NaiveConfig::Parse(const base::Value::Dict& value) {
std::cerr << "Invalid proxy chain" << std::endl;
return false;
}
} else {
std::cerr << "Invalid proxy argument" << std::endl;
return false;
proxy_chains.push_back(proxy_chain);
}
}

View File

@ -44,7 +44,7 @@ struct NaiveConfig {
HttpRequestHeaders extra_headers;
// The last server is assumed to be Naive.
ProxyChain proxy_chain = ProxyChain::Direct();
std::vector<ProxyChain> proxy_chains;
std::set<HostPortPair> origins_to_force_quic_on;
std::map<url::SchemeHostPort, AuthCredentials> auth_store;

View File

@ -172,7 +172,8 @@ std::unique_ptr<URLRequestContext> BuildCertURLRequestContext(NetLog* net_log) {
std::unique_ptr<URLRequestContext> BuildURLRequestContext(
const NaiveConfig& config,
scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher,
NetLog* net_log) {
NetLog* net_log,
int proxy_chain_index = 0) {
URLRequestContextBuilder builder;
builder.DisableHttpCache();
@ -212,8 +213,13 @@ std::unique_ptr<URLRequestContext> BuildURLRequestContext(
ProxyConfig proxy_config;
proxy_config.proxy_rules().type =
net::ProxyConfig::ProxyRules::Type::PROXY_LIST;
proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
config.proxy_chain);
if (config.proxy_chains.empty()) {
proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
ProxyChain::Direct());
} else {
proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
config.proxy_chains.at(proxy_chain_index));
}
LOG(INFO) << "Proxying via "
<< proxy_config.proxy_rules().single_proxies.ToDebugString();
auto proxy_service =
@ -456,14 +462,20 @@ int main(int argc, char* argv[]) {
cert_net_fetcher = base::MakeRefCounted<net::CertNetFetcherURLRequest>();
cert_net_fetcher->SetURLRequestContext(cert_context.get());
#endif
auto context =
net::BuildURLRequestContext(config, std::move(cert_net_fetcher), net_log);
auto* session = context->http_transaction_factory()->GetSession();
if (config.proxy_chains.size() >= 2 &&
config.proxy_chains.size() != config.listen.size()) {
std::cerr << "Listen addresses do not match multiple proxy chains"
<< std::endl;
return EXIT_FAILURE;
}
std::vector<std::unique_ptr<net::NaiveProxy>> naive_proxies;
std::vector<std::unique_ptr<net::URLRequestContext>> contexts;
std::unique_ptr<net::RedirectResolver> resolver;
for (const net::NaiveListenConfig& listen_config : config.listen) {
for (size_t listen_i = 0; listen_i < config.listen.size(); ++listen_i) {
const net::NaiveListenConfig& listen_config = config.listen[listen_i];
auto listen_socket =
std::make_unique<net::TCPServerSocket>(net_log, net::NetLogSource());
@ -503,6 +515,15 @@ int main(int argc, char* argv[]) {
config.resolver_prefix);
}
if (config.proxy_chains.size() >= 2) {
contexts.push_back(net::BuildURLRequestContext(
config, std::move(cert_net_fetcher), net_log, listen_i));
} else if (contexts.empty()) {
contexts.push_back(net::BuildURLRequestContext(
config, std::move(cert_net_fetcher), net_log));
}
auto& context = contexts.back();
auto* session = context->http_transaction_factory()->GetSession();
auto naive_proxy = std::make_unique<net::NaiveProxy>(
std::move(listen_socket), listen_config.protocol, listen_config.user,
listen_config.pass, config.insecure_concurrency, resolver.get(),

View File

@ -245,6 +245,16 @@ test_naive('Multiple listens - config file', 'http://127.0.0.1:{PORT2}',
config_content='"listen":["socks://:{PORT1}", "http://:{PORT2}"],"log":""',
config_file='multiple-listen.json')
test_naive('Multiple proxies - command line', 'socks5h://127.0.0.1:{PORT1}',
'--log --listen=socks://:{PORT1} --listen=socks://:{PORT2} --proxy=http://127.0.0.1:{PORT3} --proxy=http://127.0.0.1:{PORT4}',
'--log --listen=http://:{PORT3} --listen=http://:{PORT4} --proxy=socks://127.0.0.1:{PORT5}',
'--log --listen=socks://:{PORT5}')
test_naive('Multiple proxies - command line', 'socks5h://127.0.0.1:{PORT2}',
'--log --listen=socks://:{PORT1} --listen=socks://:{PORT2} --proxy=http://127.0.0.1:{PORT3} --proxy=http://127.0.0.1:{PORT4}',
'--log --listen=http://:{PORT3} --listen=http://:{PORT4} --proxy=socks://127.0.0.1:{PORT5}',
'--log --listen=socks://:{PORT5}')
test_naive('Trivial - listen scheme only', 'socks5h://127.0.0.1:1080',
'--log --listen=socks://')