1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/alternative_service.h"
6
7 #include "base/check_op.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/metrics/histogram_macros_local.h"
10 #include "base/notreached.h"
11 #include "base/strings/stringprintf.h"
12 #include "net/base/port_util.h"
13 #include "net/third_party/quiche/src/quiche/quic/core/http/spdy_utils.h"
14 #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
15
16 namespace net {
17
HistogramAlternateProtocolUsage(AlternateProtocolUsage usage,bool is_google_host)18 void HistogramAlternateProtocolUsage(AlternateProtocolUsage usage,
19 bool is_google_host) {
20 UMA_HISTOGRAM_ENUMERATION("Net.AlternateProtocolUsage", usage,
21 ALTERNATE_PROTOCOL_USAGE_MAX);
22 if (is_google_host) {
23 UMA_HISTOGRAM_ENUMERATION("Net.AlternateProtocolUsageGoogle", usage,
24 ALTERNATE_PROTOCOL_USAGE_MAX);
25 }
26 }
27
HistogramBrokenAlternateProtocolLocation(BrokenAlternateProtocolLocation location)28 void HistogramBrokenAlternateProtocolLocation(
29 BrokenAlternateProtocolLocation location) {
30 UMA_HISTOGRAM_ENUMERATION("Net.AlternateProtocolBrokenLocation", location,
31 BROKEN_ALTERNATE_PROTOCOL_LOCATION_MAX);
32 }
33
IsAlternateProtocolValid(NextProto protocol)34 bool IsAlternateProtocolValid(NextProto protocol) {
35 switch (protocol) {
36 case kProtoUnknown:
37 return false;
38 case kProtoHTTP11:
39 return false;
40 case kProtoHTTP2:
41 return true;
42 case kProtoQUIC:
43 return true;
44 }
45 NOTREACHED();
46 }
47
IsProtocolEnabled(NextProto protocol,bool is_http2_enabled,bool is_quic_enabled)48 bool IsProtocolEnabled(NextProto protocol,
49 bool is_http2_enabled,
50 bool is_quic_enabled) {
51 switch (protocol) {
52 case kProtoUnknown:
53 NOTREACHED();
54 case kProtoHTTP11:
55 return true;
56 case kProtoHTTP2:
57 return is_http2_enabled;
58 case kProtoQUIC:
59 return is_quic_enabled;
60 }
61 NOTREACHED();
62 }
63
AlternativeService(NextProto protocol,std::string_view host,uint16_t port)64 AlternativeService::AlternativeService(NextProto protocol,
65 std::string_view host,
66 uint16_t port)
67 : protocol(protocol), host(host), port(port) {}
68
AlternativeService(NextProto protocol,const HostPortPair & host_port_pair)69 AlternativeService::AlternativeService(NextProto protocol,
70 const HostPortPair& host_port_pair)
71 : AlternativeService(protocol,
72 host_port_pair.host(),
73 host_port_pair.port()) {}
74
75 AlternativeService::AlternativeService(
76 const AlternativeService& alternative_service) = default;
77 AlternativeService::AlternativeService(AlternativeService&&) noexcept = default;
78
79 AlternativeService& AlternativeService::operator=(
80 const AlternativeService& alternative_service) = default;
81 AlternativeService& AlternativeService::operator=(AlternativeService&&) =
82 default;
83
GetHostPortPair() const84 HostPortPair AlternativeService::GetHostPortPair() const {
85 return HostPortPair(host, port);
86 }
87
88 std::strong_ordering AlternativeService::operator<=>(
89 const AlternativeService& other) const = default;
90
ToString() const91 std::string AlternativeService::ToString() const {
92 return base::StringPrintf("%s %s:%d", NextProtoToString(protocol),
93 host.c_str(), port);
94 }
95
operator <<(std::ostream & os,const AlternativeService & alternative_service)96 std::ostream& operator<<(std::ostream& os,
97 const AlternativeService& alternative_service) {
98 os << alternative_service.ToString();
99 return os;
100 }
101
102 // static
103 AlternativeServiceInfo
CreateHttp2AlternativeServiceInfo(const AlternativeService & alternative_service,base::Time expiration)104 AlternativeServiceInfo::CreateHttp2AlternativeServiceInfo(
105 const AlternativeService& alternative_service,
106 base::Time expiration) {
107 DCHECK_EQ(alternative_service.protocol, kProtoHTTP2);
108 return AlternativeServiceInfo(alternative_service, expiration,
109 quic::ParsedQuicVersionVector());
110 }
111
112 // static
CreateQuicAlternativeServiceInfo(const AlternativeService & alternative_service,base::Time expiration,const quic::ParsedQuicVersionVector & advertised_versions)113 AlternativeServiceInfo AlternativeServiceInfo::CreateQuicAlternativeServiceInfo(
114 const AlternativeService& alternative_service,
115 base::Time expiration,
116 const quic::ParsedQuicVersionVector& advertised_versions) {
117 DCHECK_EQ(alternative_service.protocol, kProtoQUIC);
118 return AlternativeServiceInfo(alternative_service, expiration,
119 advertised_versions);
120 }
121
122 AlternativeServiceInfo::AlternativeServiceInfo() = default;
123
124 AlternativeServiceInfo::AlternativeServiceInfo(
125 const AlternativeServiceInfo& alternative_service_info) = default;
126 AlternativeServiceInfo::AlternativeServiceInfo(
127 AlternativeServiceInfo&&) noexcept = default;
128
129 AlternativeServiceInfo& AlternativeServiceInfo::operator=(
130 AlternativeServiceInfo&&) = default;
131 AlternativeServiceInfo& AlternativeServiceInfo::operator=(
132 const AlternativeServiceInfo& alternative_service_info) = default;
133
134 AlternativeServiceInfo::~AlternativeServiceInfo() = default;
135
136 bool AlternativeServiceInfo::operator==(
137 const AlternativeServiceInfo& other) const = default;
138
ToString() const139 std::string AlternativeServiceInfo::ToString() const {
140 // NOTE: Cannot use `base::UnlocalizedTimeFormatWithPattern()` since
141 // `net/DEPS` disallows `base/i18n`.
142 base::Time::Exploded exploded;
143 expiration_.LocalExplode(&exploded);
144 return base::StringPrintf(
145 "%s, expires %04d-%02d-%02d %02d:%02d:%02d",
146 alternative_service_.ToString().c_str(), exploded.year, exploded.month,
147 exploded.day_of_month, exploded.hour, exploded.minute, exploded.second);
148 }
149
SetAdvertisedVersions(const quic::ParsedQuicVersionVector & advertised_versions)150 void AlternativeServiceInfo::SetAdvertisedVersions(
151 const quic::ParsedQuicVersionVector& advertised_versions) {
152 if (alternative_service_.protocol != kProtoQUIC) {
153 return;
154 }
155
156 advertised_versions_ = advertised_versions;
157 std::ranges::sort(advertised_versions_, {},
158 &quic::ParsedQuicVersion::transport_version);
159 }
160
ProcessAlternativeServices(const spdy::SpdyAltSvcWireFormat::AlternativeServiceVector & alternative_service_vector,bool is_http2_enabled,bool is_quic_enabled,const quic::ParsedQuicVersionVector & supported_quic_versions)161 AlternativeServiceInfoVector ProcessAlternativeServices(
162 const spdy::SpdyAltSvcWireFormat::AlternativeServiceVector&
163 alternative_service_vector,
164 bool is_http2_enabled,
165 bool is_quic_enabled,
166 const quic::ParsedQuicVersionVector& supported_quic_versions) {
167 // Convert spdy::SpdyAltSvcWireFormat::AlternativeService entries
168 // to AlternativeServiceInfo.
169 AlternativeServiceInfoVector alternative_service_info_vector;
170 for (const spdy::SpdyAltSvcWireFormat::AlternativeService&
171 alternative_service_entry : alternative_service_vector) {
172 if (!IsPortValid(alternative_service_entry.port)) {
173 continue;
174 }
175
176 NextProto protocol =
177 NextProtoFromString(alternative_service_entry.protocol_id);
178 quic::ParsedQuicVersionVector advertised_versions;
179 if (protocol == kProtoQUIC) {
180 continue; // Ignore legacy QUIC alt-svc advertisements.
181 } else if (!IsAlternateProtocolValid(protocol)) {
182 quic::ParsedQuicVersion version =
183 quic::SpdyUtils::ExtractQuicVersionFromAltSvcEntry(
184 alternative_service_entry, supported_quic_versions);
185 if (version == quic::ParsedQuicVersion::Unsupported()) {
186 continue;
187 }
188 protocol = kProtoQUIC;
189 advertised_versions = {version};
190 }
191 if (!IsAlternateProtocolValid(protocol) ||
192 !IsProtocolEnabled(protocol, is_http2_enabled, is_quic_enabled)) {
193 continue;
194 }
195
196 AlternativeService alternative_service(protocol,
197 alternative_service_entry.host,
198 alternative_service_entry.port);
199 base::Time expiration =
200 base::Time::Now() +
201 base::Seconds(alternative_service_entry.max_age_seconds);
202 AlternativeServiceInfo alternative_service_info;
203 if (protocol == kProtoQUIC) {
204 alternative_service_info =
205 AlternativeServiceInfo::CreateQuicAlternativeServiceInfo(
206 alternative_service, expiration, advertised_versions);
207 } else {
208 alternative_service_info =
209 AlternativeServiceInfo::CreateHttp2AlternativeServiceInfo(
210 alternative_service, expiration);
211 }
212 alternative_service_info_vector.push_back(alternative_service_info);
213 }
214 return alternative_service_info_vector;
215 }
216
AlternativeServiceInfo(const AlternativeService & alternative_service,base::Time expiration,const quic::ParsedQuicVersionVector & advertised_versions)217 AlternativeServiceInfo::AlternativeServiceInfo(
218 const AlternativeService& alternative_service,
219 base::Time expiration,
220 const quic::ParsedQuicVersionVector& advertised_versions)
221 : alternative_service_(alternative_service), expiration_(expiration) {
222 if (alternative_service_.protocol == kProtoQUIC) {
223 advertised_versions_ = advertised_versions;
224 }
225 }
226
227 } // namespace net
228