| /** @file |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you 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 "P_SSLConfig.h" |
| #include "SSLSessionCache.h" |
| #include "SSLStats.h" |
| |
| #include <cstring> |
| |
| #define SSLSESSIONCACHE_STRINGIFY0(x) #x |
| #define SSLSESSIONCACHE_STRINGIFY(x) SSLSESSIONCACHE_STRINGIFY0(x) |
| #define SSLSESSIONCACHE_LINENO SSLSESSIONCACHE_STRINGIFY(__LINE__) |
| |
| #ifdef DEBUG |
| #define PRINT_BUCKET(x) this->print(x " at " __FILE__ ":" SSLSESSIONCACHE_LINENO); |
| #else |
| #define PRINT_BUCKET(x) |
| #endif |
| |
| /* Session Cache */ |
| SSLSessionCache::SSLSessionCache() : nbuckets(SSLConfigParams::session_cache_number_buckets) |
| { |
| Debug("ssl.session_cache", "Created new ssl session cache %p with %zu buckets each with size max size %zu", this, nbuckets, |
| SSLConfigParams::session_cache_max_bucket_size); |
| |
| session_bucket = new SSLSessionBucket[nbuckets]; |
| } |
| |
| SSLSessionCache::~SSLSessionCache() |
| { |
| delete[] session_bucket; |
| } |
| |
| int |
| SSLSessionCache::getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const |
| { |
| uint64_t hash = sid.hash(); |
| uint64_t target_bucket = hash % nbuckets; |
| SSLSessionBucket *bucket = &session_bucket[target_bucket]; |
| |
| return bucket->getSessionBuffer(sid, buffer, len); |
| } |
| |
| bool |
| SSLSessionCache::getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data) const |
| { |
| uint64_t hash = sid.hash(); |
| uint64_t target_bucket = hash % nbuckets; |
| SSLSessionBucket *bucket = &session_bucket[target_bucket]; |
| |
| if (is_debug_tag_set("ssl.session_cache")) { |
| char buf[sid.len * 2 + 1]; |
| sid.toString(buf, sizeof(buf)); |
| Debug("ssl.session_cache.get", "SessionCache looking in bucket %" PRId64 " (%p) for session '%s' (hash: %" PRIX64 ").", |
| target_bucket, bucket, buf, hash); |
| } |
| |
| return bucket->getSession(sid, sess, data); |
| } |
| |
| void |
| SSLSessionCache::removeSession(const SSLSessionID &sid) |
| { |
| uint64_t hash = sid.hash(); |
| uint64_t target_bucket = hash % nbuckets; |
| SSLSessionBucket *bucket = &session_bucket[target_bucket]; |
| |
| if (is_debug_tag_set("ssl.session_cache")) { |
| char buf[sid.len * 2 + 1]; |
| sid.toString(buf, sizeof(buf)); |
| Debug("ssl.session_cache.remove", "SessionCache using bucket %" PRId64 " (%p): Removing session '%s' (hash: %" PRIX64 ").", |
| target_bucket, bucket, buf, hash); |
| } |
| |
| if (ssl_rsb) { |
| SSL_INCREMENT_DYN_STAT(ssl_session_cache_eviction); |
| } |
| bucket->removeSession(sid); |
| } |
| |
| void |
| SSLSessionCache::insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl) |
| { |
| uint64_t hash = sid.hash(); |
| uint64_t target_bucket = hash % nbuckets; |
| SSLSessionBucket *bucket = &session_bucket[target_bucket]; |
| |
| if (is_debug_tag_set("ssl.session_cache")) { |
| char buf[sid.len * 2 + 1]; |
| sid.toString(buf, sizeof(buf)); |
| Debug("ssl.session_cache.insert", "SessionCache using bucket %" PRId64 " (%p): Inserting session '%s' (hash: %" PRIX64 ").", |
| target_bucket, bucket, buf, hash); |
| } |
| |
| bucket->insertSession(sid, sess, ssl); |
| } |
| |
| void |
| SSLSessionBucket::insertSession(const SSLSessionID &id, SSL_SESSION *sess, SSL *ssl) |
| { |
| std::shared_lock r_lock(mutex, std::try_to_lock); |
| if (!r_lock.owns_lock()) { |
| if (ssl_rsb) { |
| SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention); |
| } |
| if (SSLConfigParams::session_cache_skip_on_lock_contention) { |
| return; |
| } |
| r_lock.lock(); |
| } |
| |
| // Don't insert if it is already there |
| if (bucket_map.find(id) != bucket_map.end()) { |
| return; |
| } |
| |
| r_lock.unlock(); |
| |
| size_t len = i2d_SSL_SESSION(sess, nullptr); // make sure we're not going to need more than SSL_MAX_SESSION_SIZE bytes |
| /* do not cache a session that's too big. */ |
| if (len > static_cast<size_t>(SSL_MAX_SESSION_SIZE)) { |
| Debug("ssl.session_cache", "Unable to save SSL session because size of %zd exceeds the max of %d", len, SSL_MAX_SESSION_SIZE); |
| return; |
| } |
| |
| if (is_debug_tag_set("ssl.session_cache")) { |
| char buf[id.len * 2 + 1]; |
| id.toString(buf, sizeof(buf)); |
| Debug("ssl.session_cache", "Inserting session '%s' to bucket %p.", buf, this); |
| } |
| |
| Ptr<IOBufferData> buf; |
| Ptr<IOBufferData> buf_exdata; |
| size_t len_exdata = sizeof(ssl_session_cache_exdata); |
| buf = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED); |
| ink_release_assert(static_cast<size_t>(buf->block_size()) >= len); |
| unsigned char *loc = reinterpret_cast<unsigned char *>(buf->data()); |
| i2d_SSL_SESSION(sess, &loc); |
| buf_exdata = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED); |
| ink_release_assert(static_cast<size_t>(buf_exdata->block_size()) >= len_exdata); |
| ssl_session_cache_exdata *exdata = reinterpret_cast<ssl_session_cache_exdata *>(buf_exdata->data()); |
| // This could be moved to a function in charge of populating exdata |
| exdata->curve = (ssl == nullptr) ? 0 : SSLGetCurveNID(ssl); |
| |
| ats_scoped_obj<SSLSession> ssl_session(new SSLSession(id, buf, len, buf_exdata)); |
| |
| std::unique_lock w_lock(mutex, std::try_to_lock); |
| if (!w_lock.owns_lock()) { |
| if (ssl_rsb) { |
| SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention); |
| } |
| if (SSLConfigParams::session_cache_skip_on_lock_contention) { |
| return; |
| } |
| w_lock.lock(); |
| } |
| |
| PRINT_BUCKET("insertSession before") |
| if (bucket_map.size() >= SSLConfigParams::session_cache_max_bucket_size) { |
| if (ssl_rsb) { |
| SSL_INCREMENT_DYN_STAT(ssl_session_cache_eviction); |
| } |
| removeOldestSession(w_lock); |
| } |
| |
| /* do the actual insert */ |
| auto node = ssl_session.release(); |
| bucket_que.enqueue(node); |
| bucket_map[id] = node; |
| |
| PRINT_BUCKET("insertSession after") |
| } |
| |
| int |
| SSLSessionBucket::getSessionBuffer(const SSLSessionID &id, char *buffer, int &len) |
| { |
| int true_len = 0; |
| std::shared_lock lock(mutex, std::try_to_lock); |
| if (!lock.owns_lock()) { |
| if (ssl_rsb) { |
| SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention); |
| } |
| if (SSLConfigParams::session_cache_skip_on_lock_contention) { |
| return true_len; |
| } |
| lock.lock(); |
| } |
| |
| auto entry = bucket_map.find(id); |
| if (buffer && entry != bucket_map.end()) { |
| true_len = entry->second->len_asn1_data; |
| const unsigned char *loc = reinterpret_cast<const unsigned char *>(entry->second->asn1_data->data()); |
| if (true_len < len) { |
| len = true_len; |
| } |
| memcpy(buffer, loc, len); |
| return true_len; |
| } |
| return 0; |
| } |
| |
| bool |
| SSLSessionBucket::getSession(const SSLSessionID &id, SSL_SESSION **sess, ssl_session_cache_exdata **data) |
| { |
| char buf[id.len * 2 + 1]; |
| buf[0] = '\0'; // just to be safe. |
| if (is_debug_tag_set("ssl.session_cache")) { |
| id.toString(buf, sizeof(buf)); |
| } |
| |
| Debug("ssl.session_cache", "Looking for session with id '%s' in bucket %p", buf, this); |
| |
| std::shared_lock lock(mutex, std::try_to_lock); |
| if (!lock.owns_lock()) { |
| if (ssl_rsb) { |
| SSL_INCREMENT_DYN_STAT(ssl_session_cache_lock_contention); |
| } |
| if (SSLConfigParams::session_cache_skip_on_lock_contention) { |
| return false; |
| } |
| lock.lock(); |
| } |
| |
| PRINT_BUCKET("getSession") |
| |
| auto entry = bucket_map.find(id); |
| if (entry == bucket_map.end()) { |
| Debug("ssl.session_cache", "Session with id '%s' not found in bucket %p.", buf, this); |
| return false; |
| } |
| const unsigned char *loc = reinterpret_cast<const unsigned char *>(entry->second->asn1_data->data()); |
| *sess = d2i_SSL_SESSION(nullptr, &loc, entry->second->len_asn1_data); |
| if (data != nullptr) { |
| ssl_session_cache_exdata *exdata = reinterpret_cast<ssl_session_cache_exdata *>(entry->second->extra_data->data()); |
| *data = exdata; |
| } |
| return true; |
| } |
| |
| void inline SSLSessionBucket::print(const char *ref_str) const |
| { |
| /* NOTE: This method assumes you're already holding the bucket lock */ |
| if (!is_debug_tag_set("ssl.session_cache.bucket")) { |
| return; |
| } |
| |
| fprintf(stderr, "-------------- BUCKET %p (%s) ----------------\n", this, ref_str); |
| fprintf(stderr, "Current Size: %ld, Max Size: %zd\n", bucket_map.size(), SSLConfigParams::session_cache_max_bucket_size); |
| fprintf(stderr, "Bucket: \n"); |
| |
| for (auto &x : bucket_map) { |
| char s_buf[2 * x.second->session_id.len + 1]; |
| x.second->session_id.toString(s_buf, sizeof(s_buf)); |
| fprintf(stderr, " %s\n", s_buf); |
| } |
| } |
| |
| void inline SSLSessionBucket::removeOldestSession(const std::unique_lock<std::shared_mutex> &lock) |
| { |
| // Caller must hold the bucket shared_mutex with unique_lock. |
| ink_assert(lock.owns_lock()); |
| |
| PRINT_BUCKET("removeOldestSession before") |
| |
| while (bucket_que.head && bucket_que.size >= static_cast<int>(SSLConfigParams::session_cache_max_bucket_size)) { |
| auto node = bucket_que.pop(); |
| bucket_map.erase(node->session_id); |
| delete node; |
| } |
| |
| PRINT_BUCKET("removeOldestSession after") |
| } |
| |
| void |
| SSLSessionBucket::removeSession(const SSLSessionID &id) |
| { |
| // We can't bail on contention here because this session MUST be removed. |
| std::unique_lock lock(mutex); |
| |
| PRINT_BUCKET("removeSession before") |
| |
| auto entry = bucket_map.find(id); |
| if (entry != bucket_map.end()) { |
| auto node = entry->second; |
| bucket_que.remove(node); |
| bucket_map.erase(entry); |
| delete node; |
| } |
| |
| PRINT_BUCKET("removeSession after") |
| |
| return; |
| } |
| |
| /* Session Bucket */ |
| SSLSessionBucket::SSLSessionBucket() {} |
| |
| SSLSessionBucket::~SSLSessionBucket() {} |
| |
| SSLOriginSessionCache::SSLOriginSessionCache() {} |
| |
| SSLOriginSessionCache::~SSLOriginSessionCache() {} |
| |
| void |
| SSLOriginSessionCache::insert_session(const std::string &lookup_key, SSL_SESSION *sess, SSL *ssl) |
| { |
| if (is_debug_tag_set("ssl.origin_session_cache")) { |
| Debug("ssl.origin_session_cache", "insert session: %s = %p", lookup_key.c_str(), sess); |
| } |
| |
| ssl_curve_id curve = (ssl == nullptr) ? 0 : SSLGetCurveNID(ssl); |
| ats_scoped_obj<SSLOriginSession> ssl_orig_session(new SSLOriginSession(lookup_key, sess, curve)); |
| auto new_node = ssl_orig_session.release(); |
| |
| std::unique_lock lock(mutex); |
| auto entry = orig_sess_map.find(lookup_key); |
| if (entry != orig_sess_map.end()) { |
| auto node = entry->second; |
| orig_sess_que.remove(node); |
| orig_sess_map.erase(entry); |
| delete node; |
| } else if (orig_sess_map.size() >= SSLConfigParams::origin_session_cache_size) { |
| remove_oldest_session(lock); |
| } |
| |
| orig_sess_que.enqueue(new_node); |
| orig_sess_map[lookup_key] = new_node; |
| } |
| |
| bool |
| SSLOriginSessionCache::get_session(const std::string &lookup_key, SSL_SESSION **sess, ssl_curve_id *curve) |
| { |
| if (is_debug_tag_set("ssl.origin_session_cache")) { |
| Debug("ssl.origin_session_cache", "get session: %s", lookup_key.c_str()); |
| } |
| |
| std::shared_lock lock(mutex); |
| auto entry = orig_sess_map.find(lookup_key); |
| if (entry == orig_sess_map.end()) { |
| return false; |
| } |
| |
| *sess = entry->second->session; |
| if (curve != nullptr) { |
| *curve = entry->second->curve_id; |
| } |
| return true; |
| } |
| |
| void |
| SSLOriginSessionCache::remove_oldest_session(const std::unique_lock<std::shared_mutex> &lock) |
| { |
| // Caller must hold the bucket shared_mutex with unique_lock. |
| ink_assert(lock.owns_lock()); |
| |
| while (orig_sess_que.head && orig_sess_que.size >= static_cast<int>(SSLConfigParams::origin_session_cache_size)) { |
| auto node = orig_sess_que.pop(); |
| if (is_debug_tag_set("ssl.origin_session_cache")) { |
| Debug("ssl.origin_session_cache", "remove oldest session: %s", node->key.c_str()); |
| } |
| orig_sess_map.erase(node->key); |
| delete node; |
| } |
| } |