// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/http/alternative_service.h" #include "base/check_op.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros_local.h" #include "base/notreached.h" #include "base/strings/stringprintf.h" #include "net/base/port_util.h" #include "net/third_party/quiche/src/quiche/quic/core/http/spdy_utils.h" #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h" namespace net { void HistogramAlternateProtocolUsage(AlternateProtocolUsage usage, bool is_google_host) { UMA_HISTOGRAM_ENUMERATION("Net.AlternateProtocolUsage", usage, ALTERNATE_PROTOCOL_USAGE_MAX); if (is_google_host) { UMA_HISTOGRAM_ENUMERATION("Net.AlternateProtocolUsageGoogle", usage, ALTERNATE_PROTOCOL_USAGE_MAX); } } void HistogramBrokenAlternateProtocolLocation( BrokenAlternateProtocolLocation location) { UMA_HISTOGRAM_ENUMERATION("Net.AlternateProtocolBrokenLocation", location, BROKEN_ALTERNATE_PROTOCOL_LOCATION_MAX); } bool IsAlternateProtocolValid(NextProto protocol) { switch (protocol) { case kProtoUnknown: return false; case kProtoHTTP11: return false; case kProtoHTTP2: return true; case kProtoQUIC: return true; } NOTREACHED(); } bool IsProtocolEnabled(NextProto protocol, bool is_http2_enabled, bool is_quic_enabled) { switch (protocol) { case kProtoUnknown: NOTREACHED(); case kProtoHTTP11: return true; case kProtoHTTP2: return is_http2_enabled; case kProtoQUIC: return is_quic_enabled; } NOTREACHED(); } AlternativeService::AlternativeService(NextProto protocol, std::string_view host, uint16_t port) : protocol(protocol), host(host), port(port) {} AlternativeService::AlternativeService(NextProto protocol, const HostPortPair& host_port_pair) : AlternativeService(protocol, host_port_pair.host(), host_port_pair.port()) {} AlternativeService::AlternativeService( const AlternativeService& alternative_service) = default; AlternativeService::AlternativeService(AlternativeService&&) noexcept = default; AlternativeService& AlternativeService::operator=( const AlternativeService& alternative_service) = default; AlternativeService& AlternativeService::operator=(AlternativeService&&) = default; HostPortPair AlternativeService::GetHostPortPair() const { return HostPortPair(host, port); } std::strong_ordering AlternativeService::operator<=>( const AlternativeService& other) const = default; std::string AlternativeService::ToString() const { return base::StringPrintf("%s %s:%d", NextProtoToString(protocol), host.c_str(), port); } std::ostream& operator<<(std::ostream& os, const AlternativeService& alternative_service) { os << alternative_service.ToString(); return os; } // static AlternativeServiceInfo AlternativeServiceInfo::CreateHttp2AlternativeServiceInfo( const AlternativeService& alternative_service, base::Time expiration) { DCHECK_EQ(alternative_service.protocol, kProtoHTTP2); return AlternativeServiceInfo(alternative_service, expiration, quic::ParsedQuicVersionVector()); } // static AlternativeServiceInfo AlternativeServiceInfo::CreateQuicAlternativeServiceInfo( const AlternativeService& alternative_service, base::Time expiration, const quic::ParsedQuicVersionVector& advertised_versions) { DCHECK_EQ(alternative_service.protocol, kProtoQUIC); return AlternativeServiceInfo(alternative_service, expiration, advertised_versions); } AlternativeServiceInfo::AlternativeServiceInfo() = default; AlternativeServiceInfo::AlternativeServiceInfo( const AlternativeServiceInfo& alternative_service_info) = default; AlternativeServiceInfo::AlternativeServiceInfo( AlternativeServiceInfo&&) noexcept = default; AlternativeServiceInfo& AlternativeServiceInfo::operator=( AlternativeServiceInfo&&) = default; AlternativeServiceInfo& AlternativeServiceInfo::operator=( const AlternativeServiceInfo& alternative_service_info) = default; AlternativeServiceInfo::~AlternativeServiceInfo() = default; bool AlternativeServiceInfo::operator==( const AlternativeServiceInfo& other) const = default; std::string AlternativeServiceInfo::ToString() const { // NOTE: Cannot use `base::UnlocalizedTimeFormatWithPattern()` since // `net/DEPS` disallows `base/i18n`. base::Time::Exploded exploded; expiration_.LocalExplode(&exploded); return base::StringPrintf( "%s, expires %04d-%02d-%02d %02d:%02d:%02d", alternative_service_.ToString().c_str(), exploded.year, exploded.month, exploded.day_of_month, exploded.hour, exploded.minute, exploded.second); } void AlternativeServiceInfo::SetAdvertisedVersions( const quic::ParsedQuicVersionVector& advertised_versions) { if (alternative_service_.protocol != kProtoQUIC) { return; } advertised_versions_ = advertised_versions; std::ranges::sort(advertised_versions_, {}, &quic::ParsedQuicVersion::transport_version); } AlternativeServiceInfoVector ProcessAlternativeServices( const spdy::SpdyAltSvcWireFormat::AlternativeServiceVector& alternative_service_vector, bool is_http2_enabled, bool is_quic_enabled, const quic::ParsedQuicVersionVector& supported_quic_versions) { // Convert spdy::SpdyAltSvcWireFormat::AlternativeService entries // to AlternativeServiceInfo. AlternativeServiceInfoVector alternative_service_info_vector; for (const spdy::SpdyAltSvcWireFormat::AlternativeService& alternative_service_entry : alternative_service_vector) { if (!IsPortValid(alternative_service_entry.port)) { continue; } NextProto protocol = NextProtoFromString(alternative_service_entry.protocol_id); quic::ParsedQuicVersionVector advertised_versions; if (protocol == kProtoQUIC) { continue; // Ignore legacy QUIC alt-svc advertisements. } else if (!IsAlternateProtocolValid(protocol)) { quic::ParsedQuicVersion version = quic::SpdyUtils::ExtractQuicVersionFromAltSvcEntry( alternative_service_entry, supported_quic_versions); if (version == quic::ParsedQuicVersion::Unsupported()) { continue; } protocol = kProtoQUIC; advertised_versions = {version}; } if (!IsAlternateProtocolValid(protocol) || !IsProtocolEnabled(protocol, is_http2_enabled, is_quic_enabled)) { continue; } AlternativeService alternative_service(protocol, alternative_service_entry.host, alternative_service_entry.port); base::Time expiration = base::Time::Now() + base::Seconds(alternative_service_entry.max_age_seconds); AlternativeServiceInfo alternative_service_info; if (protocol == kProtoQUIC) { alternative_service_info = AlternativeServiceInfo::CreateQuicAlternativeServiceInfo( alternative_service, expiration, advertised_versions); } else { alternative_service_info = AlternativeServiceInfo::CreateHttp2AlternativeServiceInfo( alternative_service, expiration); } alternative_service_info_vector.push_back(alternative_service_info); } return alternative_service_info_vector; } AlternativeServiceInfo::AlternativeServiceInfo( const AlternativeService& alternative_service, base::Time expiration, const quic::ParsedQuicVersionVector& advertised_versions) : alternative_service_(alternative_service), expiration_(expiration) { if (alternative_service_.protocol == kProtoQUIC) { advertised_versions_ = advertised_versions; } } } // namespace net