Commit 2519fd76 authored by Ryan Hamilton's avatar Ryan Hamilton Committed by Commit Bot

Support IETF-style QUIC Alt-Svc advertisements.

Change-Id: If9c443341119cdd82d4f162b702abd7a15ed10d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1773884
Commit-Queue: Ryan Hamilton <rch@chromium.org>
Reviewed-by: default avatarZhongyi Shi <zhongyi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#692312}
parent e9b308fd
...@@ -38,6 +38,16 @@ AlternativeProxyUsage ConvertProtocolUsageToProxyUsage( ...@@ -38,6 +38,16 @@ AlternativeProxyUsage ConvertProtocolUsageToProxyUsage(
} }
} }
quic::ParsedQuicVersion ParsedQuicVersionFromAlpn(
base::StringPiece str,
quic::ParsedQuicVersionVector supported_versions) {
for (const quic::ParsedQuicVersion version : supported_versions) {
if (AlpnForVersion(version) == str)
return version;
}
return {quic::PROTOCOL_UNSUPPORTED, quic::QUIC_VERSION_UNSUPPORTED};
}
} // anonymous namespace } // anonymous namespace
void HistogramAlternateProtocolUsage(AlternateProtocolUsage usage, void HistogramAlternateProtocolUsage(AlternateProtocolUsage usage,
...@@ -173,22 +183,38 @@ AlternativeServiceInfoVector ProcessAlternativeServices( ...@@ -173,22 +183,38 @@ AlternativeServiceInfoVector ProcessAlternativeServices(
AlternativeServiceInfoVector alternative_service_info_vector; AlternativeServiceInfoVector alternative_service_info_vector;
for (const spdy::SpdyAltSvcWireFormat::AlternativeService& for (const spdy::SpdyAltSvcWireFormat::AlternativeService&
alternative_service_entry : alternative_service_vector) { alternative_service_entry : alternative_service_vector) {
if (!IsPortValid(alternative_service_entry.port))
continue;
NextProto protocol = NextProto protocol =
NextProtoFromString(alternative_service_entry.protocol_id); NextProtoFromString(alternative_service_entry.protocol_id);
if (!IsAlternateProtocolValid(protocol) ||
!IsProtocolEnabled(protocol, is_http2_enabled, is_quic_enabled) ||
!IsPortValid(alternative_service_entry.port)) {
continue;
}
// Check if QUIC version is supported. Filter supported QUIC versions. // Check if QUIC version is supported. Filter supported QUIC versions.
quic::ParsedQuicVersionVector advertised_versions; quic::ParsedQuicVersionVector advertised_versions;
if (protocol == kProtoQUIC && !alternative_service_entry.version.empty()) { if (protocol == kProtoQUIC) {
advertised_versions = FilterSupportedAltSvcVersions( if (!IsProtocolEnabled(protocol, is_http2_enabled, is_quic_enabled))
alternative_service_entry, supported_quic_versions,
support_ietf_format_quic_altsvc);
if (advertised_versions.empty())
continue; continue;
if (!alternative_service_entry.version.empty()) {
advertised_versions = FilterSupportedAltSvcVersions(
alternative_service_entry, supported_quic_versions,
support_ietf_format_quic_altsvc);
if (advertised_versions.empty())
continue;
}
} else if (!IsAlternateProtocolValid(protocol)) {
quic::ParsedQuicVersion version = ParsedQuicVersionFromAlpn(
alternative_service_entry.protocol_id, supported_quic_versions);
if (version.handshake_protocol == quic::PROTOCOL_UNSUPPORTED ||
version.transport_version == quic::QUIC_VERSION_UNSUPPORTED) {
continue;
}
protocol = kProtoQUIC;
advertised_versions = {version};
} }
if (!IsAlternateProtocolValid(protocol) ||
!IsProtocolEnabled(protocol, is_http2_enabled, is_quic_enabled)) {
continue;
}
AlternativeService alternative_service(protocol, AlternativeService alternative_service(protocol,
alternative_service_entry.host, alternative_service_entry.host,
alternative_service_entry.port); alternative_service_entry.port);
......
...@@ -43,6 +43,7 @@ namespace net { ...@@ -43,6 +43,7 @@ namespace net {
namespace { namespace {
const char kAlternativeServiceHeader[] = "Alt-Svc"; const char kAlternativeServiceHeader[] = "Alt-Svc";
} // namespace } // namespace
HttpStreamFactory::HttpStreamFactory(HttpNetworkSession* session) HttpStreamFactory::HttpStreamFactory(HttpNetworkSession* session)
......
...@@ -3448,6 +3448,188 @@ TEST_F(HttpStreamFactoryTest, MultiIPAliases) { ...@@ -3448,6 +3448,188 @@ TEST_F(HttpStreamFactoryTest, MultiIPAliases) {
HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyServer::Direct()))); HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyServer::Direct())));
} }
class ProcessAlternativeServicesTest : public TestWithTaskEnvironment {
public:
ProcessAlternativeServicesTest() {
session_params_.enable_quic = true;
session_context_.proxy_resolution_service = proxy_resolution_service_.get();
session_context_.host_resolver = &host_resolver_;
session_context_.cert_verifier = &cert_verifier_;
session_context_.transport_security_state = &transport_security_state_;
session_context_.cert_transparency_verifier = &ct_verifier_;
session_context_.client_socket_factory = &socket_factory_;
session_context_.ct_policy_enforcer = &ct_policy_enforcer_;
session_context_.ssl_config_service = &ssl_config_service_;
session_context_.http_server_properties = &http_server_properties_;
}
protected:
HttpNetworkSession::Params session_params_;
HttpNetworkSession::Context session_context_;
std::unique_ptr<HttpNetworkSession> session_;
HttpServerProperties http_server_properties_;
private:
std::unique_ptr<ProxyResolutionService> proxy_resolution_service_ =
ProxyResolutionService::CreateDirect();
SSLConfigServiceDefaults ssl_config_service_;
MockClientSocketFactory socket_factory_;
MockHostResolver host_resolver_;
MockCertVerifier cert_verifier_;
TransportSecurityState transport_security_state_;
MultiLogCTVerifier ct_verifier_;
DefaultCTPolicyEnforcer ct_policy_enforcer_;
};
TEST_F(ProcessAlternativeServicesTest, ProcessEmptyAltSvc) {
session_ =
std::make_unique<HttpNetworkSession>(session_params_, session_context_);
url::SchemeHostPort origin;
NetworkIsolationKey network_isolation_key;
scoped_refptr<HttpResponseHeaders> headers(
base::MakeRefCounted<HttpResponseHeaders>(""));
session_->http_stream_factory()->ProcessAlternativeServices(
session_.get(), network_isolation_key, headers.get(), origin);
AlternativeServiceInfoVector alternatives =
http_server_properties_.GetAlternativeServiceInfos(origin,
network_isolation_key);
EXPECT_TRUE(alternatives.empty());
}
TEST_F(ProcessAlternativeServicesTest, ProcessAltSvcClear) {
session_ =
std::make_unique<HttpNetworkSession>(session_params_, session_context_);
url::SchemeHostPort origin(url::kHttpsScheme, "example.com", 443);
;
NetworkIsolationKey network_isolation_key(
url::Origin::Create(GURL("https://example.com")),
url::Origin::Create(GURL("https://example.com")));
http_server_properties_.SetAlternativeServices(
origin, network_isolation_key,
{AlternativeServiceInfo::CreateQuicAlternativeServiceInfo(
{kProtoQUIC, "", 443},
base::Time::Now() + base::TimeDelta::FromSeconds(30),
quic::AllSupportedVersions())});
EXPECT_FALSE(http_server_properties_
.GetAlternativeServiceInfos(origin, network_isolation_key)
.empty());
scoped_refptr<HttpResponseHeaders> headers(
base::MakeRefCounted<HttpResponseHeaders>(""));
headers->AddHeader("alt-svc: clear");
session_->http_stream_factory()->ProcessAlternativeServices(
session_.get(), network_isolation_key, headers.get(), origin);
AlternativeServiceInfoVector alternatives =
http_server_properties_.GetAlternativeServiceInfos(origin,
network_isolation_key);
EXPECT_TRUE(alternatives.empty());
}
TEST_F(ProcessAlternativeServicesTest, ProcessAltSvcQuic) {
session_params_.quic_params.supported_versions = quic::AllSupportedVersions();
session_ =
std::make_unique<HttpNetworkSession>(session_params_, session_context_);
url::SchemeHostPort origin(url::kHttpsScheme, "example.com", 443);
NetworkIsolationKey network_isolation_key(
url::Origin::Create(GURL("https://example.com")),
url::Origin::Create(GURL("https://example.com")));
scoped_refptr<HttpResponseHeaders> headers(
base::MakeRefCounted<HttpResponseHeaders>(""));
headers->AddHeader("alt-svc: quic=\":443\"; v=\"99,48,47,46,43,39\"");
session_->http_stream_factory()->ProcessAlternativeServices(
session_.get(), network_isolation_key, headers.get(), origin);
AlternativeServiceInfoVector alternatives =
http_server_properties_.GetAlternativeServiceInfos(origin,
network_isolation_key);
ASSERT_EQ(1u, alternatives.size());
EXPECT_EQ(kProtoQUIC, alternatives[0].protocol());
EXPECT_EQ(HostPortPair("example.com", 443), alternatives[0].host_port_pair());
EXPECT_EQ(quic::AllSupportedVersions().size(),
alternatives[0].advertised_versions().size());
for (quic::ParsedQuicVersion version : quic::AllSupportedVersions()) {
EXPECT_TRUE(base::Contains(alternatives[0].advertised_versions(), version))
<< version;
}
}
TEST_F(ProcessAlternativeServicesTest, ProcessAltSvcQuicIetf) {
session_params_.quic_params.supported_versions = quic::AllSupportedVersions();
session_ =
std::make_unique<HttpNetworkSession>(session_params_, session_context_);
url::SchemeHostPort origin(url::kHttpsScheme, "example.com", 443);
NetworkIsolationKey network_isolation_key(
url::Origin::Create(GURL("https://example.com")),
url::Origin::Create(GURL("https://example.com")));
scoped_refptr<HttpResponseHeaders> headers(
base::MakeRefCounted<HttpResponseHeaders>(""));
headers->AddHeader(
"alt-svc: "
"h3-Q099=\":443\",h3-Q048=\":443\",h3-Q047=\":443\",h3-Q043=\":443\",h3-"
"Q039=\":443\"");
session_->http_stream_factory()->ProcessAlternativeServices(
session_.get(), network_isolation_key, headers.get(), origin);
quic::ParsedQuicVersionVector versions = {
{quic::PROTOCOL_QUIC_CRYPTO, quic::QUIC_VERSION_99},
{quic::PROTOCOL_QUIC_CRYPTO, quic::QUIC_VERSION_48},
{quic::PROTOCOL_QUIC_CRYPTO, quic::QUIC_VERSION_47},
{quic::PROTOCOL_QUIC_CRYPTO, quic::QUIC_VERSION_43},
{quic::PROTOCOL_QUIC_CRYPTO, quic::QUIC_VERSION_39},
};
AlternativeServiceInfoVector alternatives =
http_server_properties_.GetAlternativeServiceInfos(origin,
network_isolation_key);
ASSERT_EQ(versions.size(), alternatives.size());
for (size_t i = 0; i < alternatives.size(); ++i) {
EXPECT_EQ(kProtoQUIC, alternatives[i].protocol());
EXPECT_EQ(HostPortPair("example.com", 443),
alternatives[i].host_port_pair());
EXPECT_EQ(1u, alternatives[i].advertised_versions().size());
EXPECT_EQ(versions[i], alternatives[i].advertised_versions()[0]);
}
}
TEST_F(ProcessAlternativeServicesTest, ProcessAltSvcHttp2) {
session_params_.quic_params.supported_versions = quic::AllSupportedVersions();
session_ =
std::make_unique<HttpNetworkSession>(session_params_, session_context_);
url::SchemeHostPort origin(url::kHttpsScheme, "example.com", 443);
NetworkIsolationKey network_isolation_key(
url::Origin::Create(GURL("https://example.com")),
url::Origin::Create(GURL("https://example.com")));
scoped_refptr<HttpResponseHeaders> headers(
base::MakeRefCounted<HttpResponseHeaders>(""));
headers->AddHeader("alt-svc: h2=\"other.example.com:443\"");
session_->http_stream_factory()->ProcessAlternativeServices(
session_.get(), network_isolation_key, headers.get(), origin);
AlternativeServiceInfoVector alternatives =
http_server_properties_.GetAlternativeServiceInfos(origin,
network_isolation_key);
ASSERT_EQ(1u, alternatives.size());
EXPECT_EQ(kProtoHTTP2, alternatives[0].protocol());
EXPECT_EQ(HostPortPair("other.example.com", 443),
alternatives[0].host_port_pair());
EXPECT_EQ(0u, alternatives[0].advertised_versions().size());
}
} // namespace } // namespace
} // namespace net } // namespace net
...@@ -1866,6 +1866,48 @@ TEST_P(QuicNetworkTransactionTest, UseAlternativeServiceForQuic) { ...@@ -1866,6 +1866,48 @@ TEST_P(QuicNetworkTransactionTest, UseAlternativeServiceForQuic) {
SendRequestAndExpectQuicResponse("hello!"); SendRequestAndExpectQuicResponse("hello!");
} }
TEST_P(QuicNetworkTransactionTest, UseIetfAlternativeServiceForQuic) {
std::string alt_svc_header =
"Alt-Svc: " + quic::AlpnForVersion(version_) + "=\":443\"\r\n\r\n";
MockRead http_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"), MockRead(alt_svc_header.data()),
MockRead("hello world"),
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
MockRead(ASYNC, OK)};
StaticSocketDataProvider http_data(http_reads, base::span<MockWrite>());
socket_factory_.AddSocketDataProvider(&http_data);
AddCertificate(&ssl_data_);
socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
MockQuicData mock_quic_data(version_);
mock_quic_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket(1));
mock_quic_data.AddWrite(
SYNCHRONOUS, ConstructClientRequestHeadersPacket(
2, GetNthClientInitiatedBidirectionalStreamId(0), true,
true, GetRequestHeaders("GET", "https", "/")));
mock_quic_data.AddRead(
ASYNC, ConstructServerResponseHeadersPacket(
1, GetNthClientInitiatedBidirectionalStreamId(0), false, false,
GetResponseHeaders("200 OK")));
std::string header = ConstructDataHeader(6);
mock_quic_data.AddRead(
ASYNC, ConstructServerDataPacket(
2, GetNthClientInitiatedBidirectionalStreamId(0), false, true,
header + "hello!"));
mock_quic_data.AddWrite(SYNCHRONOUS, ConstructClientAckPacket(3, 2, 1, 1));
mock_quic_data.AddRead(ASYNC, ERR_IO_PENDING); // No more data to read
mock_quic_data.AddRead(ASYNC, 0); // EOF
mock_quic_data.AddSocketDataToFactory(&socket_factory_);
AddHangingNonAlternateProtocolSocketData();
CreateSession();
SendRequestAndExpectHttpResponse("hello world");
SendRequestAndExpectQuicResponse("hello!");
}
// Much like above, but makes sure NetworkIsolationKey is respected. // Much like above, but makes sure NetworkIsolationKey is respected.
TEST_P(QuicNetworkTransactionTest, TEST_P(QuicNetworkTransactionTest,
UseAlternativeServiceForQuicWithNetworkIsolationKey) { UseAlternativeServiceForQuicWithNetworkIsolationKey) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment