*\li 'pstore' is a valid pointer to a pointer containing a non-'NULL' value.
*/
+typedef struct isc_tlsctx_client_session_cache isc_tlsctx_client_session_cache_t;
+/*%<
+ * TLS client session cache is an object which allows efficient
+ * storing and retrieval of previously saved TLS sessions so that they
+ * can be resumed. This object is supposed to be a foundation for
+ * implementing TLS session resumption - a standard technique to
+ * reduce the cost of re-establishing a connection to the remote
+ * server endpoint.
+ *
+ * OpenSSL does server-side TLS session caching transparently by
+ * default. However, on the client-side, a TLS session to resume must
+ * be manually specified when establishing the TLS connection. The TLS
+ * client session cache is precisely the foundation for that.
+ *
+ * The cache has been designed to have the following characteristics:
+ *
+ * - Fixed maximal number of entries to not keep too many obsolete
+ * sessions within the cache;
+ *
+ * - The cache is indexed by character string keys. Each string is a
+ * key representing a remote endpoint (unique remote endpoint name,
+ * e.g. combination of the remote IP address and port);
+ *
+ * - Least Recently Used (LRU) cache replacement policy while allowing
+ * easy removal of obsolete entries;
+ *
+ * - Ability to store multiple TLS sessions associated with the given
+ * key (remote endpoint name). This characteristic is important if
+ * multiple connections to the same remote server can be established;
+ *
+ * - Ability to efficiently retrieve the most recent TLS sessions
+ * associated with the key (remote endpoint name).
+ *
+ * Because of these characteristics, the cache will end up having the
+ * necessary amount of resumable TLS session parameters to the most
+ * used remote endpoints ("hot entries") after a short period of
+ * initial use ("warmup").
+ *
+ * Attempting to resume TLS sessions is an optimisation, which is not
+ * guaranteed to succeed because it requires the same session to be
+ * present in the server session caches. If it is not the case, the
+ * usual handshake procedure takes place. However, when session
+ * resumption is successful, it reduces the amount of the
+ * computational resources required as well as the amount of data to
+ * be transmitted when (re)establishing the connection. Also, it
+ * reduces round trip time (by reducing the number of packets to
+ * transmit).
+ *
+ * This optimisation is important in the context of DNS because the
+ * amount of data within the full handshake messages might be
+ * comparable to or surpass the size of a typical DNS message.
+ */
+
+isc_tlsctx_client_session_cache_t *
+isc_tlsctx_client_session_cache_new(isc_mem_t *mctx, isc_tlsctx_t *ctx,
+ const size_t max_entries);
+/*%<
+ * Create a new TLS client session cache object.
+ *
+ * Requires:
+ *\li 'mctx' is a valid memory context object;
+ *\li 'ctx' is a valid TLS context object;
+ *\li 'max_entries' is a positive number;
+ */
+
+void
+isc_tlsctx_client_session_cache_attach(
+ isc_tlsctx_client_session_cache_t *source,
+ isc_tlsctx_client_session_cache_t **targetp);
+/*%<
+ * Create a reference to the TLS client session cache object.
+ *
+ * Requires:
+ *\li 'source' is a valid TLS client session cache object;
+ *\li 'targetp' is a valid pointer to a pointer which must equal NULL.
+ */
+
+void
+isc_tlsctx_client_session_cache_detach(
+ isc_tlsctx_client_session_cache_t **cachep);
+/*%<
+ * Remove a reference to the TLS client session cache object.
+ *
+ * Requires:
+ *\li 'cachep' is a pointer to a pointer to a valid TLS client session cache
+ *object.
+ */
+
+void
+isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_peer_name, isc_tls_t *tls);
+/*%<
+ * Add a resumable TLS client session within 'tls' to the TLS client
+ * session cache object 'cache' and associate it with
+ * 'remote_server_name' string. Also, the oldest entry from the cache
+ * might get removed if the cache is full.
+ *
+ * Requires:
+ *\li 'cache' is a pointer to a valid TLS client session cache object;
+ *\li 'remote_peer_name' is a pointer to a non empty character string.
+ *\li 'tls' is a valid, non-'NULL' pointer to a TLS connection state object.
+ */
+
+void
+isc_tlsctx_client_session_cache_keep_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls);
+/*%<
+ * The same as 'isc_tlsctx_client_session_cache_keep()', but using a
+ * 'isc_sockaddr_t' as a key, instead of a character string.
+ *
+ * Requires:
+ *\li 'remote_peer' is a valid, non-'NULL' pointer to an 'isc_sockaddr_t'
+ *object.
+ */
+
+void
+isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_server_name, isc_tls_t *tls);
+/*%
+ * Try to restore a previously stored TLS session denoted by a remote
+ * server name as a key ('remote_server_name') into the given TLS
+ * connection state object ('tls'). The successfully restored session
+ * gets removed from the cache.
+ *
+ * Requires:
+ *\li 'cache' is a pointer to a valid TLS client session cache object;
+ *\li 'remote_peer_name' is a pointer to a non empty character string;
+ *\li 'tls' is a valid, non-'NULL', pointer to a TLS connection state object.
+ */
+
+void
+isc_tlsctx_client_session_cache_reuse_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls);
+/*%<
+ * The same as 'isc_tlsctx_client_session_cache_reuse()', but uses a
+ * 'isc_sockaddr_t' as a key, instead of a character string.
+ *
+ * Requires:
+ *\li 'remote_peer' is a valid, non-'NULL' pointer to an 'isc_sockaddr_t'
+ *object.
+ */
+
+const isc_tlsctx_t *
+isc_tlsctx_client_session_cache_getctx(isc_tlsctx_client_session_cache_t *cache);
+
typedef struct isc_tlsctx_cache isc_tlsctx_cache_t;
/*%<
* The TLS context cache is an object which allows retrieving a
#include <isc/random.h>
#include <isc/refcount.h>
#include <isc/rwlock.h>
+#include <isc/sockaddr.h>
#include <isc/thread.h>
#include <isc/tls.h>
#include <isc/util.h>
#define TLSCTX_CACHE_MAGIC ISC_MAGIC('T', 'l', 'S', 'c')
#define VALID_TLSCTX_CACHE(t) ISC_MAGIC_VALID(t, TLSCTX_CACHE_MAGIC)
+#define TLSCTX_CLIENT_SESSION_CACHE_MAGIC ISC_MAGIC('T', 'l', 'C', 'c')
+#define VALID_TLSCTX_CLIENT_SESSION_CACHE(t) \
+ ISC_MAGIC_VALID(t, TLSCTX_CLIENT_SESSION_CACHE_MAGIC)
+
typedef struct isc_tlsctx_cache_entry {
/*
* We need a TLS context entry for each transport on both IPv4 and
return (result);
}
+
+typedef struct client_session_cache_entry client_session_cache_entry_t;
+
+typedef struct client_session_cache_bucket {
+ char *bucket_key;
+ size_t bucket_key_len;
+ /* Cache entries within the bucket (from the oldest to the newest). */
+ ISC_LIST(client_session_cache_entry_t) entries;
+} client_session_cache_bucket_t;
+
+struct client_session_cache_entry {
+ SSL_SESSION *session;
+ client_session_cache_bucket_t *bucket; /* "Parent" bucket pointer. */
+ ISC_LINK(client_session_cache_entry_t) bucket_link;
+ ISC_LINK(client_session_cache_entry_t) cache_link;
+};
+
+struct isc_tlsctx_client_session_cache {
+ uint32_t magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+
+ /*
+ * We need to keep a reference to the related TLS context in order
+ * to ensure that it remains valid while the TLS client sessions
+ * cache object is valid, as every TLS session object
+ * (SSL_SESSION) is "tied" to a particular context.
+ */
+ isc_tlsctx_t *ctx;
+
+ /*
+ * The idea is to have one bucket per remote server. Each bucket,
+ * can maintain multiple TLS sessions to that server, as BIND
+ * might want to establish multiple TLS connections to the remote
+ * server at once.
+ */
+ isc_ht_t *buckets;
+
+ /*
+ * The list of all current entries within the cache maintained in
+ * LRU-manner, so that the oldest entry might be efficiently
+ * removed.
+ */
+ ISC_LIST(client_session_cache_entry_t) lru_entries;
+ /* Number of the entries within the cache. */
+ size_t nentries;
+ /* Maximum number of the entries within the cache. */
+ size_t max_entries;
+
+ isc_mutex_t lock;
+};
+
+isc_tlsctx_client_session_cache_t *
+isc_tlsctx_client_session_cache_new(isc_mem_t *mctx, isc_tlsctx_t *ctx,
+ const size_t max_entries) {
+ isc_tlsctx_client_session_cache_t *nc;
+
+ REQUIRE(ctx != NULL);
+ REQUIRE(max_entries > 0);
+
+ nc = isc_mem_get(mctx, sizeof(*nc));
+
+ *nc = (isc_tlsctx_client_session_cache_t){ .max_entries = max_entries };
+ isc_refcount_init(&nc->references, 1);
+ isc_mem_attach(mctx, &nc->mctx);
+ isc_tlsctx_attach(ctx, &nc->ctx);
+
+ isc_ht_init(&nc->buckets, mctx, 5, ISC_HT_CASE_SENSITIVE);
+ ISC_LIST_INIT(nc->lru_entries);
+ isc_mutex_init(&nc->lock);
+
+ nc->magic = TLSCTX_CLIENT_SESSION_CACHE_MAGIC;
+
+ return (nc);
+}
+
+void
+isc_tlsctx_client_session_cache_attach(
+ isc_tlsctx_client_session_cache_t *source,
+ isc_tlsctx_client_session_cache_t **targetp) {
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+client_cache_entry_delete(isc_tlsctx_client_session_cache_t *restrict cache,
+ client_session_cache_entry_t *restrict entry) {
+ client_session_cache_bucket_t *restrict bucket = entry->bucket;
+
+ /* Unlink and free the cache entry */
+ ISC_LIST_UNLINK(bucket->entries, entry, bucket_link);
+ ISC_LIST_UNLINK(cache->lru_entries, entry, cache_link);
+ cache->nentries--;
+ (void)SSL_SESSION_free(entry->session);
+ isc_mem_put(cache->mctx, entry, sizeof(*entry));
+
+ /* The bucket is empty - let's remove it */
+ if (ISC_LIST_EMPTY(bucket->entries)) {
+ RUNTIME_CHECK(isc_ht_delete(cache->buckets,
+ (const uint8_t *)bucket->bucket_key,
+ bucket->bucket_key_len) ==
+ ISC_R_SUCCESS);
+
+ isc_mem_free(cache->mctx, bucket->bucket_key);
+ isc_mem_put(cache->mctx, bucket, sizeof(*bucket));
+ }
+}
+
+void
+isc_tlsctx_client_session_cache_detach(
+ isc_tlsctx_client_session_cache_t **cachep) {
+ isc_tlsctx_client_session_cache_t *cache = NULL;
+ client_session_cache_entry_t *entry = NULL, *next = NULL;
+
+ REQUIRE(cachep != NULL);
+
+ cache = *cachep;
+ *cachep = NULL;
+
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+
+ if (isc_refcount_decrement(&cache->references) != 1) {
+ return;
+ }
+
+ cache->magic = 0;
+
+ isc_refcount_destroy(&cache->references);
+
+ entry = ISC_LIST_HEAD(cache->lru_entries);
+ while (entry != NULL) {
+ next = ISC_LIST_NEXT(entry, cache_link);
+ client_cache_entry_delete(cache, entry);
+ entry = next;
+ }
+
+ RUNTIME_CHECK(isc_ht_count(cache->buckets) == 0);
+ isc_ht_destroy(&cache->buckets);
+
+ isc_mutex_destroy(&cache->lock);
+ isc_tlsctx_free(&cache->ctx);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+}
+
+void
+isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_peer_name, isc_tls_t *tls) {
+ size_t name_len;
+ isc_result_t result;
+ SSL_SESSION *sess;
+ client_session_cache_bucket_t *restrict bucket = NULL;
+ client_session_cache_entry_t *restrict entry = NULL;
+
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+ REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0');
+ REQUIRE(tls != NULL);
+
+ sess = SSL_get1_session(tls);
+ if (sess == NULL) {
+ return;
+ } else if (SSL_SESSION_is_resumable(sess) == 0) {
+ SSL_SESSION_free(sess);
+ return;
+ }
+
+ isc_mutex_lock(&cache->lock);
+
+ name_len = strlen(remote_peer_name);
+ result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name,
+ name_len, (void **)&bucket);
+
+ if (result != ISC_R_SUCCESS) {
+ /* Let's create a new bucket */
+ INSIST(bucket == NULL);
+ bucket = isc_mem_get(cache->mctx, sizeof(*bucket));
+ *bucket = (client_session_cache_bucket_t){
+ .bucket_key = isc_mem_strdup(cache->mctx,
+ remote_peer_name),
+ .bucket_key_len = name_len
+ };
+ ISC_LIST_INIT(bucket->entries);
+ RUNTIME_CHECK(isc_ht_add(cache->buckets,
+ (const uint8_t *)remote_peer_name,
+ name_len,
+ (void *)bucket) == ISC_R_SUCCESS);
+ }
+
+ /* Let's add a new cache entry to the new/found bucket */
+ entry = isc_mem_get(cache->mctx, sizeof(*entry));
+ *entry = (client_session_cache_entry_t){ .session = sess,
+ .bucket = bucket };
+ ISC_LINK_INIT(entry, bucket_link);
+ ISC_LINK_INIT(entry, cache_link);
+
+ ISC_LIST_APPEND(bucket->entries, entry, bucket_link);
+
+ ISC_LIST_APPEND(cache->lru_entries, entry, cache_link);
+ cache->nentries++;
+
+ if (cache->nentries > cache->max_entries) {
+ /*
+ * Cache overrun. We need to remove the oldest entry from the
+ * cache
+ */
+ client_session_cache_entry_t *restrict oldest;
+ INSIST((cache->nentries - 1) == cache->max_entries);
+
+ oldest = ISC_LIST_HEAD(cache->lru_entries);
+ client_cache_entry_delete(cache, oldest);
+ }
+
+ isc_mutex_unlock(&cache->lock);
+}
+
+void
+isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_peer_name, isc_tls_t *tls) {
+ client_session_cache_bucket_t *restrict bucket = NULL;
+ client_session_cache_entry_t *restrict entry;
+ size_t name_len;
+ isc_result_t result;
+
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+ REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0');
+ REQUIRE(tls != NULL);
+
+ isc_mutex_lock(&cache->lock);
+
+ /* Let's find the bucket */
+ name_len = strlen(remote_peer_name);
+ result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name,
+ name_len, (void **)&bucket);
+
+ if (result != ISC_R_SUCCESS) {
+ goto exit;
+ }
+
+ INSIST(bucket != NULL);
+
+ /*
+ * If the bucket has been found, let's use the newest session from
+ * the bucket, as it has the highest chance to be successfully
+ * resumed.
+ */
+ INSIST(!ISC_LIST_EMPTY(bucket->entries));
+ entry = ISC_LIST_TAIL(bucket->entries);
+ RUNTIME_CHECK(SSL_set_session(tls, entry->session) == 1);
+ client_cache_entry_delete(cache, entry);
+
+exit:
+ isc_mutex_unlock(&cache->lock);
+}
+
+void
+isc_tlsctx_client_session_cache_keep_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls) {
+ char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+
+ REQUIRE(remote_peer != NULL);
+
+ isc_sockaddr_format(remote_peer, peername, sizeof(peername));
+
+ isc_tlsctx_client_session_cache_keep(cache, peername, tls);
+}
+
+void
+isc_tlsctx_client_session_cache_reuse_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls) {
+ char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+
+ REQUIRE(remote_peer != NULL);
+
+ isc_sockaddr_format(remote_peer, peername, sizeof(peername));
+
+ isc_tlsctx_client_session_cache_reuse(cache, peername, tls);
+}
+
+const isc_tlsctx_t *
+isc_tlsctx_client_session_cache_getctx(
+ isc_tlsctx_client_session_cache_t *cache) {
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+ return (cache->ctx);
+}