0

Allow mixed QUIC/HTTPS proxy chains

The implementation of QUIC proxies allows mixed chains, as long as all
of the SCHEME_QUIC servers come before all of the SCHEME_HTTPS servers.
In practice, IP protection won't use such mixed chains, but to
facilitate testing the new functionality this CL allows such chains to
be constructed.

Bug: 324462739
Change-Id: I506b4bd9cdc3d46aa1cf79286184fb1c7c611183
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5373589
Reviewed-by: mmenke <mmenke@chromium.org>
Commit-Queue: Dustin Mitchell <djmitche@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1274919}
This commit is contained in:
Dustin J. Mitchell
2024-03-19 14:58:44 +00:00
committed by Chromium LUCI CQ
parent 9885c50b61
commit ca1a34d703
3 changed files with 66 additions and 30 deletions

@@ -106,6 +106,9 @@ bool ProxyChain::IsValidInternal() const {
if (!proxy_server_list_.has_value()) { if (!proxy_server_list_.has_value()) {
return false; return false;
} }
if (is_direct()) {
return true;
}
if (is_single_proxy()) { if (is_single_proxy()) {
bool is_valid = proxy_server_list_.value().at(0).is_valid(); bool is_valid = proxy_server_list_.value().at(0).is_valid();
if (proxy_server_list_.value().at(0).is_quic()) { if (proxy_server_list_.value().at(0).is_quic()) {
@@ -113,15 +116,28 @@ bool ProxyChain::IsValidInternal() const {
} }
return is_valid; return is_valid;
} }
bool all_https = base::ranges::all_of( DCHECK(is_multi_proxy());
proxy_server_list_.value(), [](const auto& proxy_server) {
return proxy_server.is_valid() && proxy_server.is_https(); // Verify that the chain is zero or more SCHEME_QUIC servers followed by zero
}); // or more SCHEME_HTTPS servers.
bool all_quic = base::ranges::all_of( bool seen_quic = false;
proxy_server_list_.value(), [](const auto& proxy_server) { bool seen_https = false;
return proxy_server.is_valid() && proxy_server.is_quic(); for (const auto& proxy_server : proxy_server_list_.value()) {
}); if (proxy_server.is_quic()) {
return all_https || (all_quic && is_for_ip_protection()); if (seen_https) {
// SCHEME_QUIC cannot follow SCHEME_HTTPS.
return false;
}
seen_quic = true;
} else if (proxy_server.is_https()) {
seen_https = true;
} else {
return false;
}
}
// QUIC is only allowed for IP protection.
return !seen_quic || is_for_ip_protection();
} }
std::ostream& operator<<(std::ostream& os, const ProxyChain& proxy_chain) { std::ostream& operator<<(std::ostream& os, const ProxyChain& proxy_chain) {

@@ -67,7 +67,7 @@ class NET_EXPORT ProxyChain {
static ProxyChain Direct() { return ProxyChain(std::vector<ProxyServer>()); } static ProxyChain Direct() { return ProxyChain(std::vector<ProxyServer>()); }
// Creates a `ProxyChain` for use by the IP Protection feature. This is used // Creates a `ProxyChain` for use by the IP Protection feature. This is used
// for metrics collection and for special handling. If not give, the // for metrics collection and for special handling. If not given, the
// chain_id defaults to 0 which corresponds to an un-identified chain. // chain_id defaults to 0 which corresponds to an un-identified chain.
static ProxyChain ForIpProtection(std::vector<ProxyServer> proxy_server_list, static ProxyChain ForIpProtection(std::vector<ProxyServer> proxy_server_list,
int chain_id = 0) { int chain_id = 0) {
@@ -179,11 +179,12 @@ class NET_EXPORT ProxyChain {
// A negative value indicates this chain is not used for IP protection. // A negative value indicates this chain is not used for IP protection.
int ip_protection_chain_id_ = kNotIpProtectionChainId; int ip_protection_chain_id_ = kNotIpProtectionChainId;
// Returns true if this chain is valid. A chain is considered valid if (1) is // Returns true if this chain is valid. A chain is considered valid if
// a single valid proxy server. If single QUIC proxy, it must // (1) it is a single valid proxy server, or
// also be an IP protection proxy chain. (2) is multi-proxy and // (2) it is a chain of servers, composed of zero or more SCHEME_QUIC servers
// all servers are either HTTPS or QUIC. If QUIC servers, it must also // followed by zero or more SCHEME_HTTPS servers.
// be an IP protection proxy chain. // If any SCHEME_QUIC servers are included, then the chain must be for IP
// protection.
bool IsValidInternal() const; bool IsValidInternal() const;
}; };

@@ -8,6 +8,7 @@
#include <sstream> #include <sstream>
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h"
#include "net/base/proxy_server.h" #include "net/base/proxy_server.h"
#include "net/base/proxy_string_util.h" #include "net/base/proxy_string_util.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
@@ -367,25 +368,43 @@ TEST(ProxyChainTest, IsGetToProxyAllowed) {
} }
TEST(ProxyChainTest, IsValid) { TEST(ProxyChainTest, IsValid) {
auto direct_chain = ProxyChain::Direct();
// Single hop proxy of type Direct is valid. // Single hop proxy of type Direct is valid.
EXPECT_TRUE(direct_chain.IsValid()); EXPECT_TRUE(ProxyChain::Direct().IsValid());
auto http_proxy1 = auto https1 = ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS); auto https2 = ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS);
auto http_proxy2 = auto quic1 = ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_QUIC);
ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS); auto quic2 = ProxyUriToProxyServer("foo:777", ProxyServer::SCHEME_QUIC);
auto socks = ProxyUriToProxyServer("foo:777", ProxyServer::SCHEME_SOCKS5);
// Multi hop proxy with HTTPs type is valid. EXPECT_TRUE(ProxyChain({https1}).IsValid());
EXPECT_TRUE(ProxyChain({http_proxy1, http_proxy2}).IsValid()); EXPECT_FALSE(ProxyChain({quic1}).IsValid());
EXPECT_TRUE(ProxyChain({https1, https2}).IsValid());
EXPECT_FALSE(ProxyChain({quic1, https1}).IsValid());
EXPECT_FALSE(ProxyChain({quic1, quic2, https1, https2}).IsValid());
EXPECT_FALSE(ProxyChain({https1, quic2}).IsValid());
EXPECT_FALSE(ProxyChain({https1, https2, quic1, quic2}).IsValid());
EXPECT_FALSE(ProxyChain({socks, https1}).IsValid());
EXPECT_FALSE(ProxyChain({socks, https1, https2}).IsValid());
EXPECT_FALSE(ProxyChain({https1, socks}).IsValid());
EXPECT_FALSE(ProxyChain({https1, https2, socks}).IsValid());
auto quic_proxy1 = ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_QUIC); // IP protection accepts chains with SCHEME_QUIC, but CHECKs on failure
auto quic_proxy2 = ProxyUriToProxyServer("foo:777", ProxyServer::SCHEME_QUIC); // instead of just creating an invalid chain.
auto ip_protection_quic_proxy_chain = auto IppChain = [](std::vector<ProxyServer> proxy_servers) {
ProxyChain::ForIpProtection({quic_proxy1, quic_proxy2}); return ProxyChain::ForIpProtection(std::move(proxy_servers));
// Multi hop proxy with QUIC and IP Protection is valid. };
EXPECT_TRUE(ip_protection_quic_proxy_chain.IsValid()); EXPECT_TRUE(IppChain({https1}).IsValid());
EXPECT_TRUE(IppChain({quic1}).IsValid());
EXPECT_TRUE(IppChain({https1, https2}).IsValid());
EXPECT_TRUE(IppChain({quic1, https1}).IsValid());
EXPECT_TRUE(IppChain({quic1, quic2, https1, https2}).IsValid());
EXPECT_CHECK_DEATH(IppChain({https1, quic2}).IsValid());
EXPECT_CHECK_DEATH(IppChain({https1, https2, quic1, quic2}).IsValid());
EXPECT_CHECK_DEATH(IppChain({socks, https1}).IsValid());
EXPECT_CHECK_DEATH(IppChain({socks, https1, https2}).IsValid());
EXPECT_CHECK_DEATH(IppChain({https1, socks}).IsValid());
EXPECT_CHECK_DEATH(IppChain({https1, https2, socks}).IsValid());
} }
TEST(ProxyChainTest, Unequal) { TEST(ProxyChainTest, Unequal) {