From f8c564d3a017b7c9bdb591e750d7ae815cdcb5a8 Mon Sep 17 00:00:00 2001 From: klzgrad Date: Mon, 22 Jul 2024 00:37:22 +0800 Subject: [PATCH] Support proxy chaining --- src/net/tools/naive/naive_config.cc | 94 ++++++++++++++++++--- src/net/tools/naive/naive_config.h | 14 ++- src/net/tools/naive/naive_proxy_bin.cc | 45 ++++------ src/net/tools/naive/naive_proxy_delegate.cc | 35 +++++--- src/net/tools/naive/naive_proxy_delegate.h | 8 +- 5 files changed, 133 insertions(+), 63 deletions(-) diff --git a/src/net/tools/naive/naive_config.cc b/src/net/tools/naive/naive_config.cc index 9a8fccd22a..0c8d98c7ca 100644 --- a/src/net/tools/naive/naive_config.cc +++ b/src/net/tools/naive/naive_config.cc @@ -3,14 +3,27 @@ // found in the LICENSE file. #include "net/tools/naive/naive_config.h" +#include #include #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 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; } } diff --git a/src/net/tools/naive/naive_config.h b/src/net/tools/naive/naive_config.h index b157f74f95..186fca1b2f 100644 --- a/src/net/tools/naive/naive_config.h +++ b/src/net/tools/naive/naive_config.h @@ -4,16 +4,22 @@ #ifndef NET_TOOLS_NAIVE_NAIVE_CONFIG_H_ #define NET_TOOLS_NAIVE_NAIVE_CONFIG_H_ +#include #include +#include #include #include #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 origins_to_force_quic_on; + std::map auth_store; std::string host_resolver_rules; diff --git a/src/net/tools/naive/naive_proxy_bin.cc b/src/net/tools/naive/naive_proxy_bin.cc index c580e0cc4d..892afbdced 100644 --- a/src/net/tools/naive/naive_proxy_bin.cc +++ b/src/net/tools/naive/naive_proxy_bin.cc @@ -175,22 +175,11 @@ std::unique_ptr 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 = @@ -209,7 +198,7 @@ std::unique_ptr BuildURLRequestContext( CertVerifier::CreateDefault(std::move(cert_net_fetcher))); builder.set_proxy_delegate(std::make_unique( - config.extra_headers, + config.extra_headers, std::vector{PaddingType::kVariant1, PaddingType::kNone})); if (config.no_post_quantum == true) { @@ -229,22 +218,20 @@ std::unique_ptr 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; diff --git a/src/net/tools/naive/naive_proxy_delegate.cc b/src/net/tools/naive/naive_proxy_delegate.cc index ba3313429c..3c246d06f2 100644 --- a/src/net/tools/naive/naive_proxy_delegate.cc +++ b/src/net/tools/naive/naive_proxy_delegate.cc @@ -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& 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 NaiveProxyDelegate::GetProxyServerPaddingType( +std::optional 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 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_; } diff --git a/src/net/tools/naive/naive_proxy_delegate.h b/src/net/tools/naive/naive_proxy_delegate.h index b4c2ab8ff3..8cb25b248e 100644 --- a/src/net/tools/naive/naive_proxy_delegate.h +++ b/src/net/tools/naive/naive_proxy_delegate.h @@ -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 GetProxyServerPaddingType( + std::optional 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> padding_type_by_server_; + std::map> padding_type_by_server_; }; class ClientPaddingDetectorDelegate {