Commit 313d12c6 authored by Renjie Tang's avatar Renjie Tang Committed by Commit Bot

Implement QuicSessionCache.

Change-Id: Ib5fca1d1763f46d4e63b5225674d9b98a97e3efd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2112832
Commit-Queue: Renjie Tang <renjietang@chromium.org>
Reviewed-by: default avatarNick Harper <nharper@chromium.org>
Cr-Commit-Position: refs/heads/master@{#753274}
parent d9fb28c0
...@@ -913,6 +913,8 @@ component("net") { ...@@ -913,6 +913,8 @@ component("net") {
"quic/quic_chromium_packet_reader.h", "quic/quic_chromium_packet_reader.h",
"quic/quic_chromium_packet_writer.cc", "quic/quic_chromium_packet_writer.cc",
"quic/quic_chromium_packet_writer.h", "quic/quic_chromium_packet_writer.h",
"quic/quic_client_session_cache.cc",
"quic/quic_client_session_cache.h",
"quic/quic_clock_skew_detector.cc", "quic/quic_clock_skew_detector.cc",
"quic/quic_clock_skew_detector.h", "quic/quic_clock_skew_detector.h",
"quic/quic_connection_logger.cc", "quic/quic_connection_logger.cc",
...@@ -4331,6 +4333,7 @@ test("net_unittests") { ...@@ -4331,6 +4333,7 @@ test("net_unittests") {
"quic/quic_chromium_client_session_test.cc", "quic/quic_chromium_client_session_test.cc",
"quic/quic_chromium_client_stream_test.cc", "quic/quic_chromium_client_stream_test.cc",
"quic/quic_chromium_connection_helper_test.cc", "quic/quic_chromium_connection_helper_test.cc",
"quic/quic_client_session_cache_unittests.cc",
"quic/quic_clock_skew_detector_test.cc", "quic/quic_clock_skew_detector_test.cc",
"quic/quic_connectivity_probing_manager_test.cc", "quic/quic_connectivity_probing_manager_test.cc",
"quic/quic_end_to_end_unittest.cc", "quic/quic_end_to_end_unittest.cc",
......
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/quic/quic_client_session_cache.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
namespace net {
namespace {
const size_t kDefaultMaxEntries = 1024;
// Check whether the SSL session inside |state| is expired at |now|.
bool IsExpired(quic::QuicResumptionState* state, time_t now) {
if (now < 0)
return true;
SSL_SESSION* session = state->tls_session.get();
uint64_t now_u64 = static_cast<uint64_t>(now);
// now_u64 may be slightly behind because of differences in how
// time is calculated at this layer versus BoringSSL.
// Add a second of wiggle room to account for this.
return now_u64 < SSL_SESSION_get_time(session) - 1 ||
now_u64 >=
SSL_SESSION_get_time(session) + SSL_SESSION_get_timeout(session);
}
} // namespace
QuicClientSessionCache::QuicClientSessionCache()
: QuicClientSessionCache(kDefaultMaxEntries) {}
QuicClientSessionCache::QuicClientSessionCache(size_t max_entries)
: clock_(base::DefaultClock::GetInstance()), cache_(max_entries) {
memory_pressure_listener_.reset(
new base::MemoryPressureListener(base::BindRepeating(
&QuicClientSessionCache::OnMemoryPressure, base::Unretained(this))));
}
QuicClientSessionCache::~QuicClientSessionCache() {
Flush();
}
void QuicClientSessionCache::Insert(
const quic::QuicServerId& server_id,
std::unique_ptr<quic::QuicResumptionState> state) {
cache_.Put(server_id, std::move(state));
}
std::unique_ptr<quic::QuicResumptionState> QuicClientSessionCache::Lookup(
const quic::QuicServerId& server_id,
const SSL_CTX* /*ctx*/) {
auto iter = cache_.Get(server_id);
if (iter == cache_.end())
return nullptr;
time_t now = clock_->Now().ToTimeT();
std::unique_ptr<quic::QuicResumptionState> state = std::move(iter->second);
cache_.Erase(iter);
if (IsExpired(state.get(), now))
state = nullptr;
return state;
}
void QuicClientSessionCache::FlushExpiredStates() {
time_t now = clock_->Now().ToTimeT();
auto iter = cache_.begin();
while (iter != cache_.end()) {
if (IsExpired(iter->second.get(), now)) {
iter = cache_.Erase(iter);
} else {
++iter;
}
}
}
void QuicClientSessionCache::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
switch (memory_pressure_level) {
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
break;
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
FlushExpiredStates();
break;
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
Flush();
break;
}
}
} // namespace net
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_QUIC_QUIC_CLIENT_SESSION_CACHE_H_
#define NET_QUIC_QUIC_CLIENT_SESSION_CACHE_H_
#include <stddef.h>
#include <time.h>
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/containers/mru_cache.h"
#include "base/memory/memory_pressure_monitor.h"
#include "base/time/time.h"
#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h"
#include "third_party/boringssl/src/include/openssl/base.h"
namespace base {
class Clock;
}
namespace net {
class NET_EXPORT_PRIVATE QuicClientSessionCache : public quic::SessionCache {
public:
QuicClientSessionCache();
explicit QuicClientSessionCache(size_t max_entries);
~QuicClientSessionCache() override;
void Insert(const quic::QuicServerId& server_id,
std::unique_ptr<quic::QuicResumptionState> state) override;
std::unique_ptr<quic::QuicResumptionState> Lookup(
const quic::QuicServerId& server_id,
const SSL_CTX* ctx) override;
void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
size_t size() const { return cache_.size(); }
void Flush() { cache_.Clear(); }
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
private:
void FlushExpiredStates();
base::Clock* clock_;
base::MRUCache<quic::QuicServerId, std::unique_ptr<quic::QuicResumptionState>>
cache_;
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
};
} // namespace net
#endif // NET_QUIC_QUIC_CLIENT_SESSION_CACHE_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/quic/quic_client_session_cache.h"
#include "base/run_loop.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
namespace net {
namespace {
std::unique_ptr<base::SimpleTestClock> MakeTestClock() {
std::unique_ptr<base::SimpleTestClock> clock =
std::make_unique<base::SimpleTestClock>();
// SimpleTestClock starts at the null base::Time which converts to and from
// time_t confusingly.
clock->SetNow(base::Time::FromTimeT(1000000000));
return clock;
}
class QuicClientSessionCacheTest : public testing::Test {
public:
QuicClientSessionCacheTest() : ssl_ctx_(SSL_CTX_new(TLS_method())) {}
protected:
bssl::UniquePtr<SSL_SESSION> NewSSLSession() {
SSL_SESSION* session = SSL_SESSION_new(ssl_ctx_.get());
if (!SSL_SESSION_set_protocol_version(session, TLS1_3_VERSION))
return nullptr;
return bssl::UniquePtr<SSL_SESSION>(session);
}
bssl::UniquePtr<SSL_SESSION> MakeTestSession(base::Time now,
base::TimeDelta timeout) {
bssl::UniquePtr<SSL_SESSION> session = NewSSLSession();
SSL_SESSION_set_time(session.get(), now.ToTimeT());
SSL_SESSION_set_timeout(session.get(), timeout.InSeconds());
return session;
}
bssl::UniquePtr<SSL_CTX> ssl_ctx_;
};
} // namespace
// Tests that simple insertion and lookup work correctly.
TEST_F(QuicClientSessionCacheTest, Basic) {
QuicClientSessionCache cache;
std::unique_ptr<quic::QuicResumptionState> state1 =
std::make_unique<quic::QuicResumptionState>();
state1->application_state.push_back('a');
state1->tls_session = NewSSLSession();
quic::QuicServerId id1("a.com", 443);
std::unique_ptr<quic::QuicResumptionState> state2 =
std::make_unique<quic::QuicResumptionState>();
state2->application_state.push_back('b');
state2->tls_session = NewSSLSession();
quic::QuicServerId id2("b.com", 443);
EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get()));
EXPECT_EQ(nullptr, cache.Lookup(id2, ssl_ctx_.get()));
EXPECT_EQ(0u, cache.size());
cache.Insert(id1, std::move(state1));
EXPECT_EQ(1u, cache.size());
EXPECT_EQ('a', cache.Lookup(id1, ssl_ctx_.get())->application_state.front());
EXPECT_EQ(nullptr, cache.Lookup(id2, ssl_ctx_.get()));
std::unique_ptr<quic::QuicResumptionState> state3 =
std::make_unique<quic::QuicResumptionState>();
state3->application_state.push_back('c');
state3->tls_session = NewSSLSession();
quic::QuicServerId id3("c.com", 443);
cache.Insert(id3, std::move(state3));
cache.Insert(id2, std::move(state2));
EXPECT_EQ(2u, cache.size());
EXPECT_EQ('b', cache.Lookup(id2, ssl_ctx_.get())->application_state.front());
EXPECT_EQ('c', cache.Lookup(id3, ssl_ctx_.get())->application_state.front());
// Verify that the cache is cleared after Lookups.
EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get()));
EXPECT_EQ(nullptr, cache.Lookup(id2, ssl_ctx_.get()));
EXPECT_EQ(nullptr, cache.Lookup(id3, ssl_ctx_.get()));
EXPECT_EQ(0u, cache.size());
}
// When the size limit is exceeded, the oldest entry should be erased.
TEST_F(QuicClientSessionCacheTest, SizeLimit) {
QuicClientSessionCache cache(2);
std::unique_ptr<quic::QuicResumptionState> state1 =
std::make_unique<quic::QuicResumptionState>();
state1->application_state.push_back('a');
state1->tls_session = NewSSLSession();
quic::QuicServerId id1("a.com", 443);
std::unique_ptr<quic::QuicResumptionState> state2 =
std::make_unique<quic::QuicResumptionState>();
state2->application_state.push_back('b');
state2->tls_session = NewSSLSession();
quic::QuicServerId id2("b.com", 443);
std::unique_ptr<quic::QuicResumptionState> state3 =
std::make_unique<quic::QuicResumptionState>();
state3->application_state.push_back('c');
state3->tls_session = NewSSLSession();
quic::QuicServerId id3("c.com", 443);
cache.Insert(id1, std::move(state1));
cache.Insert(id2, std::move(state2));
cache.Insert(id3, std::move(state3));
EXPECT_EQ(2u, cache.size());
EXPECT_EQ('b', cache.Lookup(id2, ssl_ctx_.get())->application_state.front());
EXPECT_EQ('c', cache.Lookup(id3, ssl_ctx_.get())->application_state.front());
EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get()));
}
// Expired session isn't considered valid and nullptr will be returned upon
// Lookup.
TEST_F(QuicClientSessionCacheTest, Expiration) {
const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(1000);
QuicClientSessionCache cache;
std::unique_ptr<base::SimpleTestClock> clock = MakeTestClock();
cache.SetClockForTesting(clock.get());
std::unique_ptr<quic::QuicResumptionState> state1 =
std::make_unique<quic::QuicResumptionState>();
state1->tls_session = MakeTestSession(clock->Now(), kTimeout);
quic::QuicServerId id1("a.com", 443);
std::unique_ptr<quic::QuicResumptionState> state2 =
std::make_unique<quic::QuicResumptionState>();
state2->tls_session = MakeTestSession(clock->Now(), 3 * kTimeout);
;
state2->application_state.push_back('b');
quic::QuicServerId id2("b.com", 443);
cache.Insert(id1, std::move(state1));
cache.Insert(id2, std::move(state2));
EXPECT_EQ(2u, cache.size());
// Expire the session.
clock->Advance(kTimeout * 2);
// The entry has not been removed yet.
EXPECT_EQ(2u, cache.size());
EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get()));
EXPECT_EQ(1u, cache.size());
EXPECT_EQ('b', cache.Lookup(id2, ssl_ctx_.get())->application_state.front());
EXPECT_EQ(0u, cache.size());
}
TEST_F(QuicClientSessionCacheTest, FlushOnMemoryNotifications) {
base::test::TaskEnvironment task_environment;
const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(1000);
QuicClientSessionCache cache;
std::unique_ptr<base::SimpleTestClock> clock = MakeTestClock();
cache.SetClockForTesting(clock.get());
std::unique_ptr<quic::QuicResumptionState> state1 =
std::make_unique<quic::QuicResumptionState>();
state1->tls_session = MakeTestSession(clock->Now(), kTimeout);
quic::QuicServerId id1("a.com", 443);
std::unique_ptr<quic::QuicResumptionState> state2 =
std::make_unique<quic::QuicResumptionState>();
state2->tls_session = MakeTestSession(clock->Now(), 3 * kTimeout);
;
state2->application_state.push_back('b');
quic::QuicServerId id2("b.com", 443);
cache.Insert(id1, std::move(state1));
cache.Insert(id2, std::move(state2));
EXPECT_EQ(2u, cache.size());
// Expire the session.
clock->Advance(kTimeout * 2);
// The entry has not been removed yet.
EXPECT_EQ(2u, cache.size());
// Fire a notification that will flush expired sessions.
base::MemoryPressureListener::NotifyMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
base::RunLoop().RunUntilIdle();
// session1 is expired and should be flushed.
EXPECT_EQ(nullptr, cache.Lookup(id1, ssl_ctx_.get()));
EXPECT_EQ(1u, cache.size());
// Fire notification that will flush everything.
base::MemoryPressureListener::NotifyMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, cache.size());
}
} // namespace net
\ No newline at end of file
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