1
#include "source/common/http/http_server_properties_cache_impl.h"
2

            
3
#include <memory>
4

            
5
#include "source/common/common/logger.h"
6
#include "source/common/http/http3_status_tracker_impl.h"
7
#include "source/common/runtime/runtime_features.h"
8

            
9
#include "quiche/http2/core/spdy_alt_svc_wire_format.h"
10
#include "re2/re2.h"
11

            
12
namespace Envoy {
13
namespace Http {
14
namespace {
15

            
16
struct RegexHolder {
17
4
  RegexHolder() : origin_regex("(.*)://(.*):(\\d+)") {}
18

            
19
  const re2::RE2 origin_regex;
20
};
21

            
22
using ConstRegexHolder = ConstSingleton<RegexHolder>;
23

            
24
} // namespace
25

            
26
std::string
27
79
HttpServerPropertiesCacheImpl::originToString(const HttpServerPropertiesCache::Origin& origin) {
28
79
  return absl::StrCat(origin.scheme_, "://", origin.hostname_, ":", origin.port_);
29
79
}
30

            
31
absl::optional<HttpServerPropertiesCache::Origin>
32
28
HttpServerPropertiesCacheImpl::stringToOrigin(const std::string& str) {
33
28
  const re2::RE2& origin_regex = ConstRegexHolder::get().origin_regex;
34
28
  std::string scheme;
35
28
  std::string hostname;
36
28
  int port = 0;
37
28
  if (re2::RE2::FullMatch(str.c_str(), origin_regex, &scheme, &hostname, &port)) {
38
21
    return HttpServerPropertiesCache::Origin(scheme, hostname, port);
39
21
  }
40
7
  return {};
41
28
}
42

            
43
76
std::string HttpServerPropertiesCacheImpl::originDataToStringForCache(const OriginData& data) {
44
76
  std::string value;
45
76
  if (!data.protocols.has_value() || data.protocols->empty()) {
46
4
    value = "clear";
47
72
  } else {
48
84
    for (auto& protocol : *data.protocols) {
49
84
      if (!value.empty()) {
50
12
        value.push_back(',');
51
12
      }
52
84
      absl::StrAppend(&value, protocol.alpn_, "=\"", protocol.hostname_, ":", protocol.port_, "\"");
53
      // Note this is _not_ actually the max age, but the absolute time at which
54
      // this entry will expire. protocolsFromString will convert back to ma.
55
84
      absl::StrAppend(
56
84
          &value, "; ma=",
57
84
          std::chrono::duration_cast<std::chrono::seconds>(protocol.expiration_.time_since_epoch())
58
84
              .count());
59
84
    }
60
72
  }
61
76
  absl::StrAppend(&value, "|", data.srtt.count(), "|", data.concurrent_streams);
62
76
  return value;
63
76
}
64

            
65
absl::optional<HttpServerPropertiesCacheImpl::OriginData>
66
HttpServerPropertiesCacheImpl::originDataFromString(absl::string_view origin_data_string,
67
37
                                                    TimeSource& time_source, bool from_cache) {
68
37
  const std::vector<absl::string_view> parts = absl::StrSplit(origin_data_string, '|');
69
37
  if (parts.size() != 3) {
70
3
    return {};
71
3
  }
72

            
73
34
  OriginData data;
74
34
  data.protocols = alternateProtocolsFromString(parts[0], time_source, from_cache);
75

            
76
34
  int64_t srtt;
77
34
  if (!absl::SimpleAtoi(parts[1], &srtt)) {
78
2
    return {};
79
2
  }
80
32
  data.srtt = std::chrono::microseconds(srtt);
81

            
82
32
  int32_t concurrency;
83
32
  if (!absl::SimpleAtoi(parts[2], &concurrency)) {
84
2
    return {};
85
2
  }
86
30
  data.concurrent_streams = concurrency;
87

            
88
30
  return data;
89
32
}
90

            
91
std::vector<Http::HttpServerPropertiesCache::AlternateProtocol>
92
HttpServerPropertiesCacheImpl::alternateProtocolsFromString(absl::string_view altsvc_str,
93
                                                            TimeSource& time_source,
94
55
                                                            bool from_cache) {
95
55
  spdy::SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
96
55
  if (!spdy::SpdyAltSvcWireFormat::ParseHeaderFieldValue(altsvc_str, &altsvc_vector)) {
97
1
    return {};
98
1
  }
99
54
  std::vector<Http::HttpServerPropertiesCache::AlternateProtocol> results;
100
63
  for (const auto& alt_svc : altsvc_vector) {
101
63
    MonotonicTime expiration;
102
63
    if (from_cache) {
103
36
      auto expire_time_from_epoch = std::chrono::seconds(alt_svc.max_age_seconds);
104
36
      auto time_since_epoch = std::chrono::duration_cast<std::chrono::seconds>(
105
36
          time_source.monotonicTime().time_since_epoch());
106
36
      if (expire_time_from_epoch < time_since_epoch) {
107
        expiration = time_source.monotonicTime();
108
36
      } else {
109
36
        expiration = time_source.monotonicTime() + (expire_time_from_epoch - time_since_epoch);
110
36
      }
111
51
    } else {
112
27
      expiration = time_source.monotonicTime() + std::chrono::seconds(alt_svc.max_age_seconds);
113
27
    }
114
63
    results.emplace_back(alt_svc.protocol_id, alt_svc.host, alt_svc.port, expiration);
115
63
  }
116
54
  return results;
117
55
}
118

            
119
HttpServerPropertiesCacheImpl::HttpServerPropertiesCacheImpl(
120
    Event::Dispatcher& dispatcher, std::vector<std::string>&& canonical_suffixes,
121
    std::unique_ptr<KeyValueStore>&& key_value_store, size_t max_entries)
122
5361
    : dispatcher_(dispatcher), canonical_suffixes_(canonical_suffixes),
123
5361
      max_entries_(max_entries > 0 ? max_entries : 1024) {
124
5361
  if (key_value_store) {
125
54
    KeyValueStore::ConstIterateCb load_protocols = [this](const std::string& key,
126
54
                                                          const std::string& value) {
127
18
      absl::optional<OriginData> origin_data =
128
18
          originDataFromString(value, dispatcher_.timeSource(), true);
129
18
      absl::optional<Origin> origin = stringToOrigin(key);
130
18
      if (origin_data.has_value() && origin.has_value()) {
131
        // We deferred transfering ownership into key_value_store_ prior, so
132
        // that we won't end up doing redundant updates to the store while
133
        // iterating.
134
17
        OptRef<std::vector<AlternateProtocol>> protocols;
135
17
        if (origin_data->protocols.has_value()) {
136
17
          protocols = *origin_data->protocols;
137
17
        }
138
17
        OriginDataWithOptRef data(protocols, origin_data->srtt, nullptr,
139
17
                                  origin_data->concurrent_streams);
140
17
        setPropertiesImpl(*origin, data);
141
17
      } else {
142
1
        ENVOY_LOG(warn,
143
1
                  fmt::format("Unable to parse cache entry with key: {} value: {}", key, value));
144
1
      }
145
18
      return KeyValueStore::Iterate::Continue;
146
18
    };
147
54
    key_value_store->iterate(load_protocols);
148
54
    key_value_store_ = std::move(key_value_store);
149
54
  }
150
5361
}
151

            
152
5361
HttpServerPropertiesCacheImpl::~HttpServerPropertiesCacheImpl() = default;
153

            
154
void HttpServerPropertiesCacheImpl::setAlternatives(const Origin& origin,
155
108
                                                    std::vector<AlternateProtocol>& protocols) {
156
108
  OriginDataWithOptRef data;
157
108
  data.protocols = protocols;
158
108
  auto it = setPropertiesImpl(origin, data);
159
108
  if (key_value_store_) {
160
45
    key_value_store_->addOrUpdate(originToString(origin), originDataToStringForCache(it->second),
161
45
                                  absl::nullopt);
162
45
  }
163
108
}
164

            
165
30
void HttpServerPropertiesCacheImpl::setSrtt(const Origin& origin, std::chrono::microseconds srtt) {
166
30
  OriginDataWithOptRef data;
167
30
  data.srtt = srtt;
168
30
  auto it = setPropertiesImpl(origin, data);
169
30
  if (key_value_store_) {
170
23
    key_value_store_->addOrUpdate(originToString(origin), originDataToStringForCache(it->second),
171
23
                                  absl::nullopt);
172
23
  }
173
30
}
174

            
175
std::chrono::microseconds HttpServerPropertiesCacheImpl::getSrtt(const Origin& origin,
176
146
                                                                 bool use_canonical_suffix) const {
177
146
  auto entry_it = protocols_.find(origin);
178
146
  if (entry_it != protocols_.end()) {
179
71
    return entry_it->second.srtt;
180
71
  }
181
75
  if (use_canonical_suffix) {
182
12
    absl::optional<Origin> canonical = getCanonicalOrigin(origin.hostname_);
183
12
    if (canonical.has_value()) {
184
6
      entry_it = protocols_.find(*canonical);
185
6
      if (entry_it != protocols_.end()) {
186
6
        return entry_it->second.srtt;
187
6
      }
188
6
    }
189
12
  }
190
69
  return std::chrono::microseconds(0);
191
75
}
192

            
193
void HttpServerPropertiesCacheImpl::setConcurrentStreams(const Origin& origin,
194
127
                                                         uint32_t concurrent_streams) {
195
127
  OriginDataWithOptRef data;
196
127
  data.concurrent_streams = concurrent_streams;
197
127
  auto it = setPropertiesImpl(origin, data);
198
127
  if (key_value_store_) {
199
1
    key_value_store_->addOrUpdate(originToString(origin), originDataToStringForCache(it->second),
200
1
                                  absl::nullopt);
201
1
  }
202
127
}
203

            
204
168
uint32_t HttpServerPropertiesCacheImpl::getConcurrentStreams(const Origin& origin) const {
205
168
  auto entry_it = protocols_.find(origin);
206
168
  if (entry_it == protocols_.end()) {
207
142
    return 0;
208
142
  }
209
26
  return entry_it->second.concurrent_streams;
210
168
}
211

            
212
HttpServerPropertiesCacheImpl::ProtocolsMap::iterator
213
HttpServerPropertiesCacheImpl::setPropertiesImpl(const Origin& origin,
214
299
                                                 OriginDataWithOptRef& origin_data) {
215
299
  if (origin_data.protocols.has_value()) {
216
125
    maybeSetCanonicalOrigin(origin);
217
125
    std::vector<AlternateProtocol>& protocols = *origin_data.protocols;
218
125
    static const size_t max_protocols = 10;
219
125
    if (protocols.size() > max_protocols) {
220
2
      ENVOY_LOG_MISC(trace, "Too many alternate protocols: {}, truncating", protocols.size());
221
2
      protocols.erase(protocols.begin() + max_protocols, protocols.end());
222
2
    }
223
249
  } else if (origin_data.srtt.count() > 0 &&
224
174
             Runtime::runtimeFeatureEnabled(
225
30
                 "envoy.reloadable_features.use_canonical_suffix_for_srtt")) {
226
4
    maybeSetCanonicalOrigin(origin);
227
4
  }
228
299
  auto entry_it = protocols_.find(origin);
229
299
  if (entry_it != protocols_.end()) {
230
49
    if (origin_data.protocols.has_value()) {
231
15
      entry_it->second.protocols = *origin_data.protocols;
232
15
    }
233
49
    if (origin_data.srtt.count()) {
234
24
      entry_it->second.srtt = origin_data.srtt;
235
24
    }
236
49
    if (origin_data.h3_status_tracker) {
237
      entry_it->second.h3_status_tracker = std::move(origin_data.h3_status_tracker);
238
    }
239

            
240
49
    return entry_it;
241
49
  }
242
250
  return addOriginData(origin,
243
250
                       {origin_data.protocols, origin_data.srtt,
244
250
                        std::move(origin_data.h3_status_tracker), origin_data.concurrent_streams});
245
299
}
246

            
247
HttpServerPropertiesCacheImpl::ProtocolsMap::iterator
248
250
HttpServerPropertiesCacheImpl::addOriginData(const Origin& origin, OriginData&& origin_data) {
249
250
  ASSERT(protocols_.find(origin) == protocols_.end());
250
255
  while (protocols_.size() >= max_entries_) {
251
5
    auto iter = protocols_.begin();
252
5
    if (key_value_store_) {
253
3
      key_value_store_->remove(originToString(iter->first));
254
3
    }
255
5
    protocols_.erase(iter);
256
5
  }
257
250
  protocols_[origin] = std::move(origin_data);
258
250
  return protocols_.find(origin);
259
250
}
260

            
261
OptRef<const std::vector<HttpServerPropertiesCache::AlternateProtocol>>
262
134
HttpServerPropertiesCacheImpl::findAlternatives(const Origin& origin) {
263
134
  auto entry_it = protocols_.find(origin);
264
134
  if (entry_it == protocols_.end() || !entry_it->second.protocols.has_value()) {
265
32
    absl::optional<Origin> canonical = getCanonicalOrigin(origin.hostname_);
266
32
    if (canonical.has_value()) {
267
7
      entry_it = protocols_.find(*canonical);
268
7
    }
269
32
    if (entry_it == protocols_.end() || !entry_it->second.protocols.has_value()) {
270
25
      return makeOptRefFromPtr<const std::vector<AlternateProtocol>>(nullptr);
271
25
    }
272
32
  }
273
109
  std::vector<AlternateProtocol>& protocols = *entry_it->second.protocols;
274

            
275
109
  auto original_size = protocols.size();
276
109
  const MonotonicTime now = dispatcher_.timeSource().monotonicTime();
277
109
  protocols.erase(std::remove_if(protocols.begin(), protocols.end(),
278
128
                                 [now](const AlternateProtocol& protocol) {
279
127
                                   return (now > protocol.expiration_);
280
127
                                 }),
281
109
                  protocols.end());
282

            
283
109
  if (protocols.empty()) {
284
6
    if (key_value_store_) {
285
2
      key_value_store_->remove(originToString(origin));
286
2
    }
287
6
    return makeOptRefFromPtr<const std::vector<AlternateProtocol>>(nullptr);
288
6
  }
289
103
  if (key_value_store_ && original_size != protocols.size()) {
290
1
    key_value_store_->addOrUpdate(originToString(origin),
291
1
                                  originDataToStringForCache(entry_it->second), absl::nullopt);
292
1
  }
293
103
  return makeOptRef(const_cast<const std::vector<AlternateProtocol>&>(protocols));
294
109
}
295

            
296
25
size_t HttpServerPropertiesCacheImpl::size() const { return protocols_.size(); }
297

            
298
HttpServerPropertiesCache::Http3StatusTracker&
299
393
HttpServerPropertiesCacheImpl::getOrCreateHttp3StatusTracker(const Origin& origin) {
300
393
  auto entry_it = protocols_.find(origin);
301
393
  if (entry_it != protocols_.end()) {
302
376
    if (entry_it->second.h3_status_tracker == nullptr) {
303
69
      entry_it->second.h3_status_tracker = std::make_unique<Http3StatusTrackerImpl>(dispatcher_);
304
69
    }
305
376
    return *entry_it->second.h3_status_tracker;
306
376
  }
307

            
308
17
  OriginDataWithOptRef data;
309
17
  data.h3_status_tracker = std::make_unique<Http3StatusTrackerImpl>(dispatcher_);
310
17
  auto it = setPropertiesImpl(origin, data);
311
17
  return *it->second.h3_status_tracker;
312
393
}
313

            
314
28
void HttpServerPropertiesCacheImpl::markHttp3Broken(const Origin& origin) {
315
28
  getOrCreateHttp3StatusTracker(origin).markHttp3Broken();
316
28
  if (Runtime::runtimeFeatureEnabled(
317
28
          "envoy.reloadable_features.use_canonical_suffix_for_quic_brokenness")) {
318
8
    maybeSetCanonicalOriginForHttp3Brokenness(origin);
319
8
  }
320
28
}
321

            
322
135
bool HttpServerPropertiesCacheImpl::isHttp3Broken(const Origin& origin) {
323
135
  if (!Runtime::runtimeFeatureEnabled(
324
135
          "envoy.reloadable_features.use_canonical_suffix_for_quic_brokenness")) {
325
111
    return getOrCreateHttp3StatusTracker(origin).isHttp3Broken();
326
111
  }
327

            
328
  // Note that we don't create a new tracker for the origin.
329
24
  if (auto entry_it = protocols_.find(origin);
330
24
      entry_it != protocols_.end() && entry_it->second.h3_status_tracker != nullptr) {
331
6
    return entry_it->second.h3_status_tracker->isHttp3Broken();
332
6
  }
333

            
334
18
  absl::optional<Origin> canonical = getCanonicalOriginForHttp3Brokenness(origin.hostname_);
335
18
  if (!canonical.has_value()) {
336
10
    return false;
337
10
  }
338
8
  if (auto entry_it = protocols_.find(*canonical); entry_it != protocols_.end()) {
339
8
    if (entry_it->second.h3_status_tracker == nullptr) {
340
      ENVOY_BUG(false, "the canonical origin doesn't have HTTP3 tracker");
341
      return false;
342
    }
343
8
    return entry_it->second.h3_status_tracker->isHttp3Broken();
344
8
  }
345

            
346
  return false;
347
8
}
348

            
349
absl::optional<HttpServerPropertiesCacheImpl::Origin>
350
18
HttpServerPropertiesCacheImpl::getCanonicalOriginForHttp3Brokenness(absl::string_view hostname) {
351
18
  absl::string_view suffix = getCanonicalSuffix(hostname);
352
18
  if (suffix.empty()) {
353
2
    return {};
354
2
  }
355

            
356
16
  auto it = canonical_h3_brokenness_map_.find(suffix);
357
16
  if (it == canonical_h3_brokenness_map_.end()) {
358
8
    return {};
359
8
  }
360
8
  return it->second;
361
16
}
362

            
363
void HttpServerPropertiesCacheImpl::maybeSetCanonicalOriginForHttp3Brokenness(
364
8
    const Origin& origin) {
365
8
  absl::string_view suffix = getCanonicalSuffix(origin.hostname_);
366
8
  if (suffix.empty()) {
367
    return;
368
  }
369
8
  canonical_h3_brokenness_map_[suffix] = origin;
370
8
}
371

            
372
2
void HttpServerPropertiesCacheImpl::resetBrokenness() {
373
6
  for (auto& protocol : protocols_) {
374
6
    if (protocol.second.h3_status_tracker && protocol.second.h3_status_tracker->isHttp3Broken()) {
375
4
      protocol.second.h3_status_tracker->markHttp3FailedRecently();
376
4
    }
377
6
  }
378
2
}
379

            
380
void HttpServerPropertiesCacheImpl::resetStatus() {
381
  for (const std::pair<const Origin, OriginData>& protocol : protocols_) {
382
    if (protocol.second.h3_status_tracker) {
383
      protocol.second.h3_status_tracker->markHttp3Pending();
384
    }
385
  }
386
}
387

            
388
absl::string_view
389
199
HttpServerPropertiesCacheImpl::getCanonicalSuffix(absl::string_view hostname) const {
390
199
  for (const std::string& suffix : canonical_suffixes_) {
391
69
    if (absl::EndsWith(hostname, suffix)) {
392
59
      return suffix;
393
59
    }
394
69
  }
395
140
  return "";
396
199
}
397

            
398
absl::optional<HttpServerPropertiesCache::Origin>
399
44
HttpServerPropertiesCacheImpl::getCanonicalOrigin(absl::string_view hostname) const {
400
44
  absl::string_view suffix = getCanonicalSuffix(hostname);
401
44
  if (suffix.empty()) {
402
28
    return {};
403
28
  }
404

            
405
16
  auto it = canonical_alt_svc_map_.find(suffix);
406
16
  if (it == canonical_alt_svc_map_.end()) {
407
3
    return {};
408
3
  }
409
13
  return it->second;
410
16
}
411

            
412
129
void HttpServerPropertiesCacheImpl::maybeSetCanonicalOrigin(const Origin& origin) {
413
129
  absl::string_view suffix = getCanonicalSuffix(origin.hostname_);
414
129
  if (suffix.empty()) {
415
110
    return;
416
110
  }
417

            
418
19
  canonical_alt_svc_map_[suffix] = origin;
419
19
}
420

            
421
} // namespace Http
422
} // namespace Envoy