From eb37d967c2f15870f0c2b081c20891ccd21a6392 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Wed, 22 Dec 2021 17:11:11 +0200 Subject: [PATCH 1/4] Add TLS context cache This commit adds a TLS context object cache implementation. The intention of having this object is manyfold: - In the case of client-side contexts: allow reusing the previously created contexts to employ the context-specific TLS session resumption cache. That will enable XoT connection to be reestablished faster and with fewer resources by not going through the full TLS handshake procedure. - In the case of server-side contexts: reduce the number of contexts created on startup. That could reduce startup time in a case when there are many "listen-on" statements referring to a smaller amount of `tls` statements, especially when "ephemeral" certificates are involved. - The long-term goal is to provide in-memory storage for additional data associated with the certificates, like runtime representation (X509_STORE) of intermediate CA-certificates bundle for Strict TLS/Mutual TLS ("ca-file"). --- lib/isc/include/isc/tls.h | 112 ++++++++++++++++++++- lib/isc/tls.c | 202 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+), 2 deletions(-) diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h index fcc484df47..ec7382901d 100644 --- a/lib/isc/include/isc/tls.h +++ b/lib/isc/include/isc/tls.h @@ -158,7 +158,6 @@ isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx); void isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *ctx); /*%< - * * Enable HTTP/2 Application Layer Protocol Negotation for 'ctx'. * * Requires: @@ -178,9 +177,118 @@ isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx); void isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *ctx); /*%< - * * Enable DoT Application Layer Protocol Negotation for 'ctx'. * * Requires: *\li 'ctx' is not NULL. */ + +typedef struct isc_tlsctx_cache isc_tlsctx_cache_t; +/*%< + * The TLS context cache is an object which allows retrieving a + * previously created TLS context based on the following tuple: + * + * 1. The name of a TLS entry, as defined in the configuration file; + * 2. A transport type. Currently, only TLS (DoT) and HTTPS (DoH) are + * supported; + * 3. An IP address family (AF_INET or AF_INET6). + * + * There are multiple uses for this object: + * + * First, it allows reuse of client-side contexts during zone transfers. + * That, in turn, allows use of session caches associated with these + * contexts, which enables TLS session resumption, making establishment + * of XoT connections faster and computationally cheaper. + * + * Second, it can be extended to be used as storage for TLS context related + * data, as defined in 'tls' statements in the configuration file (for + * example, CA-bundle intermediate certificate storage, client-side contexts + * with pre-loaded certificates in a case of Mutual TLS, etc). This will + * be used to implement Strict/Mutual TLS. + * + * Third, it avoids creating an excessive number of server-side TLS + * contexts, which might help to reduce the number of contexts + * created during server initialisation and reconfiguration. + */ + +typedef enum { + isc_tlsctx_cache_none = 0, + isc_tlsctx_cache_tls, + isc_tlsctx_cache_https, + isc_tlsctx_cache_count +} isc_tlsctx_cache_transport_t; +/*%< TLS context cache transport type values. */ + +isc_tlsctx_cache_t * +isc_tlsctx_cache_new(isc_mem_t *mctx); +/*%< + * Create a new TLS context cache object. + * + * Requires: + *\li 'mctx' is a valid memory context. + */ + +void +isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source, + isc_tlsctx_cache_t **targetp); +/*%< + * Create a reference to the TLS context cache object. + * + * Requires: + *\li 'source' is a valid TLS context cache object; + *\li 'targetp' is a valid pointer to a pointer which must equal NULL. + */ + +void +isc_tlsctx_cache_detach(isc_tlsctx_cache_t **pcache); +/*%< + * Remove a reference to the TLS context cache object. + * + * Requires: + *\li 'pcache' is a valid pointer to a pointer which must point to a + * valid TLS context cache object. + */ + +isc_result_t +isc_tlsctx_cache_add(isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, + const uint16_t family, isc_tlsctx_t *ctx, + isc_tlsctx_t **pfound); +/*%< + * + * Add a new TLS context to the TLS context cache. 'pfound' is an + * optional pointer, which can be used to retrieve an already + * existing TLS context object in a case it exists. + * + * Requires: + *\li 'cache' is a valid pointer to a TLS context cache object; + *\li 'name' is a valid pointer to a non-empty string; + *\li 'transport' is a valid transport identifier (currently only + * TLS/DoT and HTTPS/DoH are supported); + *\li 'family' - either 'AF_INET' or 'AF_INET6'; + *\li 'ctx' - a valid pointer to a valid TLS context object. + * + * Returns: + *\li #ISC_R_EXISTS - node of the same key already exists; + *\li #ISC_R_SUCCESS - the new entry has been added successfully. + */ + +isc_result_t +isc_tlsctx_cache_find(isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, + const uint16_t family, isc_tlsctx_t **pctx); +/*%< + * Look up a TLS context in the TLS context cache. + * + * Requires: + *\li 'cache' is a valid pointer to a TLS context cache object; + *\li 'name' is a valid pointer to a non empty string; + *\li 'transport' - a valid transport identifier (currently only + * TLS/DoT and HTTPS/DoH are supported; + *\li 'family' - either 'AF_INET' or 'AF_INET6'; + *\li 'pctx' - a valid pointer to a non-NULL pointer. + * + * Returns: + *\li #ISC_R_SUCCESS - the context has been found; + *\li #ISC_R_NOTFOUND - the context has not been found. + */ diff --git a/lib/isc/tls.c b/lib/isc/tls.c index 7515d833cf..cf781a2302 100644 --- a/lib/isc/tls.c +++ b/lib/isc/tls.c @@ -12,6 +12,7 @@ #include #include #include +#include #if HAVE_LIBNGHTTP2 #include #endif /* HAVE_LIBNGHTTP2 */ @@ -27,10 +28,14 @@ #include #include +#include #include +#include #include #include #include +#include +#include #include #include #include @@ -885,3 +890,200 @@ isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) { SSL_CTX_set_alpn_select_cb(tls, dot_alpn_select_proto_cb, NULL); #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L } + +#define TLSCTX_CACHE_MAGIC ISC_MAGIC('T', 'l', 'S', 'c') +#define VALID_TLSCTX_CACHE(t) ISC_MAGIC_VALID(t, TLSCTX_CACHE_MAGIC) + +typedef struct isc_tlsctx_cache_entry { + /* + * We need a TLS context entry for each transport on both IPv4 and + * IPv6 in order to avoid cluttering a context-specific + * session-resumption cache. + */ + isc_tlsctx_t *ctx[isc_tlsctx_cache_count - 1][2]; + /* + * TODO: add a certificate store for an intermediate certificates + * from a CA-bundle file. One is enough for all the contexts defined + * above. We will need that for validation. + * + * X509_STORE *ca_bundle_store; // TODO: define the utilities to + * operate on these ones + */ +} isc_tlsctx_cache_entry_t; + +struct isc_tlsctx_cache { + uint32_t magic; + isc_refcount_t references; + isc_mem_t *mctx; + + isc_rwlock_t rwlock; + isc_ht_t *data; +}; + +isc_tlsctx_cache_t * +isc_tlsctx_cache_new(isc_mem_t *mctx) { + isc_tlsctx_cache_t *nc; + + nc = isc_mem_get(mctx, sizeof(*nc)); + + *nc = (isc_tlsctx_cache_t){ .magic = TLSCTX_CACHE_MAGIC }; + isc_refcount_init(&nc->references, 1); + isc_mem_attach(mctx, &nc->mctx); + + RUNTIME_CHECK(isc_ht_init(&nc->data, mctx, 5) == ISC_R_SUCCESS); + isc_rwlock_init(&nc->rwlock, 0, 0); + + return (nc); +} + +void +isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source, + isc_tlsctx_cache_t **targetp) { + REQUIRE(VALID_TLSCTX_CACHE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +tlsctx_cache_entry_destroy(isc_mem_t *mctx, isc_tlsctx_cache_entry_t *entry) { + size_t i, k; + + for (i = 0; i < (isc_tlsctx_cache_count - 1); i++) { + for (k = 0; k < 2; k++) { + if (entry->ctx[i][k] != NULL) { + isc_tlsctx_free(&entry->ctx[i][k]); + } + } + } + isc_mem_put(mctx, entry, sizeof(*entry)); +} + +void +isc_tlsctx_cache_detach(isc_tlsctx_cache_t **pcache) { + isc_tlsctx_cache_t *cache; + isc_ht_iter_t *it = NULL; + isc_result_t result; + REQUIRE(pcache != NULL); + cache = *pcache; + REQUIRE(VALID_TLSCTX_CACHE(cache)); + + if (isc_refcount_decrement(&cache->references) > 1) { + return; + } + + RUNTIME_CHECK(isc_ht_iter_create(cache->data, &it) == ISC_R_SUCCESS); + for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(it)) + { + isc_tlsctx_cache_entry_t *entry = NULL; + isc_ht_iter_current(it, (void **)&entry); + tlsctx_cache_entry_destroy(cache->mctx, entry); + } + isc_ht_iter_destroy(&it); + isc_ht_destroy(&cache->data); + + isc_rwlock_destroy(&cache->rwlock); + cache->magic = 0; + isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache)); + *pcache = NULL; +} + +isc_result_t +isc_tlsctx_cache_add(isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, + const uint16_t family, isc_tlsctx_t *ctx, + isc_tlsctx_t **pfound) { + isc_result_t result = ISC_R_FAILURE; + size_t name_len, tr_offset; + isc_tlsctx_cache_entry_t *entry = NULL; + bool ipv6; + + REQUIRE(VALID_TLSCTX_CACHE(cache)); + REQUIRE(name != NULL && *name != '\0'); + REQUIRE(transport > isc_tlsctx_cache_none && + transport < isc_tlsctx_cache_count); + REQUIRE(family == AF_INET || family == AF_INET6); + REQUIRE(ctx != NULL); + + tr_offset = (transport - 1); + ipv6 = (family == AF_INET6); + + RWLOCK(&cache->rwlock, isc_rwlocktype_write); + + name_len = strlen(name); + result = isc_ht_find(cache->data, (const uint8_t *)name, name_len, + (void **)&entry); + if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) { + /* The entry exists. */ + if (pfound != NULL) { + INSIST(*pfound == NULL); + *pfound = entry->ctx[tr_offset][ipv6]; + } + result = ISC_R_EXISTS; + } else if (result == ISC_R_SUCCESS && + entry->ctx[tr_offset][ipv6] == NULL) { + /* + * The hast table entry exists, but is not filled for this + * particular transport/IP type combination. + */ + entry->ctx[tr_offset][ipv6] = ctx; + result = ISC_R_SUCCESS; + } else { + /* + * The hash table entry does not exist, let's create one. + */ + INSIST(result != ISC_R_SUCCESS); + entry = isc_mem_get(cache->mctx, sizeof(*entry)); + /* Oracle/Red Hat Linux, GCC bug #53119 */ + memset(entry, 0, sizeof(*entry)); + entry->ctx[tr_offset][ipv6] = ctx; + RUNTIME_CHECK(isc_ht_add(cache->data, (const uint8_t *)name, + name_len, + (void *)entry) == ISC_R_SUCCESS); + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&cache->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +isc_tlsctx_cache_find(isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, + const uint16_t family, isc_tlsctx_t **pctx) { + isc_result_t result = ISC_R_FAILURE; + size_t tr_offset; + isc_tlsctx_cache_entry_t *entry = NULL; + bool ipv6; + + REQUIRE(VALID_TLSCTX_CACHE(cache)); + REQUIRE(name != NULL && *name != '\0'); + REQUIRE(transport > isc_tlsctx_cache_none && + transport < isc_tlsctx_cache_count); + REQUIRE(family == AF_INET || family == AF_INET6); + REQUIRE(pctx != NULL && *pctx == NULL); + + tr_offset = (transport - 1); + ipv6 = (family == AF_INET6); + + RWLOCK(&cache->rwlock, isc_rwlocktype_read); + + result = isc_ht_find(cache->data, (const uint8_t *)name, strlen(name), + (void **)&entry); + if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) { + *pctx = entry->ctx[tr_offset][ipv6]; + } else if (result == ISC_R_SUCCESS && + entry->ctx[tr_offset][ipv6] == NULL) { + result = ISC_R_NOTFOUND; + } else { + INSIST(result != ISC_R_SUCCESS); + } + + RWUNLOCK(&cache->rwlock, isc_rwlocktype_read); + + return (result); +} From 5b7d4341fe19be0f1dce3e575383860ab64bde3b Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Thu, 23 Dec 2021 12:01:34 +0200 Subject: [PATCH 2/4] Use the TLS context cache for server-side contexts Using the TLS context cache for server-side contexts could reduce the number of contexts to initialise in the configurations when e.g. the same 'tls' entry is used in multiple 'listen-on' statements for the same DNS transport, binding to multiple IP addresses. In such a case, only one TLS context will be created, instead of a context per IP address, which could reduce the initialisation time, as initialising even a non-ephemeral TLS context introduces some delay, which can be *visually* noticeable by log activity. Also, this change lays down a foundation for Mutual TLS (when the server validates a client certificate, additionally to a client validating the server), as the TLS context cache can be extended to store additional data required for validation (like intermediates CA chain). Additionally to the above, the change ensures that the contexts are not being changed after initialisation, as such a practice is frowned upon. Previously we would set the supported ALPN tags within isc_nm_listenhttp() and isc_nm_listentlsdns(). We do not do that for client-side contexts, so that appears to be an overlook. Now we set the supported ALPN tags right after server-side contexts creation, similarly how we do for client-side ones. --- bin/named/include/named/server.h | 3 + bin/named/server.c | 62 ++++++++++---- lib/isc/netmgr/http.c | 1 - lib/isc/netmgr/tlsdns.c | 2 - lib/isc/tests/doh_test.c | 1 + lib/ns/include/ns/listenlist.h | 34 ++++---- lib/ns/listenlist.c | 142 ++++++++++++++++++++++--------- lib/ns/tests/listenlist_test.c | 4 +- lib/ns/tests/nstest.c | 5 +- 9 files changed, 173 insertions(+), 81 deletions(-) diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 089145c29c..dd0d670118 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -109,6 +110,8 @@ struct named_server { dns_dtenv_t *dtenv; /*%< Dnstap environment */ char *lockfile; + + isc_tlsctx_cache_t *tlsctx_server_cache; }; #define NAMED_SERVER_MAGIC ISC_MAGIC('S', 'V', 'E', 'R') diff --git a/bin/named/server.c b/bin/named/server.c index d4641516fc..0ac53c9a76 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -403,19 +403,21 @@ named_server_reload(isc_task_t *task, isc_event_t *event); #ifdef HAVE_LIBNGHTTP2 static isc_result_t -listenelt_http(const cfg_obj_t *http, bool tls, - const ns_listen_tls_params_t *tls_params, in_port_t port, +listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, + const ns_listen_tls_params_t *tls_params, + isc_tlsctx_cache_t *tlsctx_cache, in_port_t port, isc_mem_t *mctx, ns_listenelt_t **target); #endif static isc_result_t listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, - ns_listenelt_t **target); + isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target); static isc_result_t listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, ns_listenlist_t **target); static isc_result_t @@ -8422,6 +8424,13 @@ load_configuration(const char *filename, named_server_t *server, */ CHECK(bind9_check_namedconf(config, false, named_g_lctx, named_g_mctx)); + /* Let's recreate the TLS context cache */ + if (server->tlsctx_server_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_server_cache); + } + + server->tlsctx_server_cache = isc_tlsctx_cache_new(named_g_mctx); + /* * Fill in the maps array, used for resolving defaults. */ @@ -8874,13 +8883,15 @@ load_configuration(const char *filename, named_server_t *server, if (clistenon != NULL) { CHECK(listenlist_fromconfig( clistenon, config, named_g_aclconfctx, - named_g_mctx, AF_INET, &listenon)); + named_g_mctx, AF_INET, + server->tlsctx_server_cache, &listenon)); } else { /* * Not specified, use default. */ CHECK(ns_listenlist_default(named_g_mctx, listen_port, - -1, true, &listenon)); + -1, true, AF_INET, + &listenon)); } if (listenon != NULL) { ns_interfacemgr_setlistenon4(server->interfacemgr, @@ -8901,13 +8912,15 @@ load_configuration(const char *filename, named_server_t *server, if (clistenon != NULL) { CHECK(listenlist_fromconfig( clistenon, config, named_g_aclconfctx, - named_g_mctx, AF_INET6, &listenon)); + named_g_mctx, AF_INET6, + server->tlsctx_server_cache, &listenon)); } else { /* * Not specified, use default. */ CHECK(ns_listenlist_default(named_g_mctx, listen_port, - -1, true, &listenon)); + -1, true, AF_INET6, + &listenon)); } if (listenon != NULL) { ns_interfacemgr_setlistenon6(server->interfacemgr, @@ -10167,6 +10180,9 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) { server->dtenv = NULL; server->magic = NAMED_SERVER_MAGIC; + + server->tlsctx_server_cache = NULL; + *serverp = server; } @@ -10221,6 +10237,10 @@ named_server_destroy(named_server_t **serverp) { INSIST(ISC_LIST_EMPTY(server->viewlist)); INSIST(ISC_LIST_EMPTY(server->cachelist)); + if (server->tlsctx_server_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_server_cache); + } + server->magic = 0; isc_mem_put(server->mctx, server, sizeof(*server)); *serverp = NULL; @@ -10860,6 +10880,7 @@ named_server_togglequerylog(named_server_t *server, isc_lex_t *lex) { static isc_result_t listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, ns_listenlist_t **target) { isc_result_t result; const cfg_listelt_t *element; @@ -10878,7 +10899,7 @@ listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, ns_listenelt_t *delt = NULL; const cfg_obj_t *listener = cfg_listelt_value(element); result = listenelt_fromconfig(listener, config, actx, mctx, - family, &delt); + family, tlsctx_cache, &delt); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -10925,6 +10946,7 @@ find_maplist(const cfg_obj_t *config, const char *listname, const char *name) { static isc_result_t listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) { isc_result_t result; const cfg_obj_t *ltup = NULL; @@ -10942,6 +10964,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, ns_listenelt_t *delt = NULL; uint32_t tls_protos = 0; ns_listen_tls_params_t tls_params = { 0 }; + const char *tlsname = NULL; REQUIRE(target != NULL && *target == NULL); @@ -10950,7 +10973,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, tlsobj = cfg_tuple_get(ltup, "tls"); if (tlsobj != NULL && cfg_obj_isstring(tlsobj)) { - const char *tlsname = cfg_obj_asstring(tlsobj); + tlsname = cfg_obj_asstring(tlsobj); if (strcasecmp(tlsname, "none") == 0) { no_tls = true; @@ -11033,6 +11056,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, } tls_params = (ns_listen_tls_params_t){ + .name = tlsname, .key = key, .cert = cert, .protocols = tls_protos, @@ -11126,14 +11150,15 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, #ifdef HAVE_LIBNGHTTP2 if (http) { - CHECK(listenelt_http(http_server, do_tls, &tls_params, port, - mctx, &delt)); + CHECK(listenelt_http(http_server, family, do_tls, &tls_params, + tlsctx_cache, port, mctx, &delt)); } #endif /* HAVE_LIBNGHTTP2 */ if (!http) { - CHECK(ns_listenelt_create(mctx, port, dscp, NULL, do_tls, - &tls_params, &delt)); + CHECK(ns_listenelt_create(mctx, port, dscp, NULL, family, + do_tls, &tls_params, tlsctx_cache, + &delt)); } result = cfg_acl_fromconfig2(cfg_tuple_get(listener, "acl"), config, @@ -11151,8 +11176,9 @@ cleanup: #ifdef HAVE_LIBNGHTTP2 static isc_result_t -listenelt_http(const cfg_obj_t *http, bool tls, - const ns_listen_tls_params_t *tls_params, in_port_t port, +listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, + const ns_listen_tls_params_t *tls_params, + isc_tlsctx_cache_t *tlsctx_cache, in_port_t port, isc_mem_t *mctx, ns_listenelt_t **target) { isc_result_t result = ISC_R_SUCCESS; ns_listenelt_t *delt = NULL; @@ -11224,9 +11250,9 @@ listenelt_http(const cfg_obj_t *http, bool tls, quota = isc_mem_get(mctx, sizeof(isc_quota_t)); isc_quota_init(quota, max_clients); } - result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, tls, - tls_params, endpoints, len, quota, - max_streams, &delt); + result = ns_listenelt_create_http( + mctx, port, named_g_dscp, NULL, family, tls, tls_params, + tlsctx_cache, endpoints, len, quota, max_streams, &delt); if (result != ISC_R_SUCCESS) { goto error; } diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index 539fef6fd3..e31180bd02 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -2454,7 +2454,6 @@ isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog, isc_nm_http_endpoints_attach(eps, &sock->h2.listener_endpoints); if (ctx != NULL) { - isc_tlsctx_enable_http2server_alpn(ctx); result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock, sizeof(isc_nm_http_session_t), backlog, quota, ctx, &sock->outer); diff --git a/lib/isc/netmgr/tlsdns.c b/lib/isc/netmgr/tlsdns.c index 0e34b95011..54848102b2 100644 --- a/lib/isc/netmgr/tlsdns.c +++ b/lib/isc/netmgr/tlsdns.c @@ -494,8 +494,6 @@ isc_nm_listentlsdns(isc_nm_t *mgr, isc_sockaddr_t *iface, sock->tid = 0; sock->fd = -1; - isc_tlsctx_enable_dot_server_alpn(sslctx); - #if !HAVE_SO_REUSEPORT_LB fd = isc__nm_tlsdns_lb_socket(iface->type.sa.sa_family); #endif diff --git a/lib/isc/tests/doh_test.c b/lib/isc/tests/doh_test.c index 7a2c06571b..a5694979f8 100644 --- a/lib/isc/tests/doh_test.c +++ b/lib/isc/tests/doh_test.c @@ -332,6 +332,7 @@ nm_setup(void **state) { server_tlsctx = NULL; isc_tlsctx_createserver(NULL, NULL, &server_tlsctx); + isc_tlsctx_enable_http2server_alpn(server_tlsctx); client_tlsctx = NULL; isc_tlsctx_createclient(&client_tlsctx); isc_tlsctx_enable_http2client_alpn(client_tlsctx); diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index 0b052b6a70..936061e457 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -40,15 +40,16 @@ typedef struct ns_listenlist ns_listenlist_t; struct ns_listenelt { isc_mem_t *mctx; - in_port_t port; - bool is_http; - isc_dscp_t dscp; /* -1 = not set, 0..63 */ + in_port_t port; + bool is_http; + isc_dscp_t dscp; /* -1 = not set, 0..63 */ dns_acl_t *acl; - isc_tlsctx_t *sslctx; - char **http_endpoints; - size_t http_endpoints_number; - isc_quota_t *http_quota; - uint32_t max_concurrent_streams; + isc_tlsctx_t *sslctx; + isc_tlsctx_cache_t *sslctx_cache; + char **http_endpoints; + size_t http_endpoints_number; + isc_quota_t *http_quota; + uint32_t max_concurrent_streams; ISC_LINK(ns_listenelt_t) link; }; @@ -59,6 +60,7 @@ struct ns_listenlist { }; typedef struct ns_listen_tls_params { + const char *name; const char *key; const char *cert; uint32_t protocols; @@ -76,24 +78,25 @@ typedef struct ns_listen_tls_params { isc_result_t ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, + dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - ns_listenelt_t **target); + isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target); /*%< * Create a listen-on list element. * * Requires: * \li 'targetp' is a valid pointer to a pointer containing 'NULL'; * \li 'tls_params' is a valid, non-'NULL' pointer if 'tls' equals 'true'. + * \li 'tlsctx_cache' is a valid, non-'NULL' pointer if 'tls' equals 'true'. */ isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, + dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - char **endpoints, size_t nendpoints, - isc_quota_t *quota, const uint32_t max_streams, - ns_listenelt_t **target); + isc_tlsctx_cache_t *tlsctx_cache, char **endpoints, + size_t nendpoints, isc_quota_t *quota, + const uint32_t max_streams, ns_listenelt_t **target); /*%< * Create a listen-on list element for HTTP(S). */ @@ -124,7 +127,8 @@ ns_listenlist_detach(ns_listenlist_t **listp); isc_result_t ns_listenlist_default(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, - bool enabled, ns_listenlist_t **target); + bool enabled, const uint16_t family, + ns_listenlist_t **target); /*%< * Create a listen-on list with default contents, matching * all addresses with port 'port' (if 'enabled' is true), diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index 6aa67dbd2b..602d9c3807 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -24,50 +24,92 @@ static void destroy(ns_listenlist_t *list); -isc_result_t -ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, - const ns_listen_tls_params_t *tls_params, - ns_listenelt_t **target) { +static isc_result_t +listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, + dns_acl_t *acl, const uint16_t family, const bool is_http, + bool tls, const ns_listen_tls_params_t *tls_params, + isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) { ns_listenelt_t *elt = NULL; isc_result_t result = ISC_R_SUCCESS; isc_tlsctx_t *sslctx = NULL; REQUIRE(target != NULL && *target == NULL); - REQUIRE(!tls || tls_params != NULL); + REQUIRE(!tls || (tls_params != NULL && tlsctx_cache != NULL)); if (tls) { - result = isc_tlsctx_createserver(tls_params->key, - tls_params->cert, &sslctx); + const isc_tlsctx_cache_transport_t transport = + is_http ? isc_tlsctx_cache_https : isc_tlsctx_cache_tls; + + /* + * Let's try to reuse the existing context from the cache in + * order to avoid excessive TLS contexts creation. + */ + result = isc_tlsctx_cache_find(tlsctx_cache, tls_params->name, + transport, family, &sslctx); if (result != ISC_R_SUCCESS) { - return (result); - } + /* + * The lookup failed, let's try to create a new context + * and store it within the cache. + */ + INSIST(tls_params->name != NULL && + *tls_params->name != '\0'); - if (tls_params->protocols != 0) { - isc_tlsctx_set_protocols(sslctx, tls_params->protocols); - } - - if (tls_params->dhparam_file != NULL) { - if (!isc_tlsctx_load_dhparams(sslctx, - tls_params->dhparam_file)) - { - isc_tlsctx_free(&sslctx); - return (ISC_R_FAILURE); + result = isc_tlsctx_createserver( + tls_params->key, tls_params->cert, &sslctx); + if (result != ISC_R_SUCCESS) { + return (result); } - } - if (tls_params->ciphers != NULL) { - isc_tlsctx_set_cipherlist(sslctx, tls_params->ciphers); - } + if (tls_params->protocols != 0) { + isc_tlsctx_set_protocols(sslctx, + tls_params->protocols); + } - if (tls_params->prefer_server_ciphers_set) { - isc_tlsctx_prefer_server_ciphers( - sslctx, tls_params->prefer_server_ciphers); - } + if (tls_params->dhparam_file != NULL) { + if (!isc_tlsctx_load_dhparams( + sslctx, tls_params->dhparam_file)) { + isc_tlsctx_free(&sslctx); + return (ISC_R_FAILURE); + } + } - if (tls_params->session_tickets_set) { - isc_tlsctx_session_tickets(sslctx, - tls_params->session_tickets); + if (tls_params->ciphers != NULL) { + isc_tlsctx_set_cipherlist(sslctx, + tls_params->ciphers); + } + + if (tls_params->prefer_server_ciphers_set) { + isc_tlsctx_prefer_server_ciphers( + sslctx, + tls_params->prefer_server_ciphers); + } + + if (tls_params->session_tickets_set) { + isc_tlsctx_session_tickets( + sslctx, tls_params->session_tickets); + } + +#ifdef HAVE_LIBNGHTTP2 + if (is_http) { + isc_tlsctx_enable_http2server_alpn(sslctx); + } +#endif /* HAVE_LIBNGHTTP2 */ + + if (!is_http) { + isc_tlsctx_enable_dot_server_alpn(sslctx); + } + + /* + * The storing in the cache should not fail because the + * (re)initialisation happens from within a single + * thread. + */ + RUNTIME_CHECK(isc_tlsctx_cache_add( + tlsctx_cache, tls_params->name, + transport, family, sslctx, + NULL) == ISC_R_SUCCESS); + } else { + INSIST(sslctx != NULL); } } @@ -79,6 +121,10 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, elt->dscp = dscp; elt->acl = acl; elt->sslctx = sslctx; + elt->sslctx_cache = NULL; + if (sslctx != NULL && tlsctx_cache != NULL) { + isc_tlsctx_cache_attach(tlsctx_cache, &elt->sslctx_cache); + } elt->http_endpoints = NULL; elt->http_endpoints_number = 0; elt->http_quota = NULL; @@ -87,21 +133,30 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, return (ISC_R_SUCCESS); } +isc_result_t +ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, + dns_acl_t *acl, const uint16_t family, bool tls, + const ns_listen_tls_params_t *tls_params, + isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) { + return listenelt_create(mctx, port, dscp, acl, family, false, tls, + tls_params, tlsctx_cache, target); +} + isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, + dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - char **endpoints, size_t nendpoints, - isc_quota_t *quota, const uint32_t max_streams, - ns_listenelt_t **target) { + isc_tlsctx_cache_t *tlsctx_cache, char **endpoints, + size_t nendpoints, isc_quota_t *quota, + const uint32_t max_streams, ns_listenelt_t **target) { isc_result_t result; REQUIRE(target != NULL && *target == NULL); REQUIRE(endpoints != NULL && *endpoints != NULL); REQUIRE(nendpoints > 0); - result = ns_listenelt_create(mctx, http_port, dscp, acl, tls, - tls_params, target); + result = listenelt_create(mctx, http_port, dscp, acl, family, true, tls, + tls_params, tlsctx_cache, target); if (result == ISC_R_SUCCESS) { (*target)->is_http = true; (*target)->http_endpoints = endpoints; @@ -123,8 +178,11 @@ ns_listenelt_destroy(ns_listenelt_t *elt) { if (elt->acl != NULL) { dns_acl_detach(&elt->acl); } - if (elt->sslctx != NULL) { - isc_tlsctx_free(&elt->sslctx); + + elt->sslctx = NULL; /* this one is going to be destroyed alongside the + sslctx_cache */ + if (elt->sslctx_cache != NULL) { + isc_tlsctx_cache_detach(&elt->sslctx_cache); } if (elt->http_endpoints != NULL) { size_t i; @@ -179,7 +237,8 @@ ns_listenlist_detach(ns_listenlist_t **listp) { isc_result_t ns_listenlist_default(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, - bool enabled, ns_listenlist_t **target) { + bool enabled, const uint16_t family, + ns_listenlist_t **target) { isc_result_t result; dns_acl_t *acl = NULL; ns_listenelt_t *elt = NULL; @@ -195,7 +254,8 @@ ns_listenlist_default(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, goto cleanup; } - result = ns_listenelt_create(mctx, port, dscp, acl, false, NULL, &elt); + result = ns_listenelt_create(mctx, port, dscp, acl, family, false, NULL, + NULL, &elt); if (result != ISC_R_SUCCESS) { goto cleanup_acl; } diff --git a/lib/ns/tests/listenlist_test.c b/lib/ns/tests/listenlist_test.c index 94012e9fdc..a197590f0a 100644 --- a/lib/ns/tests/listenlist_test.c +++ b/lib/ns/tests/listenlist_test.c @@ -71,7 +71,7 @@ ns_listenlist_default_test(void **state) { UNUSED(state); - result = ns_listenlist_default(mctx, port, -1, false, &list); + result = ns_listenlist_default(mctx, port, -1, false, AF_INET, &list); assert_int_equal(result, ISC_R_SUCCESS); assert_non_null(list); @@ -98,7 +98,7 @@ ns_listenlist_default_test(void **state) { ns_listenlist_detach(&list); - result = ns_listenlist_default(mctx, port, -1, true, &list); + result = ns_listenlist_default(mctx, port, -1, true, AF_INET, &list); assert_int_equal(result, ISC_R_SUCCESS); assert_false(ISC_LIST_EMPTY(list->elts)); diff --git a/lib/ns/tests/nstest.c b/lib/ns/tests/nstest.c index 3c458ca1d6..5202b2e9b3 100644 --- a/lib/ns/tests/nstest.c +++ b/lib/ns/tests/nstest.c @@ -11,7 +11,6 @@ /*! \file */ -#include "nstest.h" #include #include #include @@ -50,6 +49,8 @@ #include #include +#include "nstest.h" + isc_mem_t *mctx = NULL; isc_log_t *lctx = NULL; isc_nm_t *netmgr = NULL; @@ -236,7 +237,7 @@ create_managers(void) { dispatchmgr, maintask, NULL, ncpus, false, &interfacemgr)); - CHECK(ns_listenlist_default(mctx, port, -1, true, &listenon)); + CHECK(ns_listenlist_default(mctx, port, -1, true, AF_INET, &listenon)); ns_interfacemgr_setlistenon4(interfacemgr, listenon); ns_listenlist_detach(&listenon); From 64f7c5566214a5fe9b6fac63f53e741b20cbbda0 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Thu, 23 Dec 2021 16:08:41 +0200 Subject: [PATCH 3/4] Use the TLS context cache for client-side contexts (XoT) This commit enables client-side TLS contexts re-use for zone transfers over TLS. That, in turn, makes it possible to use the internal session cache associated with the contexts, allowing the TLS connections to be established faster and requiring fewer resources by not going through the full TLS handshake procedure. Previously that would recreate the context on every connection, making TLS session resumption impossible. Also, this change lays down a foundation for Strict TLS (when the client validates a server certificate), as the TLS context cache can be extended to store additional data required for validation (like intermediates CA chain). --- bin/named/include/named/server.h | 1 + bin/named/server.c | 14 +++++ bin/named/transportconf.c | 6 +- lib/dns/include/dns/transport.h | 5 ++ lib/dns/include/dns/xfrin.h | 4 +- lib/dns/include/dns/zone.h | 13 +++++ lib/dns/transport.c | 28 +++++++++ lib/dns/xfrin.c | 97 +++++++++++++++++++++++++------- lib/dns/zone.c | 28 ++++++++- 9 files changed, 173 insertions(+), 23 deletions(-) diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index dd0d670118..869f5f764e 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -112,6 +112,7 @@ struct named_server { char *lockfile; isc_tlsctx_cache_t *tlsctx_server_cache; + isc_tlsctx_cache_t *tlsctx_client_cache; }; #define NAMED_SERVER_MAGIC ISC_MAGIC('S', 'V', 'E', 'R') diff --git a/bin/named/server.c b/bin/named/server.c index 0ac53c9a76..8e27090da8 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -8431,6 +8431,15 @@ load_configuration(const char *filename, named_server_t *server, server->tlsctx_server_cache = isc_tlsctx_cache_new(named_g_mctx); + if (server->tlsctx_client_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_client_cache); + } + + server->tlsctx_client_cache = isc_tlsctx_cache_new(named_g_mctx); + + dns_zonemgr_set_tlsctx_cache(server->zonemgr, + server->tlsctx_client_cache); + /* * Fill in the maps array, used for resolving defaults. */ @@ -10182,6 +10191,7 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) { server->magic = NAMED_SERVER_MAGIC; server->tlsctx_server_cache = NULL; + server->tlsctx_client_cache = NULL; *serverp = server; } @@ -10241,6 +10251,10 @@ named_server_destroy(named_server_t **serverp) { isc_tlsctx_cache_detach(&server->tlsctx_server_cache); } + if (server->tlsctx_client_cache != NULL) { + isc_tlsctx_cache_detach(&server->tlsctx_client_cache); + } + server->magic = 0; isc_mem_put(server->mctx, server, sizeof(*server)); *serverp = NULL; diff --git a/bin/named/transportconf.c b/bin/named/transportconf.c index 61c7462f0b..40c21a6cd0 100644 --- a/bin/named/transportconf.c +++ b/bin/named/transportconf.c @@ -108,6 +108,7 @@ add_doh_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) { transport = dns_transport_new(&dohname, DNS_TRANSPORT_HTTP, list); + dns_transport_set_tlsname(transport, dohid); parse_transport_option(doh, transport, "key-file", dns_transport_set_keyfile); parse_transport_option(doh, transport, "cert-file", @@ -165,6 +166,7 @@ add_tls_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) { transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, list); + dns_transport_set_tlsname(transport, tlsid); parse_transport_option(tls, transport, "key-file", dns_transport_set_keyfile); parse_transport_option(tls, transport, "cert-file", @@ -228,10 +230,12 @@ static void transport_list_add_ephemeral(dns_transport_list_t *list) { isc_result_t result; dns_name_t tlsname; + dns_transport_t *transport; create_name("ephemeral", &tlsname); - (void)dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, list); + transport = dns_transport_new(&tlsname, DNS_TRANSPORT_TLS, list); + dns_transport_set_tlsname(transport, "ephemeral"); return; failure: diff --git a/lib/dns/include/dns/transport.h b/lib/dns/include/dns/transport.h index 91fe382630..69ed013c87 100644 --- a/lib/dns/include/dns/transport.h +++ b/lib/dns/include/dns/transport.h @@ -54,6 +54,8 @@ dns_http_mode_t dns_transport_get_mode(dns_transport_t *transport); char * dns_transport_get_ciphers(dns_transport_t *transport); +char * +dns_transport_get_tlsname(dns_transport_t *transport); uint32_t dns_transport_get_tls_versions(const dns_transport_t *transport); bool @@ -82,6 +84,9 @@ void dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode); void dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers); +void +dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname); + void dns_transport_set_tls_versions(dns_transport_t *transport, const uint32_t tls_versions); diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h index d080b089cb..6cbac87f90 100644 --- a/lib/dns/include/dns/xfrin.h +++ b/lib/dns/include/dns/xfrin.h @@ -25,6 +25,7 @@ ***/ #include +#include #include #include @@ -49,7 +50,8 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, const isc_sockaddr_t *primaryaddr, const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp, dns_tsigkey_t *tsigkey, dns_transport_t *transport, - isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done, + isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx, + isc_nm_t *netmgr, dns_xfrindone_t done, dns_xfrin_ctx_t **xfrp); /*%< * Attempt to start an incoming zone transfer of 'zone' diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index d82d6eb0da..5211740406 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -2068,6 +2069,18 @@ dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, *\li 'local' to be a valid sockaddr. */ +void +dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, + isc_tlsctx_cache_t *tlsctx_cache); +/*%< + * Set the TLS client context cache used for zone transfers via + * encrypted transports (e.g. XoT). + * + * Requires: + *\li 'zmgr' is a valid zone manager. + *\li 'tlsctx_cache' is a valid TLS context cache. + */ + void dns_zone_forcereload(dns_zone_t *zone); /*%< diff --git a/lib/dns/transport.c b/lib/dns/transport.c index 9f7e2bdc79..36df552634 100644 --- a/lib/dns/transport.c +++ b/lib/dns/transport.c @@ -44,6 +44,7 @@ struct dns_transport { isc_mem_t *mctx; dns_transport_type_t type; struct { + char *tlsname; char *certfile; char *keyfile; char *cafile; @@ -269,6 +270,22 @@ dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers) { } } +void +dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname) { + REQUIRE(VALID_TRANSPORT(transport)); + REQUIRE(transport->type == DNS_TRANSPORT_TLS || + transport->type == DNS_TRANSPORT_HTTP); + + if (transport->tls.tlsname != NULL) { + isc_mem_free(transport->mctx, transport->tls.tlsname); + } + + if (tlsname != NULL) { + transport->tls.tlsname = isc_mem_strdup(transport->mctx, + tlsname); + } +} + char * dns_transport_get_ciphers(dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); @@ -276,6 +293,13 @@ dns_transport_get_ciphers(dns_transport_t *transport) { return (transport->tls.ciphers); } +char * +dns_transport_get_tlsname(dns_transport_t *transport) { + REQUIRE(VALID_TRANSPORT(transport)); + + return (transport->tls.tlsname); +} + void dns_transport_set_prefer_server_ciphers(dns_transport_t *transport, const bool prefer) { @@ -330,6 +354,10 @@ transport_destroy(dns_transport_t *transport) { isc_mem_free(transport->mctx, transport->tls.ciphers); } + if (transport->tls.tlsname != NULL) { + isc_mem_free(transport->mctx, transport->tls.tlsname); + } + isc_mem_putanddetach(&transport->mctx, transport, sizeof(*transport)); } diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index bdda30cbe2..d48cebbed1 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -163,7 +163,6 @@ struct dns_xfrin_ctx { unsigned int sincetsig; /*%< recvd since the last TSIG */ dns_transport_t *transport; - isc_tlsctx_t *tlsctx; dns_xfrindone_t done; @@ -183,6 +182,8 @@ struct dns_xfrin_ctx { dns_rdata_t firstsoa; unsigned char *firstsoa_data; + + isc_tlsctx_cache_t *tlsctx_cache; }; #define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I') @@ -199,7 +200,7 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr, dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr, const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp, dns_tsigkey_t *tsigkey, dns_transport_t *transport, - dns_xfrin_ctx_t **xfrp); + isc_tlsctx_cache_t *tlsctx_cache, dns_xfrin_ctx_t **xfrp); static isc_result_t axfr_init(dns_xfrin_ctx_t *xfr); @@ -693,7 +694,8 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, const isc_sockaddr_t *primaryaddr, const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp, dns_tsigkey_t *tsigkey, dns_transport_t *transport, - isc_mem_t *mctx, isc_nm_t *netmgr, dns_xfrindone_t done, + isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx, + isc_nm_t *netmgr, dns_xfrindone_t done, dns_xfrin_ctx_t **xfrp) { dns_name_t *zonename = dns_zone_getorigin(zone); dns_xfrin_ctx_t *xfr = NULL; @@ -712,7 +714,7 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, xfrin_create(mctx, zone, db, netmgr, zonename, dns_zone_getclass(zone), xfrtype, primaryaddr, sourceaddr, dscp, tsigkey, transport, - &xfr); + tlsctx_cache, &xfr); if (db != NULL) { xfr->zone_had_db = true; @@ -860,7 +862,7 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr, dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr, const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp, dns_tsigkey_t *tsigkey, dns_transport_t *transport, - dns_xfrin_ctx_t **xfrp) { + isc_tlsctx_cache_t *tlsctx_cache, dns_xfrin_ctx_t **xfrp) { dns_xfrin_ctx_t *xfr = NULL; xfr = isc_mem_get(mctx, sizeof(*xfr)); @@ -918,6 +920,8 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr, isc_buffer_init(&xfr->qbuffer, &xfr->qbuffer_data[2], sizeof(xfr->qbuffer_data) - 2); + isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache); + xfr->magic = XFRIN_MAGIC; *xfrp = xfr; @@ -928,6 +932,7 @@ xfrin_start(dns_xfrin_ctx_t *xfr) { isc_result_t result; dns_xfrin_ctx_t *connect_xfr = NULL; dns_transport_type_t transport_type = DNS_TRANSPORT_TCP; + isc_tlsctx_t *tlsctx = NULL, *found = NULL; (void)isc_refcount_increment0(&xfr->connects); dns_xfrin_attach(xfr, &connect_xfr); @@ -948,32 +953,79 @@ xfrin_start(dns_xfrin_ctx_t *xfr) { break; case DNS_TRANSPORT_TLS: { uint32_t tls_versions; - const char *ciphers; + const char *ciphers = NULL; bool prefer_server_ciphers; - CHECK(isc_tlsctx_createclient(&xfr->tlsctx)); - if (xfr->transport != NULL) { + const uint16_t family = isc_sockaddr_pf(&xfr->primaryaddr) == + PF_INET6 + ? AF_INET6 + : AF_INET; + const char *tlsname = NULL; + + INSIST(xfr->transport != NULL); + tlsname = dns_transport_get_tlsname(xfr->transport); + INSIST(tlsname != NULL && *tlsname != '\0'); + + /* + * Let's try to re-use the already created context. This way + * we have a chance to resume the TLS session, bypassing the + * full TLS handshake procedure, making establishing + * subsequent TLS connections for XoT faster. + */ + result = isc_tlsctx_cache_find(xfr->tlsctx_cache, tlsname, + isc_tlsctx_cache_tls, family, + &tlsctx); + if (result != ISC_R_SUCCESS) { + /* + * So, no context exists. Let's create one using the + * parameters from the configuration file and try to + * store it for further reuse. + */ + CHECK(isc_tlsctx_createclient(&tlsctx)); tls_versions = dns_transport_get_tls_versions(xfr->transport); if (tls_versions != 0) { - isc_tlsctx_set_protocols(xfr->tlsctx, - tls_versions); + isc_tlsctx_set_protocols(tlsctx, tls_versions); } ciphers = dns_transport_get_ciphers(xfr->transport); if (ciphers != NULL) { - isc_tlsctx_set_cipherlist(xfr->tlsctx, ciphers); + isc_tlsctx_set_cipherlist(tlsctx, ciphers); } if (dns_transport_get_prefer_server_ciphers( xfr->transport, &prefer_server_ciphers)) { isc_tlsctx_prefer_server_ciphers( - xfr->tlsctx, prefer_server_ciphers); + tlsctx, prefer_server_ciphers); + } + isc_tlsctx_enable_dot_client_alpn(tlsctx); + + result = isc_tlsctx_cache_add( + xfr->tlsctx_cache, tlsname, + isc_tlsctx_cache_tls, family, tlsctx, &found); + if (result == ISC_R_EXISTS) { + /* + * It seems the entry has just been created + * from within another thread while we were + * initialising ours. Although this is + * unlikely, it could happen after + * startup/re-initialisation. In such a case, + * discard the new context and use the already + * established one from now on. + * + * Such situation will not occur after the + * initial 'warm-up', so it is not critical + * performance-wise. + */ + INSIST(found != NULL); + isc_tlsctx_free(&tlsctx); + tlsctx = found; + } else { + INSIST(result == ISC_R_SUCCESS); } } - isc_tlsctx_enable_dot_client_alpn(xfr->tlsctx); isc_nm_tlsdnsconnect(xfr->netmgr, &xfr->sourceaddr, &xfr->primaryaddr, xfrin_connect_done, - connect_xfr, 30000, 0, xfr->tlsctx); + connect_xfr, 30000, 0, tlsctx); } break; default: INSIST(0); @@ -983,8 +1035,13 @@ xfrin_start(dns_xfrin_ctx_t *xfr) { return (ISC_R_SUCCESS); failure: - if (xfr->tlsctx != NULL) { - isc_tlsctx_free(&xfr->tlsctx); + /* + * The 'found' context is being managed by the TLS context cache. + * Thus, we should keep it as it is, as it will get destroyed + * alongside the cache. + */ + if (tlsctx != NULL && found != tlsctx) { + isc_tlsctx_free(&tlsctx); } isc_refcount_decrement0(&xfr->connects); dns_xfrin_detach(&connect_xfr); @@ -1032,10 +1089,6 @@ xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { isc_refcount_decrement0(&xfr->connects); - if (xfr->tlsctx != NULL) { - isc_tlsctx_free(&xfr->tlsctx); - } - if (atomic_load(&xfr->shuttingdown)) { result = ISC_R_SHUTTINGDOWN; } @@ -1670,6 +1723,10 @@ xfrin_destroy(dns_xfrin_ctx_t *xfr) { isc_mem_free(xfr->mctx, xfr->firstsoa_data); } + if (xfr->tlsctx_cache != NULL) { + isc_tlsctx_cache_detach(&xfr->tlsctx_cache); + } + isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr)); } diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 08112ac2b5..2e54038b3c 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -629,6 +630,8 @@ struct dns_zonemgr { struct dns_unreachable unreachable[UNREACH_CACHE_SIZE]; dns_keymgmt_t *keymgmt; + + isc_tlsctx_cache_t *tlsctx_cache; }; /*% @@ -18133,7 +18136,8 @@ got_transfer_quota(isc_task_t *task, isc_event_t *event) { } CHECK(dns_xfrin_create(zone, xfrtype, &primaryaddr, &sourceaddr, dscp, - zone->tsigkey, zone->transport, zone->mctx, + zone->tsigkey, zone->transport, + zone->zmgr->tlsctx_cache, zone->mctx, zone->zmgr->netmgr, zone_xfrdone, &zone->xfr)); LOCK_ZONE(zone); if (xfrtype == dns_rdatatype_axfr) { @@ -18831,6 +18835,8 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, isc_mutex_init(&zmgr->iolock); + zmgr->tlsctx_cache = NULL; + zmgr->magic = ZONEMGR_MAGIC; *zmgrp = zmgr; @@ -19188,6 +19194,9 @@ zonemgr_free(dns_zonemgr_t *zmgr) { zonemgr_keymgmt_destroy(zmgr); mctx = zmgr->mctx; + if (zmgr->tlsctx_cache != NULL) { + isc_tlsctx_cache_detach(&zmgr->tlsctx_cache); + } isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr)); isc_mem_detach(&mctx); } @@ -23627,3 +23636,20 @@ zone_nsecttl(dns_zone_t *zone) { return (ISC_MIN(zone->minimum, zone->soattl)); } + +void +dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, + isc_tlsctx_cache_t *tlsctx_cache) { + REQUIRE(DNS_ZONEMGR_VALID(zmgr)); + REQUIRE(tlsctx_cache != NULL); + + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + + if (zmgr->tlsctx_cache != NULL) { + isc_tlsctx_cache_detach(&zmgr->tlsctx_cache); + } + + isc_tlsctx_cache_attach(tlsctx_cache, &zmgr->tlsctx_cache); + + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); +} From cb330c432d38f7813f83b2a639f2bb835820e214 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Fri, 24 Dec 2021 14:54:19 +0200 Subject: [PATCH 4/4] Add a CHANGES entry [GL !5672] Mention that TLS contexts reuse was implemented. --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index df26bf014c..053fec41c9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +5784. [func] Implement TLS-contexts reuse. Reusing the + previously created TLS context objects can reduce + initialisation time for some configurations and enables + TLS session resumption for incoming zone transfers over + TLS (XoT). [GL #3067] + 5783. [func] named is now able to log TLS pre-master secrets for debugging purposes. This requires setting the SSLKEYLOGFILE environment variable appropriately.