1 /*
2 *
3 * Copyright 2018 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include <string>
20 #include <unordered_set>
21
22 #include "src/core/tsi/ssl/session_cache/ssl_session_cache.h"
23 #include "test/core/util/test_config.h"
24
25 #include <grpc/grpc.h>
26 #include <grpc/support/log.h>
27 #include <gtest/gtest.h>
28
29 namespace grpc_core {
30
31 namespace {
32
33 class SessionTracker;
34
35 struct SessionExDataId {
36 SessionTracker* tracker;
37 long id;
38 };
39
40 class SessionTracker {
41 public:
SessionTracker()42 SessionTracker() { ssl_context_ = SSL_CTX_new(TLSv1_2_method()); }
43
~SessionTracker()44 ~SessionTracker() { SSL_CTX_free(ssl_context_); }
45
NewSession(long id)46 tsi::SslSessionPtr NewSession(long id) {
47 static int ex_data_id = SSL_SESSION_get_ex_new_index(
48 0, nullptr, nullptr, nullptr, DestroyExData);
49 GPR_ASSERT(ex_data_id != -1);
50 // OpenSSL and different version of BoringSSL don't agree on API
51 // so try both.
52 tsi::SslSessionPtr session = NewSessionInternal(SSL_SESSION_new);
53 SessionExDataId* data = new SessionExDataId{this, id};
54 int result = SSL_SESSION_set_ex_data(session.get(), ex_data_id, data);
55 EXPECT_EQ(result, 1);
56 alive_sessions_.insert(id);
57 return session;
58 }
59
IsAlive(long id) const60 bool IsAlive(long id) const {
61 return alive_sessions_.find(id) != alive_sessions_.end();
62 }
63
AliveCount() const64 size_t AliveCount() const { return alive_sessions_.size(); }
65
66 private:
NewSessionInternal(SSL_SESSION * (* cb)())67 tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)()) {
68 return tsi::SslSessionPtr(cb());
69 }
70
NewSessionInternal(SSL_SESSION * (* cb)(const SSL_CTX *))71 tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)(const SSL_CTX*)) {
72 return tsi::SslSessionPtr(cb(ssl_context_));
73 }
74
DestroyExData(void * parent,void * ptr,CRYPTO_EX_DATA * ad,int index,long argl,void * argp)75 static void DestroyExData(void* parent, void* ptr, CRYPTO_EX_DATA* ad,
76 int index, long argl, void* argp) {
77 SessionExDataId* data = static_cast<SessionExDataId*>(ptr);
78 data->tracker->alive_sessions_.erase(data->id);
79 delete data;
80 }
81
82 SSL_CTX* ssl_context_;
83 std::unordered_set<long> alive_sessions_;
84 };
85
TEST(SslSessionCacheTest,InitialState)86 TEST(SslSessionCacheTest, InitialState) {
87 SessionTracker tracker;
88 // Verify session initial state.
89 {
90 tsi::SslSessionPtr tmp_sess = tracker.NewSession(1);
91 EXPECT_TRUE(tracker.IsAlive(1));
92 EXPECT_EQ(tracker.AliveCount(), 1);
93 }
94 EXPECT_FALSE(tracker.IsAlive(1));
95 EXPECT_EQ(tracker.AliveCount(), 0);
96 }
97
TEST(SslSessionCacheTest,LruCache)98 TEST(SslSessionCacheTest, LruCache) {
99 SessionTracker tracker;
100 {
101 RefCountedPtr<tsi::SslSessionLRUCache> cache =
102 tsi::SslSessionLRUCache::Create(3);
103 tsi::SslSessionPtr sess2 = tracker.NewSession(2);
104 SSL_SESSION* sess2_ptr = sess2.get();
105 cache->Put("first.dropbox.com", std::move(sess2));
106 EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess2_ptr);
107 EXPECT_TRUE(tracker.IsAlive(2));
108 EXPECT_EQ(tracker.AliveCount(), 1);
109 // Putting element with the same key destroys old session.
110 tsi::SslSessionPtr sess3 = tracker.NewSession(3);
111 SSL_SESSION* sess3_ptr = sess3.get();
112 cache->Put("first.dropbox.com", std::move(sess3));
113 EXPECT_FALSE(tracker.IsAlive(2));
114 EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess3_ptr);
115 EXPECT_TRUE(tracker.IsAlive(3));
116 EXPECT_EQ(tracker.AliveCount(), 1);
117 // Putting three more elements discards current one.
118 for (long id = 4; id < 7; id++) {
119 EXPECT_TRUE(tracker.IsAlive(3));
120 std::string domain = std::to_string(id) + ".random.domain";
121 cache->Put(domain.c_str(), tracker.NewSession(id));
122 }
123 EXPECT_EQ(cache->Size(), 3);
124 EXPECT_FALSE(tracker.IsAlive(3));
125 EXPECT_EQ(tracker.AliveCount(), 3);
126 // Accessing element moves it into front of the queue.
127 EXPECT_TRUE(cache->Get("4.random.domain"));
128 EXPECT_TRUE(tracker.IsAlive(4));
129 EXPECT_TRUE(tracker.IsAlive(5));
130 EXPECT_TRUE(tracker.IsAlive(6));
131 // One element has to be evicted from cache->
132 cache->Put("7.random.domain", tracker.NewSession(7));
133 EXPECT_TRUE(tracker.IsAlive(4));
134 EXPECT_FALSE(tracker.IsAlive(5));
135 EXPECT_TRUE(tracker.IsAlive(6));
136 EXPECT_TRUE(tracker.IsAlive(7));
137 EXPECT_EQ(tracker.AliveCount(), 3);
138 }
139 // Cache destructor destroys all sessions.
140 EXPECT_EQ(tracker.AliveCount(), 0);
141 }
142
143 } // namespace
144 } // namespace grpc_core
145
main(int argc,char ** argv)146 int main(int argc, char** argv) {
147 ::testing::InitGoogleTest(&argc, argv);
148 grpc_test_init(argc, argv);
149 grpc_init();
150 int ret = RUN_ALL_TESTS();
151 grpc_shutdown();
152 return ret;
153 }
154