mirror of https://github.com/grpc/grpc.git
parent
c4223da3ef
commit
7ae3733cab
22 changed files with 1513 additions and 111 deletions
@ -0,0 +1,73 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H |
||||
#define GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <grpc/slice.h> |
||||
|
||||
extern "C" { |
||||
#include <openssl/ssl.h> |
||||
} |
||||
|
||||
#include "src/core/lib/gprpp/ref_counted.h" |
||||
|
||||
// The main purpose of code here is to provide means to cache SSL sessions
|
||||
// in a way that they can be shared between connections.
|
||||
//
|
||||
// SSL_SESSION stands for single instance of session and is not generally safe
|
||||
// to share between SSL contexts with different lifetimes. It happens because
|
||||
// not all SSL implementations guarantee immutability of SSL_SESSION object.
|
||||
// See SSL_SESSION documentation in BoringSSL and OpenSSL for more details.
|
||||
|
||||
namespace tsi { |
||||
|
||||
struct SslSessionDeleter { |
||||
void operator()(SSL_SESSION* session) { SSL_SESSION_free(session); } |
||||
}; |
||||
|
||||
typedef std::unique_ptr<SSL_SESSION, SslSessionDeleter> SslSessionPtr; |
||||
|
||||
/// SslCachedSession is an immutable thread-safe storage for single session
|
||||
/// representation. It provides means to share SSL session data (e.g. TLS
|
||||
/// ticket) between encrypted connections regardless of SSL context lifetime.
|
||||
class SslCachedSession { |
||||
public: |
||||
// Not copyable nor movable.
|
||||
SslCachedSession(const SslCachedSession&) = delete; |
||||
SslCachedSession& operator=(const SslCachedSession&) = delete; |
||||
|
||||
/// Create single cached instance of \a session.
|
||||
static grpc_core::UniquePtr<SslCachedSession> Create(SslSessionPtr session); |
||||
|
||||
virtual ~SslCachedSession() = default; |
||||
|
||||
/// Returns a copy of previously cached session.
|
||||
virtual SslSessionPtr CopySession() const GRPC_ABSTRACT; |
||||
|
||||
GRPC_ABSTRACT_BASE_CLASS |
||||
|
||||
protected: |
||||
SslCachedSession() = default; |
||||
}; |
||||
|
||||
} // namespace tsi
|
||||
|
||||
#endif /* GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_H */ |
@ -0,0 +1,58 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/tsi/ssl/session_cache/ssl_session.h" |
||||
|
||||
#ifdef OPENSSL_IS_BORINGSSL |
||||
|
||||
// BoringSSL allows SSL_SESSION to outlive SSL and SSL_CTX objects which are
|
||||
// re-created by gRPC on every certificate rotation or subchannel creation.
|
||||
// BoringSSL guarantees that SSL_SESSION is immutable so it's safe to share
|
||||
// the same original session object between different threads and connections.
|
||||
|
||||
namespace tsi { |
||||
namespace { |
||||
|
||||
class BoringSslCachedSession : public SslCachedSession { |
||||
public: |
||||
BoringSslCachedSession(SslSessionPtr session) |
||||
: session_(std::move(session)) {} |
||||
|
||||
SslSessionPtr CopySession() const override { |
||||
// SslSessionPtr will dereference on destruction.
|
||||
SSL_SESSION_up_ref(session_.get()); |
||||
return SslSessionPtr(session_.get()); |
||||
} |
||||
|
||||
private: |
||||
SslSessionPtr session_; |
||||
}; |
||||
|
||||
} // namespace
|
||||
|
||||
grpc_core::UniquePtr<SslCachedSession> SslCachedSession::Create( |
||||
SslSessionPtr session) { |
||||
return grpc_core::UniquePtr<SslCachedSession>( |
||||
grpc_core::New<BoringSslCachedSession>(std::move(session))); |
||||
} |
||||
|
||||
} // namespace tsi
|
||||
|
||||
#endif /* OPENSSL_IS_BORINGSSL */ |
@ -0,0 +1,211 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h" |
||||
|
||||
#include "src/core/tsi/ssl/session_cache/ssl_session.h" |
||||
|
||||
#include <grpc/support/log.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
namespace tsi { |
||||
|
||||
static void cache_key_avl_destroy(void* key, void* unused) {} |
||||
|
||||
static void* cache_key_avl_copy(void* key, void* unused) { return key; } |
||||
|
||||
static long cache_key_avl_compare(void* key1, void* key2, void* unused) { |
||||
return grpc_slice_cmp(*static_cast<grpc_slice*>(key1), |
||||
*static_cast<grpc_slice*>(key2)); |
||||
} |
||||
|
||||
static void cache_value_avl_destroy(void* value, void* unused) {} |
||||
|
||||
static void* cache_value_avl_copy(void* value, void* unused) { return value; } |
||||
|
||||
// AVL only stores pointers, ownership belonges to the linked list.
|
||||
static const grpc_avl_vtable cache_avl_vtable = { |
||||
cache_key_avl_destroy, cache_key_avl_copy, cache_key_avl_compare, |
||||
cache_value_avl_destroy, cache_value_avl_copy, |
||||
}; |
||||
|
||||
/// Node for single cached session.
|
||||
class SslSessionLRUCache::Node { |
||||
public: |
||||
Node(const grpc_slice& key, SslSessionPtr session) : key_(key) { |
||||
SetSession(std::move(session)); |
||||
} |
||||
|
||||
~Node() { grpc_slice_unref(key_); } |
||||
|
||||
// Not copyable nor movable.
|
||||
Node(const Node&) = delete; |
||||
Node& operator=(const Node&) = delete; |
||||
|
||||
void* AvlKey() { return &key_; } |
||||
|
||||
/// Returns a copy of the node's cache session.
|
||||
SslSessionPtr CopySession() const { return session_->CopySession(); } |
||||
|
||||
/// Set the \a session (which is moved) for the node.
|
||||
void SetSession(SslSessionPtr session) { |
||||
session_ = SslCachedSession::Create(std::move(session)); |
||||
} |
||||
|
||||
private: |
||||
friend class SslSessionLRUCache; |
||||
|
||||
grpc_slice key_; |
||||
grpc_core::UniquePtr<SslCachedSession> session_; |
||||
|
||||
Node* next_ = nullptr; |
||||
Node* prev_ = nullptr; |
||||
}; |
||||
|
||||
SslSessionLRUCache::SslSessionLRUCache(size_t capacity) : capacity_(capacity) { |
||||
GPR_ASSERT(capacity > 0); |
||||
gpr_mu_init(&lock_); |
||||
entry_by_key_ = grpc_avl_create(&cache_avl_vtable); |
||||
} |
||||
|
||||
SslSessionLRUCache::~SslSessionLRUCache() { |
||||
Node* node = use_order_list_head_; |
||||
while (node) { |
||||
Node* next = node->next_; |
||||
grpc_core::Delete(node); |
||||
node = next; |
||||
} |
||||
grpc_avl_unref(entry_by_key_, nullptr); |
||||
gpr_mu_destroy(&lock_); |
||||
} |
||||
|
||||
size_t SslSessionLRUCache::Size() { |
||||
grpc_core::mu_guard guard(&lock_); |
||||
return use_order_list_size_; |
||||
} |
||||
|
||||
SslSessionLRUCache::Node* SslSessionLRUCache::FindLocked( |
||||
const grpc_slice& key) { |
||||
void* value = |
||||
grpc_avl_get(entry_by_key_, const_cast<grpc_slice*>(&key), nullptr); |
||||
if (value == nullptr) { |
||||
return nullptr; |
||||
} |
||||
Node* node = static_cast<Node*>(value); |
||||
// Move to the beginning.
|
||||
Remove(node); |
||||
PushFront(node); |
||||
AssertInvariants(); |
||||
return node; |
||||
} |
||||
|
||||
void SslSessionLRUCache::Put(const char* key, SslSessionPtr session) { |
||||
grpc_core::mu_guard guard(&lock_); |
||||
Node* node = FindLocked(grpc_slice_from_static_string(key)); |
||||
if (node != nullptr) { |
||||
node->SetSession(std::move(session)); |
||||
return; |
||||
} |
||||
grpc_slice key_slice = grpc_slice_from_copied_string(key); |
||||
node = grpc_core::New<Node>(key_slice, std::move(session)); |
||||
PushFront(node); |
||||
entry_by_key_ = grpc_avl_add(entry_by_key_, node->AvlKey(), node, nullptr); |
||||
AssertInvariants(); |
||||
if (use_order_list_size_ > capacity_) { |
||||
GPR_ASSERT(use_order_list_tail_); |
||||
node = use_order_list_tail_; |
||||
Remove(node); |
||||
// Order matters, key is destroyed after deleting node.
|
||||
entry_by_key_ = grpc_avl_remove(entry_by_key_, node->AvlKey(), nullptr); |
||||
grpc_core::Delete(node); |
||||
AssertInvariants(); |
||||
} |
||||
} |
||||
|
||||
SslSessionPtr SslSessionLRUCache::Get(const char* key) { |
||||
grpc_core::mu_guard guard(&lock_); |
||||
// Key is only used for lookups.
|
||||
grpc_slice key_slice = grpc_slice_from_static_string(key); |
||||
Node* node = FindLocked(key_slice); |
||||
if (node == nullptr) { |
||||
return nullptr; |
||||
} |
||||
return node->CopySession(); |
||||
} |
||||
|
||||
void SslSessionLRUCache::Remove(SslSessionLRUCache::Node* node) { |
||||
if (node->prev_ == nullptr) { |
||||
use_order_list_head_ = node->next_; |
||||
} else { |
||||
node->prev_->next_ = node->next_; |
||||
} |
||||
if (node->next_ == nullptr) { |
||||
use_order_list_tail_ = node->prev_; |
||||
} else { |
||||
node->next_->prev_ = node->prev_; |
||||
} |
||||
GPR_ASSERT(use_order_list_size_ >= 1); |
||||
use_order_list_size_--; |
||||
} |
||||
|
||||
void SslSessionLRUCache::PushFront(SslSessionLRUCache::Node* node) { |
||||
if (use_order_list_head_ == nullptr) { |
||||
use_order_list_head_ = node; |
||||
use_order_list_tail_ = node; |
||||
node->next_ = nullptr; |
||||
node->prev_ = nullptr; |
||||
} else { |
||||
node->next_ = use_order_list_head_; |
||||
node->next_->prev_ = node; |
||||
use_order_list_head_ = node; |
||||
node->prev_ = nullptr; |
||||
} |
||||
use_order_list_size_++; |
||||
} |
||||
|
||||
#ifndef NDEBUG |
||||
static size_t calculate_tree_size(grpc_avl_node* node) { |
||||
if (node == nullptr) { |
||||
return 0; |
||||
} |
||||
return 1 + calculate_tree_size(node->left) + calculate_tree_size(node->right); |
||||
} |
||||
|
||||
void SslSessionLRUCache::AssertInvariants() { |
||||
size_t size = 0; |
||||
Node* prev = nullptr; |
||||
Node* current = use_order_list_head_; |
||||
while (current != nullptr) { |
||||
size++; |
||||
GPR_ASSERT(current->prev_ == prev); |
||||
void* node = grpc_avl_get(entry_by_key_, current->AvlKey(), nullptr); |
||||
GPR_ASSERT(node == current); |
||||
prev = current; |
||||
current = current->next_; |
||||
} |
||||
GPR_ASSERT(prev == use_order_list_tail_); |
||||
GPR_ASSERT(size == use_order_list_size_); |
||||
GPR_ASSERT(calculate_tree_size(entry_by_key_.root) == use_order_list_size_); |
||||
} |
||||
#else |
||||
void SslSessionLRUCache::AssertInvariants() {} |
||||
#endif |
||||
|
||||
} // namespace tsi
|
@ -0,0 +1,93 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H |
||||
#define GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <grpc/slice.h> |
||||
#include <grpc/support/sync.h> |
||||
|
||||
extern "C" { |
||||
#include <openssl/ssl.h> |
||||
} |
||||
|
||||
#include "src/core/lib/avl/avl.h" |
||||
#include "src/core/lib/gprpp/memory.h" |
||||
#include "src/core/lib/gprpp/ref_counted.h" |
||||
#include "src/core/tsi/ssl/session_cache/ssl_session.h" |
||||
|
||||
/// Cache for SSL sessions for sessions resumption.
|
||||
///
|
||||
/// Older sessions may be evicted from the cache using LRU policy if capacity
|
||||
/// limit is hit. All sessions are associated with some key, usually server
|
||||
/// name. Note that servers are required to share session ticket encryption keys
|
||||
/// in order for cache to be effective.
|
||||
///
|
||||
/// This class is thread safe.
|
||||
|
||||
namespace tsi { |
||||
|
||||
class SslSessionLRUCache : public grpc_core::RefCounted<SslSessionLRUCache> { |
||||
public: |
||||
/// Create new LRU cache with the given capacity.
|
||||
static grpc_core::RefCountedPtr<SslSessionLRUCache> Create(size_t capacity) { |
||||
return grpc_core::MakeRefCounted<SslSessionLRUCache>(capacity); |
||||
} |
||||
|
||||
// Not copyable nor movable.
|
||||
SslSessionLRUCache(const SslSessionLRUCache&) = delete; |
||||
SslSessionLRUCache& operator=(const SslSessionLRUCache&) = delete; |
||||
|
||||
/// Returns current number of sessions in the cache.
|
||||
size_t Size(); |
||||
/// Add \a session in the cache using \a key. This operation may discard older
|
||||
/// sessions.
|
||||
void Put(const char* key, SslSessionPtr session); |
||||
/// Returns the session from the cache associated with \a key or null if not
|
||||
/// found.
|
||||
SslSessionPtr Get(const char* key); |
||||
|
||||
private: |
||||
// So New() can call our private ctor.
|
||||
template <typename T, typename... Args> |
||||
friend T* grpc_core::New(Args&&... args); |
||||
|
||||
class Node; |
||||
|
||||
explicit SslSessionLRUCache(size_t capacity); |
||||
~SslSessionLRUCache(); |
||||
|
||||
Node* FindLocked(const grpc_slice& key); |
||||
void Remove(Node* node); |
||||
void PushFront(Node* node); |
||||
void AssertInvariants(); |
||||
|
||||
gpr_mu lock_; |
||||
size_t capacity_; |
||||
|
||||
Node* use_order_list_head_ = nullptr; |
||||
Node* use_order_list_tail_ = nullptr; |
||||
size_t use_order_list_size_ = 0; |
||||
grpc_avl entry_by_key_; |
||||
}; |
||||
|
||||
} // namespace tsi
|
||||
|
||||
#endif /* GRPC_CORE_TSI_SSL_SESSION_CACHE_SSL_SESSION_CACHE_H */ |
@ -0,0 +1,76 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/tsi/ssl/session_cache/ssl_session.h" |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#ifndef OPENSSL_IS_BORINGSSL |
||||
|
||||
// OpenSSL invalidates SSL_SESSION on SSL destruction making it pointless
|
||||
// to cache sessions. The workaround is to serialize (relatively expensive)
|
||||
// session into binary blob and re-create it from blob on every handshake.
|
||||
// Note that it's safe to keep serialized session outside of SSL lifetime
|
||||
// as openssl performs all necessary validation while attempting to use a
|
||||
// session and creates a new one if something is wrong (e.g. server changed
|
||||
// set of allowed codecs).
|
||||
|
||||
namespace tsi { |
||||
namespace { |
||||
|
||||
class OpenSslCachedSession : public SslCachedSession { |
||||
public: |
||||
OpenSslCachedSession(SslSessionPtr session) { |
||||
int size = i2d_SSL_SESSION(session.get(), nullptr); |
||||
GPR_ASSERT(size > 0); |
||||
grpc_slice slice = grpc_slice_malloc(size_t(size)); |
||||
unsigned char* start = GRPC_SLICE_START_PTR(slice); |
||||
int second_size = i2d_SSL_SESSION(session.get(), &start); |
||||
GPR_ASSERT(size == second_size); |
||||
serialized_session_ = slice; |
||||
} |
||||
|
||||
virtual ~OpenSslCachedSession() { grpc_slice_unref(serialized_session_); } |
||||
|
||||
SslSessionPtr CopySession() const override { |
||||
const unsigned char* data = GRPC_SLICE_START_PTR(serialized_session_); |
||||
size_t length = GRPC_SLICE_LENGTH(serialized_session_); |
||||
SSL_SESSION* session = d2i_SSL_SESSION(nullptr, &data, length); |
||||
if (session == nullptr) { |
||||
return SslSessionPtr(); |
||||
} |
||||
return SslSessionPtr(session); |
||||
} |
||||
|
||||
private: |
||||
grpc_slice serialized_session_; |
||||
}; |
||||
|
||||
} // namespace
|
||||
|
||||
grpc_core::UniquePtr<SslCachedSession> SslCachedSession::Create( |
||||
SslSessionPtr session) { |
||||
return grpc_core::UniquePtr<SslCachedSession>( |
||||
grpc_core::New<OpenSslCachedSession>(std::move(session))); |
||||
} |
||||
|
||||
} // namespace tsi
|
||||
|
||||
#endif /* OPENSSL_IS_BORINGSSL */ |
@ -0,0 +1,280 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#include "test/core/end2end/end2end_tests.h" |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/gpr/env.h" |
||||
#include "src/core/lib/gpr/host_port.h" |
||||
#include "src/core/lib/gpr/string.h" |
||||
#include "src/core/lib/gpr/tmpfile.h" |
||||
#include "src/core/lib/security/credentials/credentials.h" |
||||
#include "test/core/end2end/cq_verifier.h" |
||||
#include "test/core/end2end/data/ssl_test_data.h" |
||||
#include "test/core/util/port.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
#include <gtest/gtest.h> |
||||
|
||||
namespace grpc { |
||||
namespace testing { |
||||
namespace { |
||||
|
||||
void* tag(intptr_t t) { return (void*)t; } |
||||
|
||||
gpr_timespec five_seconds_time() { return grpc_timeout_seconds_to_deadline(5); } |
||||
|
||||
grpc_server* server_create(grpc_completion_queue* cq, char* server_addr) { |
||||
grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key, |
||||
test_server1_cert}; |
||||
grpc_server_credentials* server_creds = grpc_ssl_server_credentials_create_ex( |
||||
test_root_cert, &pem_cert_key_pair, 1, |
||||
GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY, nullptr); |
||||
|
||||
grpc_server* server = grpc_server_create(nullptr, nullptr); |
||||
grpc_server_register_completion_queue(server, cq, nullptr); |
||||
GPR_ASSERT( |
||||
grpc_server_add_secure_http2_port(server, server_addr, server_creds)); |
||||
grpc_server_credentials_release(server_creds); |
||||
grpc_server_start(server); |
||||
|
||||
return server; |
||||
} |
||||
|
||||
grpc_channel* client_create(char* server_addr, grpc_ssl_session_cache* cache) { |
||||
grpc_ssl_pem_key_cert_pair signed_client_key_cert_pair = { |
||||
test_signed_client_key, test_signed_client_cert}; |
||||
grpc_channel_credentials* client_creds = grpc_ssl_credentials_create( |
||||
test_root_cert, &signed_client_key_cert_pair, nullptr); |
||||
|
||||
grpc_arg args[] = { |
||||
grpc_channel_arg_string_create( |
||||
const_cast<char*>(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), |
||||
const_cast<char*>("waterzooi.test.google.be")), |
||||
grpc_ssl_session_cache_create_channel_arg(cache), |
||||
}; |
||||
|
||||
grpc_channel_args* client_args = |
||||
grpc_channel_args_copy_and_add(nullptr, args, GPR_ARRAY_SIZE(args)); |
||||
|
||||
grpc_channel* client = grpc_secure_channel_create(client_creds, server_addr, |
||||
client_args, nullptr); |
||||
GPR_ASSERT(client != nullptr); |
||||
grpc_channel_credentials_release(client_creds); |
||||
|
||||
{ |
||||
grpc_core::ExecCtx exec_ctx; |
||||
grpc_channel_args_destroy(client_args); |
||||
} |
||||
|
||||
return client; |
||||
} |
||||
|
||||
void do_round_trip(grpc_completion_queue* cq, grpc_server* server, |
||||
char* server_addr, grpc_ssl_session_cache* cache, |
||||
bool expect_session_reuse) { |
||||
grpc_channel* client = client_create(server_addr, cache); |
||||
|
||||
cq_verifier* cqv = cq_verifier_create(cq); |
||||
grpc_op ops[6]; |
||||
grpc_op* op; |
||||
grpc_metadata_array initial_metadata_recv; |
||||
grpc_metadata_array trailing_metadata_recv; |
||||
grpc_metadata_array request_metadata_recv; |
||||
grpc_call_details call_details; |
||||
grpc_status_code status; |
||||
grpc_call_error error; |
||||
grpc_slice details; |
||||
int was_cancelled = 2; |
||||
|
||||
gpr_timespec deadline = grpc_timeout_seconds_to_deadline(60); |
||||
grpc_call* c = grpc_channel_create_call( |
||||
client, nullptr, GRPC_PROPAGATE_DEFAULTS, cq, |
||||
grpc_slice_from_static_string("/foo"), nullptr, deadline, nullptr); |
||||
GPR_ASSERT(c); |
||||
|
||||
grpc_metadata_array_init(&initial_metadata_recv); |
||||
grpc_metadata_array_init(&trailing_metadata_recv); |
||||
grpc_metadata_array_init(&request_metadata_recv); |
||||
grpc_call_details_init(&call_details); |
||||
|
||||
memset(ops, 0, sizeof(ops)); |
||||
op = ops; |
||||
op->op = GRPC_OP_SEND_INITIAL_METADATA; |
||||
op->data.send_initial_metadata.count = 0; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
op->op = GRPC_OP_RECV_INITIAL_METADATA; |
||||
op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
op->op = GRPC_OP_RECV_STATUS_ON_CLIENT; |
||||
op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv; |
||||
op->data.recv_status_on_client.status = &status; |
||||
op->data.recv_status_on_client.status_details = &details; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1), |
||||
nullptr); |
||||
GPR_ASSERT(GRPC_CALL_OK == error); |
||||
|
||||
grpc_call* s; |
||||
error = grpc_server_request_call(server, &s, &call_details, |
||||
&request_metadata_recv, cq, cq, tag(101)); |
||||
GPR_ASSERT(GRPC_CALL_OK == error); |
||||
CQ_EXPECT_COMPLETION(cqv, tag(101), 1); |
||||
cq_verify(cqv); |
||||
|
||||
grpc_auth_context* auth = grpc_call_auth_context(s); |
||||
grpc_auth_property_iterator it = grpc_auth_context_find_properties_by_name( |
||||
auth, GRPC_SSL_SESSION_REUSED_PROPERTY); |
||||
const grpc_auth_property* property = grpc_auth_property_iterator_next(&it); |
||||
GPR_ASSERT(property != nullptr); |
||||
|
||||
if (expect_session_reuse) { |
||||
GPR_ASSERT(strcmp(property->value, "true") == 0); |
||||
} else { |
||||
GPR_ASSERT(strcmp(property->value, "false") == 0); |
||||
} |
||||
grpc_auth_context_release(auth); |
||||
|
||||
memset(ops, 0, sizeof(ops)); |
||||
op = ops; |
||||
op->op = GRPC_OP_SEND_INITIAL_METADATA; |
||||
op->data.send_initial_metadata.count = 0; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
op->op = GRPC_OP_RECV_CLOSE_ON_SERVER; |
||||
op->data.recv_close_on_server.cancelled = &was_cancelled; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
op->op = GRPC_OP_SEND_STATUS_FROM_SERVER; |
||||
op->data.send_status_from_server.trailing_metadata_count = 0; |
||||
op->data.send_status_from_server.status = GRPC_STATUS_OK; |
||||
op->flags = 0; |
||||
op->reserved = nullptr; |
||||
op++; |
||||
error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(103), |
||||
nullptr); |
||||
GPR_ASSERT(GRPC_CALL_OK == error); |
||||
|
||||
CQ_EXPECT_COMPLETION(cqv, tag(103), 1); |
||||
CQ_EXPECT_COMPLETION(cqv, tag(1), 1); |
||||
cq_verify(cqv); |
||||
|
||||
grpc_metadata_array_destroy(&initial_metadata_recv); |
||||
grpc_metadata_array_destroy(&trailing_metadata_recv); |
||||
grpc_metadata_array_destroy(&request_metadata_recv); |
||||
grpc_call_details_destroy(&call_details); |
||||
|
||||
grpc_call_unref(c); |
||||
grpc_call_unref(s); |
||||
|
||||
cq_verifier_destroy(cqv); |
||||
|
||||
grpc_channel_destroy(client); |
||||
} |
||||
|
||||
void drain_cq(grpc_completion_queue* cq) { |
||||
grpc_event ev; |
||||
do { |
||||
ev = grpc_completion_queue_next(cq, five_seconds_time(), nullptr); |
||||
} while (ev.type != GRPC_QUEUE_SHUTDOWN); |
||||
} |
||||
|
||||
TEST(H2SessionReuseTest, SingleReuse) { |
||||
int port = grpc_pick_unused_port_or_die(); |
||||
|
||||
char* server_addr; |
||||
gpr_join_host_port(&server_addr, "localhost", port); |
||||
|
||||
grpc_completion_queue* cq = grpc_completion_queue_create_for_next(nullptr); |
||||
grpc_ssl_session_cache* cache = grpc_ssl_session_cache_create_lru(16); |
||||
|
||||
grpc_server* server = server_create(cq, server_addr); |
||||
|
||||
do_round_trip(cq, server, server_addr, cache, false); |
||||
do_round_trip(cq, server, server_addr, cache, true); |
||||
do_round_trip(cq, server, server_addr, cache, true); |
||||
|
||||
gpr_free(server_addr); |
||||
grpc_ssl_session_cache_destroy(cache); |
||||
|
||||
GPR_ASSERT(grpc_completion_queue_next( |
||||
cq, grpc_timeout_milliseconds_to_deadline(100), nullptr) |
||||
.type == GRPC_QUEUE_TIMEOUT); |
||||
|
||||
grpc_completion_queue* shutdown_cq = |
||||
grpc_completion_queue_create_for_pluck(nullptr); |
||||
grpc_server_shutdown_and_notify(server, shutdown_cq, tag(1000)); |
||||
GPR_ASSERT(grpc_completion_queue_pluck(shutdown_cq, tag(1000), |
||||
grpc_timeout_seconds_to_deadline(5), |
||||
nullptr) |
||||
.type == GRPC_OP_COMPLETE); |
||||
grpc_server_destroy(server); |
||||
grpc_completion_queue_destroy(shutdown_cq); |
||||
|
||||
grpc_completion_queue_shutdown(cq); |
||||
drain_cq(cq); |
||||
grpc_completion_queue_destroy(cq); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace grpc
|
||||
|
||||
int main(int argc, char** argv) { |
||||
FILE* roots_file; |
||||
size_t roots_size = strlen(test_root_cert); |
||||
char* roots_filename; |
||||
|
||||
grpc_test_init(argc, argv); |
||||
/* Set the SSL roots env var. */ |
||||
roots_file = gpr_tmpfile("chttp2_ssl_session_reuse_test", &roots_filename); |
||||
GPR_ASSERT(roots_filename != nullptr); |
||||
GPR_ASSERT(roots_file != nullptr); |
||||
GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size); |
||||
fclose(roots_file); |
||||
gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename); |
||||
|
||||
grpc_init(); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
|
||||
/* Cleanup. */ |
||||
remove(roots_filename); |
||||
gpr_free(roots_filename); |
||||
|
||||
return ret; |
||||
} |
@ -0,0 +1,154 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#include <string> |
||||
#include <unordered_set> |
||||
|
||||
#include "src/core/tsi/ssl/session_cache/ssl_session_cache.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
#include <grpc/grpc.h> |
||||
#include <grpc/support/log.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
class SessionTracker; |
||||
|
||||
struct SessionExDataId { |
||||
SessionTracker* tracker; |
||||
long id; |
||||
}; |
||||
|
||||
class SessionTracker { |
||||
public: |
||||
SessionTracker() { ssl_context_ = SSL_CTX_new(TLSv1_2_method()); } |
||||
|
||||
~SessionTracker() { SSL_CTX_free(ssl_context_); } |
||||
|
||||
tsi::SslSessionPtr NewSession(long id) { |
||||
static int ex_data_id = SSL_SESSION_get_ex_new_index( |
||||
0, nullptr, nullptr, nullptr, DestroyExData); |
||||
GPR_ASSERT(ex_data_id != -1); |
||||
// OpenSSL and different version of BoringSSL don't agree on API
|
||||
// so try both.
|
||||
tsi::SslSessionPtr session = NewSessionInternal(SSL_SESSION_new); |
||||
SessionExDataId* data = new SessionExDataId{this, id}; |
||||
int result = SSL_SESSION_set_ex_data(session.get(), ex_data_id, data); |
||||
EXPECT_EQ(result, 1); |
||||
alive_sessions_.insert(id); |
||||
return session; |
||||
} |
||||
|
||||
bool IsAlive(long id) const { |
||||
return alive_sessions_.find(id) != alive_sessions_.end(); |
||||
} |
||||
|
||||
size_t AliveCount() const { return alive_sessions_.size(); } |
||||
|
||||
private: |
||||
tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)()) { |
||||
return tsi::SslSessionPtr(cb()); |
||||
} |
||||
|
||||
tsi::SslSessionPtr NewSessionInternal(SSL_SESSION* (*cb)(const SSL_CTX*)) { |
||||
return tsi::SslSessionPtr(cb(ssl_context_)); |
||||
} |
||||
|
||||
static void DestroyExData(void* parent, void* ptr, CRYPTO_EX_DATA* ad, |
||||
int index, long argl, void* argp) { |
||||
SessionExDataId* data = static_cast<SessionExDataId*>(ptr); |
||||
data->tracker->alive_sessions_.erase(data->id); |
||||
delete data; |
||||
} |
||||
|
||||
SSL_CTX* ssl_context_; |
||||
std::unordered_set<long> alive_sessions_; |
||||
}; |
||||
|
||||
TEST(SslSessionCacheTest, InitialState) { |
||||
SessionTracker tracker; |
||||
// Verify session initial state.
|
||||
{ |
||||
tsi::SslSessionPtr tmp_sess = tracker.NewSession(1); |
||||
EXPECT_EQ(tmp_sess->references, 1); |
||||
EXPECT_TRUE(tracker.IsAlive(1)); |
||||
EXPECT_EQ(tracker.AliveCount(), 1); |
||||
} |
||||
EXPECT_FALSE(tracker.IsAlive(1)); |
||||
EXPECT_EQ(tracker.AliveCount(), 0); |
||||
} |
||||
|
||||
TEST(SslSessionCacheTest, LruCache) { |
||||
SessionTracker tracker; |
||||
{ |
||||
RefCountedPtr<tsi::SslSessionLRUCache> cache = |
||||
tsi::SslSessionLRUCache::Create(3); |
||||
tsi::SslSessionPtr sess2 = tracker.NewSession(2); |
||||
SSL_SESSION* sess2_ptr = sess2.get(); |
||||
cache->Put("first.dropbox.com", std::move(sess2)); |
||||
EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess2_ptr); |
||||
EXPECT_TRUE(tracker.IsAlive(2)); |
||||
EXPECT_EQ(tracker.AliveCount(), 1); |
||||
// Putting element with the same key destroys old session.
|
||||
tsi::SslSessionPtr sess3 = tracker.NewSession(3); |
||||
SSL_SESSION* sess3_ptr = sess3.get(); |
||||
cache->Put("first.dropbox.com", std::move(sess3)); |
||||
EXPECT_FALSE(tracker.IsAlive(2)); |
||||
EXPECT_EQ(cache->Get("first.dropbox.com").get(), sess3_ptr); |
||||
EXPECT_TRUE(tracker.IsAlive(3)); |
||||
EXPECT_EQ(tracker.AliveCount(), 1); |
||||
// Putting three more elements discards current one.
|
||||
for (long id = 4; id < 7; id++) { |
||||
EXPECT_TRUE(tracker.IsAlive(3)); |
||||
std::string domain = std::to_string(id) + ".random.domain"; |
||||
cache->Put(domain.c_str(), tracker.NewSession(id)); |
||||
} |
||||
EXPECT_EQ(cache->Size(), 3); |
||||
EXPECT_FALSE(tracker.IsAlive(3)); |
||||
EXPECT_EQ(tracker.AliveCount(), 3); |
||||
// Accessing element moves it into front of the queue.
|
||||
EXPECT_TRUE(cache->Get("4.random.domain")); |
||||
EXPECT_TRUE(tracker.IsAlive(4)); |
||||
EXPECT_TRUE(tracker.IsAlive(5)); |
||||
EXPECT_TRUE(tracker.IsAlive(6)); |
||||
// One element has to be evicted from cache->
|
||||
cache->Put("7.random.domain", tracker.NewSession(7)); |
||||
EXPECT_TRUE(tracker.IsAlive(4)); |
||||
EXPECT_FALSE(tracker.IsAlive(5)); |
||||
EXPECT_TRUE(tracker.IsAlive(6)); |
||||
EXPECT_TRUE(tracker.IsAlive(7)); |
||||
EXPECT_EQ(tracker.AliveCount(), 3); |
||||
} |
||||
// Cache destructor destroys all sessions.
|
||||
EXPECT_EQ(tracker.AliveCount(), 0); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc_test_init(argc, argv); |
||||
grpc_init(); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return ret; |
||||
} |
Loading…
Reference in new issue