This is an automated email from the ASF dual-hosted git repository.
maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 70bb3c1281 Add cache mechanism to certificate compression (#13284)
70bb3c1281 is described below
commit 70bb3c1281c406ea2cf7a573182177efade1f498
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Tue Jun 23 12:27:35 2026 -0600
Add cache mechanism to certificate compression (#13284)
* Add cache mechanism to certificate compression
Current implementation recompressed the cert chain on every TLS
handshake. Now cached on the first handshake per certificatees.
Cash gets invalidated when the OCSP staple refreshes.
New setting: proxy.config.ssl.server.cert_compression.cache (default 1).
BoringSSL only -- OpenSSL caches internally with no opt-out, so the
flag has no effect there.
* Check TS_HAS_CERT_COMPRESSION_CALLBACKS instead of TS_HAS_CERT_COMPRESSION
* Address copilot comments
* Address copilot comments
* Address copilot comments
* Address copilot comments
* Address unused param warnings
---
doc/admin-guide/files/records.yaml.en.rst | 18 ++
src/iocore/net/CMakeLists.txt | 10 +-
src/iocore/net/OCSPStapling.cc | 3 +
src/iocore/net/P_SSLConfig.h | 1 +
src/iocore/net/SSLClientUtils.cc | 2 +-
src/iocore/net/SSLConfig.cc | 1 +
src/iocore/net/SSLStats.cc | 1 +
src/iocore/net/SSLStats.h | 1 +
src/iocore/net/SSLUtils.cc | 6 +-
src/iocore/net/TLSCertCompression.cc | 114 +++++++-
src/iocore/net/TLSCertCompression.h | 39 ++-
src/iocore/net/TLSCertCompression_brotli.cc | 28 +-
src/iocore/net/TLSCertCompression_brotli.h | 3 +
src/iocore/net/TLSCertCompression_zlib.cc | 28 +-
src/iocore/net/TLSCertCompression_zlib.h | 3 +
src/iocore/net/TLSCertCompression_zstd.cc | 28 +-
src/iocore/net/TLSCertCompression_zstd.h | 3 +
.../net/unit_tests/benchmark_TLSCertCompression.cc | 317 +++++++++++++++++++++
src/records/RecordsConfig.cc | 2 +
.../replay/tls_cert_compression_cache.replay.yaml | 59 ++++
tests/gold_tests/tls/tls_cert_comp.test.py | 1 +
...rt_comp.test.py => tls_cert_comp_cache.test.py} | 108 +++----
22 files changed, 712 insertions(+), 64 deletions(-)
diff --git a/doc/admin-guide/files/records.yaml.en.rst
b/doc/admin-guide/files/records.yaml.en.rst
index 169c8b6a4c..8d9debca24 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -4322,6 +4322,24 @@ SSL Termination
proxy.config.ssl.server.cert_compression.algorithms: zlib,brotli
+.. ts:cv:: CONFIG proxy.config.ssl.server.cert_compression.cache INT 1
+ :reloadable:
+
+ Controls whether the compressed certificate is reused across
+ handshakes. With caching enabled (the default), the certificate is
+ compressed once and the result is reused. With caching disabled, the
+ certificate is recompressed on every handshake.
+
+ Has no effect on OpenSSL builds; OpenSSL always reuses the
+ compressed result.
+
+ ===== =================
+ Value Description
+ ===== =================
+ ``0`` Disables caching.
+ ``1`` Enables caching.
+ ===== =================
+
.. ts:cv:: CONFIG proxy.config.ssl.client.cert_compression.algorithms STRING
:reloadable:
diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt
index c5b66d8468..ac3f12d7cc 100644
--- a/src/iocore/net/CMakeLists.txt
+++ b/src/iocore/net/CMakeLists.txt
@@ -135,8 +135,14 @@ endif()
if(BUILD_TESTING)
# libinknet_stub.cc is need because GNU ld is sensitive to the order of
static libraries on the command line, and we have a cyclic dependency between
inknet and proxy
add_executable(
- test_net libinknet_stub.cc NetVCTest.cc unit_tests/test_ProxyProtocol.cc
unit_tests/test_SSLSNIConfig.cc
- unit_tests/test_YamlSNIConfig.cc unit_tests/unit_test_main.cc
+ test_net
+ libinknet_stub.cc
+ NetVCTest.cc
+ unit_tests/test_ProxyProtocol.cc
+ unit_tests/test_SSLSNIConfig.cc
+ unit_tests/test_YamlSNIConfig.cc
+ unit_tests/unit_test_main.cc
+ unit_tests/benchmark_TLSCertCompression.cc
)
# Use link groups to solve circular dependency
set(LINK_GROUP_LIBS
diff --git a/src/iocore/net/OCSPStapling.cc b/src/iocore/net/OCSPStapling.cc
index 0c500f9006..bce21e159f 100644
--- a/src/iocore/net/OCSPStapling.cc
+++ b/src/iocore/net/OCSPStapling.cc
@@ -37,6 +37,7 @@
#include "P_SSLConfig.h"
#include "P_SSLUtils.h"
#include "SSLStats.h"
+#include "TLSCertCompression.h"
#include "proxy/FetchSM.h"
// Macros for ASN1 and the code in TS_OCSP_* functions were borrowed from
OpenSSL 3.1.0 (a92271e03a8d0dee507b6f1e7f49512568b2c7ad),
@@ -1336,9 +1337,11 @@ ocsp_update()
if (stapling_refresh_response(cinf, &resp)) {
Dbg(dbg_ctl_ssl_ocsp, "Successfully refreshed OCSP for %s
certificate. url=%s", cinf->certname, cinf->uri);
Metrics::Counter::increment(ssl_rsb.ocsp_refreshed_cert);
+ cert_compress_invalidate_or_recompress(ctx.get());
} else {
Error("Failed to refresh OCSP for %s certificate. url=%s",
cinf->certname, cinf->uri);
Metrics::Counter::increment(ssl_rsb.ocsp_refresh_cert_failure);
+ cert_compress_invalidate_or_recompress(ctx.get());
}
} else {
ink_mutex_release(&cinf->stapling_mutex);
diff --git a/src/iocore/net/P_SSLConfig.h b/src/iocore/net/P_SSLConfig.h
index 62f3baf4a6..a29755ed64 100644
--- a/src/iocore/net/P_SSLConfig.h
+++ b/src/iocore/net/P_SSLConfig.h
@@ -90,6 +90,7 @@ struct SSLConfigParams : public ConfigInfo {
int alpn_protocols_array_size = 0;
char *server_cert_compression_algorithms;
+ bool server_cert_compression_cache = true;
char *client_cert_compression_algorithms;
char *server_tls13_cipher_suites;
diff --git a/src/iocore/net/SSLClientUtils.cc b/src/iocore/net/SSLClientUtils.cc
index 453b971dd7..1ec5459ef3 100644
--- a/src/iocore/net/SSLClientUtils.cc
+++ b/src/iocore/net/SSLClientUtils.cc
@@ -255,7 +255,7 @@ SSLInitClientContext(const SSLConfigParams *params)
for (const char *token = tok.getNext(); token; token = tok.getNext()) {
algs.emplace_back(token);
}
- if (register_certificate_compression_preference(client_ctx, algs) != 1) {
+ if (register_certificate_compression_preference(client_ctx, algs, true) !=
1) {
SSLError("invalid client certificate compression algorithm list in %s",
ts::filename::RECORDS);
goto fail;
}
diff --git a/src/iocore/net/SSLConfig.cc b/src/iocore/net/SSLConfig.cc
index 95ab59891f..9e8515d1c2 100644
--- a/src/iocore/net/SSLConfig.cc
+++ b/src/iocore/net/SSLConfig.cc
@@ -502,6 +502,7 @@ SSLConfigParams::initialize(ConfigContext ctx)
if (auto
rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.cert_compression.algorithms")};
rec_str) {
server_cert_compression_algorithms = ats_stringdup(rec_str);
}
+ server_cert_compression_cache =
RecGetRecordInt("proxy.config.ssl.server.cert_compression.cache").value_or(1);
if (auto
rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.cert_compression.algorithms")};
rec_str) {
client_cert_compression_algorithms = ats_stringdup(rec_str);
}
diff --git a/src/iocore/net/SSLStats.cc b/src/iocore/net/SSLStats.cc
index 06d22883be..2ca32677f0 100644
--- a/src/iocore/net/SSLStats.cc
+++ b/src/iocore/net/SSLStats.cc
@@ -177,6 +177,7 @@ SSLInitializeStatistics()
ssl_rsb.cert_compress_zlib =
Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.zlib");
ssl_rsb.cert_compress_zlib_failure =
Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.zlib_failure");
+ ssl_rsb.cert_compress_cache_hit =
Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.cache_hit");
ssl_rsb.cert_decompress_zlib =
Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.zlib");
ssl_rsb.cert_decompress_zlib_failure =
Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.zlib_failure");
ssl_rsb.cert_compress_brotli =
Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.brotli");
diff --git a/src/iocore/net/SSLStats.h b/src/iocore/net/SSLStats.h
index 76324dff72..c00371b825 100644
--- a/src/iocore/net/SSLStats.h
+++ b/src/iocore/net/SSLStats.h
@@ -39,6 +39,7 @@ using ts::Metrics;
struct SSLStatsBlock {
Metrics::Counter::AtomicType *cert_compress_zlib
= nullptr;
Metrics::Counter::AtomicType *cert_compress_zlib_failure
= nullptr;
+ Metrics::Counter::AtomicType *cert_compress_cache_hit
= nullptr;
Metrics::Counter::AtomicType *cert_decompress_zlib
= nullptr;
Metrics::Counter::AtomicType *cert_decompress_zlib_failure
= nullptr;
Metrics::Counter::AtomicType *cert_compress_brotli
= nullptr;
diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc
index 407dfd4b87..5928042d41 100644
--- a/src/iocore/net/SSLUtils.cc
+++ b/src/iocore/net/SSLUtils.cc
@@ -448,7 +448,7 @@ SSLMultiCertConfigLoader::_enable_cert_compression(SSL_CTX
*ctx)
}
}
- if (register_certificate_compression_preference(ctx, algs) == 1) {
+ if (register_certificate_compression_preference(ctx, algs,
this->_params->server_cert_compression_cache) == 1) {
return true;
} else {
SSLError("Failed to enable certificate compression");
@@ -882,6 +882,10 @@ SSLInitializeLibrary()
ssl_stapling_ex_init();
+#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
+ cert_compress_cache_init();
+#endif
+
// Reserve an application data index so that we can attach
// the SSLNetVConnection to the SSL session.
ssl_vc_index = SSL_get_ex_new_index(0, (void *)"NetVC index", nullptr,
nullptr, nullptr);
diff --git a/src/iocore/net/TLSCertCompression.cc
b/src/iocore/net/TLSCertCompression.cc
index 90192990f3..707abe13e6 100644
--- a/src/iocore/net/TLSCertCompression.cc
+++ b/src/iocore/net/TLSCertCompression.cc
@@ -97,8 +97,107 @@ find_algorithm(std::string const &name)
} // end anonymous namespace
#endif
+#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
+static int cert_compress_cache_index = -1;
+
+static void
+cert_compress_cache_free_cb(void * /* parent */, void *ptr, CRYPTO_EX_DATA *
/* ad */, int /* idx */, long /* argl */,
+ void * /* argp */)
+{
+ auto *cache = static_cast<CertCompressionCache *>(ptr);
+ if (cache) {
+ for (auto &slot : cache->slots) {
+ delete slot.live.load(std::memory_order_acquire);
+ delete slot.retired.load(std::memory_order_acquire);
+ }
+ delete cache;
+ }
+}
+
+void
+cert_compress_cache_init()
+{
+ if (cert_compress_cache_index != -1) {
+ return;
+ }
+ cert_compress_cache_index = SSL_CTX_get_ex_new_index(0, nullptr, nullptr,
nullptr, cert_compress_cache_free_cb);
+}
+
+CertCompressionCache *
+cert_compress_cache_get(SSL_CTX *ctx)
+{
+ if (cert_compress_cache_index < 0) {
+ return nullptr;
+ }
+ return static_cast<CertCompressionCache *>(SSL_CTX_get_ex_data(ctx,
cert_compress_cache_index));
+}
+
+static void
+cert_compress_cache_attach(SSL_CTX *ctx)
+{
+ if (cert_compress_cache_index < 0) {
+ return;
+ }
+ auto *cache = new CertCompressionCache();
+ if (SSL_CTX_set_ex_data(ctx, cert_compress_cache_index, cache) != 1) {
+ delete cache;
+ }
+}
+
+void
+cert_compress_cache_try_publish(CertCompressionCache::Slot &slot,
CertCompressionCache::Entry const *fresh)
+{
+ CertCompressionCache::Entry const *expected = nullptr;
+ if (!slot.live.compare_exchange_strong(expected, fresh,
std::memory_order_acq_rel)) {
+ delete fresh;
+ }
+}
+
+void
+cert_compress_cache_invalidate(SSL_CTX *ctx)
+{
+ auto *cache = cert_compress_cache_get(ctx);
+ if (!cache) {
+ return;
+ }
+ for (auto &slot : cache->slots) {
+ auto const *prev = slot.live.exchange(nullptr,
std::memory_order_acq_rel);
+ auto const *to_free = slot.retired.exchange(prev,
std::memory_order_acq_rel);
+ delete to_free;
+ }
+ Dbg(dbg_ctl_ssl_cert_compress, "Cache invalidated for SSL_CTX %p", ctx);
+}
+#endif
+
+#if HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE
+void
+cert_compress_compress_certs(SSL_CTX *ctx)
+{
+ for (unsigned int i = 0; i < countof(supported_algs); ++i) {
+ if (supported_algs[i].name == nullptr) {
+ continue;
+ }
+ if (SSL_CTX_compress_certs(ctx, supported_algs[i].number)) {
+ Dbg(dbg_ctl_ssl_cert_compress, "Pre-compressed certs for alg %s on
SSL_CTX %p", supported_algs[i].name, ctx);
+ }
+ }
+}
+#endif
+
+void
+cert_compress_invalidate_or_recompress(SSL_CTX *ctx)
+{
+#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
+ cert_compress_cache_invalidate(ctx);
+#elif HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE
+ cert_compress_compress_certs(ctx);
+#else
+ (void)ctx;
+#endif
+}
+
int
-register_certificate_compression_preference(SSL_CTX *ctx, const
std::vector<std::string> &specified_algs)
+register_certificate_compression_preference(SSL_CTX *ctx, const
std::vector<std::string> &specified_algs, bool cache)
{
ink_assert(ctx != nullptr);
if (specified_algs.empty()) {
@@ -121,8 +220,14 @@ register_certificate_compression_preference(SSL_CTX *ctx,
const std::vector<std:
}
Dbg(dbg_ctl_ssl_cert_compress, "Enabled %s", info->name);
}
+
+ if (cache) {
+ cert_compress_cache_attach(ctx);
+ }
+
return 1;
#elif HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE
+ (void)cache;
if (specified_algs.size() > N_ALGORITHMS) {
return 0;
}
@@ -139,8 +244,13 @@ register_certificate_compression_preference(SSL_CTX *ctx,
const std::vector<std:
algs[n++] = info->number;
Dbg(dbg_ctl_ssl_cert_compress, "Enabled %s", info->name);
}
- return SSL_CTX_set1_cert_comp_preference(ctx, algs, n);
+ int ret = SSL_CTX_set1_cert_comp_preference(ctx, algs, n);
+ if (ret == 1) {
+ cert_compress_compress_certs(ctx);
+ }
+ return ret;
#else
+ (void)cache;
// If Certificate Compression is unsupported there's nothing to do.
// No need to raise an error since handshake would be done successfully
without compression.
Dbg(dbg_ctl_ssl_cert_compress, "Certificate Compression is unsupported");
diff --git a/src/iocore/net/TLSCertCompression.h
b/src/iocore/net/TLSCertCompression.h
index fa7bbc6ee5..e60b2e4054 100644
--- a/src/iocore/net/TLSCertCompression.h
+++ b/src/iocore/net/TLSCertCompression.h
@@ -23,9 +23,13 @@
#pragma once
+#include "tscore/ink_config.h"
+
#include <openssl/ssl.h>
-#include <vector>
+#include <atomic>
+#include <cstdint>
#include <string>
+#include <vector>
// RFC 8879 uses uint24 for uncompressed_length, allowing up to ~16 MB.
// Real certificate chains rarely exceed 10-30 KB even with large RSA
@@ -38,6 +42,37 @@ constexpr size_t MAX_CERT_UNCOMPRESSED_LEN = 128 * 1024;
*
* @param[in] ctx SSL_CTX
* @param[in] algs A vector that contains compression algorithm names ("zlib",
"brotli", or "zstd")
+ * @param[in] cache If true, cache the compressed cert chain per SSL_CTX
(BoringSSL only; no-op on OpenSSL)
* @return 1 on success
*/
-int register_certificate_compression_preference(SSL_CTX *ctx, const
std::vector<std::string> &algs);
+int register_certificate_compression_preference(SSL_CTX *ctx, const
std::vector<std::string> &algs, bool cache);
+
+#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
+
+// Max algorithm ID + 1, used to size the cache entry array
+constexpr int CERT_COMPRESS_MAX_ALG_ID = 4;
+
+// Keyed by (SSL_CTX, alg). Assumes one cert chain per SSL_CTX, which the
+// BoringSSL build path enforces (HAVE_NATIVE_DUAL_CERT_SUPPORT is off).
+struct CertCompressionCache {
+ struct Entry {
+ std::vector<uint8_t> bytes;
+ };
+ struct Slot {
+ std::atomic<Entry const *> live{nullptr};
+ std::atomic<Entry const *> retired{nullptr};
+ };
+ Slot slots[CERT_COMPRESS_MAX_ALG_ID];
+};
+
+void cert_compress_cache_init();
+CertCompressionCache *cert_compress_cache_get(SSL_CTX *ctx);
+void cert_compress_cache_invalidate(SSL_CTX *ctx);
+void
cert_compress_cache_try_publish(CertCompressionCache::Slot &slot,
CertCompressionCache::Entry const *fresh);
+#endif
+
+#if HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE
+void cert_compress_compress_certs(SSL_CTX *ctx);
+#endif
+
+void cert_compress_invalidate_or_recompress(SSL_CTX *ctx);
diff --git a/src/iocore/net/TLSCertCompression_brotli.cc
b/src/iocore/net/TLSCertCompression_brotli.cc
index 52cf22dada..3c16394099 100644
--- a/src/iocore/net/TLSCertCompression_brotli.cc
+++ b/src/iocore/net/TLSCertCompression_brotli.cc
@@ -27,11 +27,28 @@
#include <openssl/ssl.h>
#include <brotli/decode.h>
#include <brotli/encode.h>
+#include <cstring>
int
-compression_func_brotli(SSL * /* ssl */, CBB *out, const uint8_t *in, size_t
in_len)
+compression_func_brotli(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len)
{
- // TODO Need a cache mechanism inside this function for better performance.
+ auto *cache = cert_compress_cache_get(SSL_get_SSL_CTX(ssl));
+
+ if (cache) {
+ auto const *entry =
cache->slots[CERT_COMPRESS_ALG_BROTLI].live.load(std::memory_order_acquire);
+ if (entry) {
+ uint8_t *buf;
+ if (CBB_reserve(out, &buf, entry->bytes.size()) != 1) {
+ Metrics::Counter::increment(ssl_rsb.cert_compress_brotli_failure);
+ return 0;
+ }
+ memcpy(buf, entry->bytes.data(), entry->bytes.size());
+ CBB_did_write(out, entry->bytes.size());
+ Metrics::Counter::increment(ssl_rsb.cert_compress_brotli);
+ Metrics::Counter::increment(ssl_rsb.cert_compress_cache_hit);
+ return 1;
+ }
+ }
uint8_t *buf;
unsigned long buf_len = BrotliEncoderMaxCompressedSize(in_len);
@@ -45,6 +62,13 @@ compression_func_brotli(SSL * /* ssl */, CBB *out, const
uint8_t *in, size_t in_
BROTLI_TRUE) {
CBB_did_write(out, buf_len);
Metrics::Counter::increment(ssl_rsb.cert_compress_brotli);
+
+ if (cache) {
+ auto *fresh = new CertCompressionCache::Entry();
+ fresh->bytes.assign(buf, buf + buf_len);
+ cert_compress_cache_try_publish(cache->slots[CERT_COMPRESS_ALG_BROTLI],
fresh);
+ }
+
return 1;
} else {
CBB_did_write(out, 0);
diff --git a/src/iocore/net/TLSCertCompression_brotli.h
b/src/iocore/net/TLSCertCompression_brotli.h
index 7026f4ff70..7367f3482a 100644
--- a/src/iocore/net/TLSCertCompression_brotli.h
+++ b/src/iocore/net/TLSCertCompression_brotli.h
@@ -26,5 +26,8 @@
#include <openssl/ssl.h>
#include <openssl/bytestring.h>
+// RFC 8879 algorithm ID for brotli
+constexpr int CERT_COMPRESS_ALG_BROTLI = 2;
+
int compression_func_brotli(SSL *ssl, CBB *out, const uint8_t *in, size_t
in_len);
int decompression_func_brotli(SSL *ssl, CRYPTO_BUFFER **out, size_t
uncompressed_len, const uint8_t *in, size_t in_len);
diff --git a/src/iocore/net/TLSCertCompression_zlib.cc
b/src/iocore/net/TLSCertCompression_zlib.cc
index 1d724ef541..622f50fd00 100644
--- a/src/iocore/net/TLSCertCompression_zlib.cc
+++ b/src/iocore/net/TLSCertCompression_zlib.cc
@@ -26,11 +26,28 @@
#include "SSLStats.h"
#include <openssl/ssl.h>
#include <zlib.h>
+#include <cstring>
int
-compression_func_zlib(SSL * /* ssl */, CBB *out, const uint8_t *in, size_t
in_len)
+compression_func_zlib(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len)
{
- // TODO Need a cache mechanism inside this function for better performance.
+ auto *cache = cert_compress_cache_get(SSL_get_SSL_CTX(ssl));
+
+ if (cache) {
+ auto const *entry =
cache->slots[CERT_COMPRESS_ALG_ZLIB].live.load(std::memory_order_acquire);
+ if (entry) {
+ uint8_t *buf;
+ if (CBB_reserve(out, &buf, entry->bytes.size()) != 1) {
+ Metrics::Counter::increment(ssl_rsb.cert_compress_zlib_failure);
+ return 0;
+ }
+ memcpy(buf, entry->bytes.data(), entry->bytes.size());
+ CBB_did_write(out, entry->bytes.size());
+ Metrics::Counter::increment(ssl_rsb.cert_compress_zlib);
+ Metrics::Counter::increment(ssl_rsb.cert_compress_cache_hit);
+ return 1;
+ }
+ }
uint8_t *buf;
unsigned long buf_len = compressBound(in_len);
@@ -43,6 +60,13 @@ compression_func_zlib(SSL * /* ssl */, CBB *out, const
uint8_t *in, size_t in_le
if (compress(buf, &buf_len, in, in_len) == Z_OK) {
CBB_did_write(out, buf_len);
Metrics::Counter::increment(ssl_rsb.cert_compress_zlib);
+
+ if (cache) {
+ auto *fresh = new CertCompressionCache::Entry();
+ fresh->bytes.assign(buf, buf + buf_len);
+ cert_compress_cache_try_publish(cache->slots[CERT_COMPRESS_ALG_ZLIB],
fresh);
+ }
+
return 1;
} else {
CBB_did_write(out, 0);
diff --git a/src/iocore/net/TLSCertCompression_zlib.h
b/src/iocore/net/TLSCertCompression_zlib.h
index 622f8efb5a..9f913873f1 100644
--- a/src/iocore/net/TLSCertCompression_zlib.h
+++ b/src/iocore/net/TLSCertCompression_zlib.h
@@ -26,5 +26,8 @@
#include <openssl/ssl.h>
#include <openssl/bytestring.h>
+// RFC 8879 algorithm ID for zlib
+constexpr int CERT_COMPRESS_ALG_ZLIB = 1;
+
int compression_func_zlib(SSL *ssl, CBB *out, const uint8_t *in, size_t
in_len);
int decompression_func_zlib(SSL *ssl, CRYPTO_BUFFER **out, size_t
uncompressed_len, const uint8_t *in, size_t in_len);
diff --git a/src/iocore/net/TLSCertCompression_zstd.cc
b/src/iocore/net/TLSCertCompression_zstd.cc
index 85cf2a94f9..8c7545d39c 100644
--- a/src/iocore/net/TLSCertCompression_zstd.cc
+++ b/src/iocore/net/TLSCertCompression_zstd.cc
@@ -26,11 +26,28 @@
#include "SSLStats.h"
#include <openssl/ssl.h>
#include <zstd.h>
+#include <cstring>
int
-compression_func_zstd(SSL * /* ssl */, CBB *out, const uint8_t *in, size_t
in_len)
+compression_func_zstd(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len)
{
- // TODO Need a cache mechanism inside this function for better performance.
+ auto *cache = cert_compress_cache_get(SSL_get_SSL_CTX(ssl));
+
+ if (cache) {
+ auto const *entry =
cache->slots[CERT_COMPRESS_ALG_ZSTD].live.load(std::memory_order_acquire);
+ if (entry) {
+ uint8_t *buf;
+ if (CBB_reserve(out, &buf, entry->bytes.size()) != 1) {
+ Metrics::Counter::increment(ssl_rsb.cert_compress_zstd_failure);
+ return 0;
+ }
+ memcpy(buf, entry->bytes.data(), entry->bytes.size());
+ CBB_did_write(out, entry->bytes.size());
+ Metrics::Counter::increment(ssl_rsb.cert_compress_zstd);
+ Metrics::Counter::increment(ssl_rsb.cert_compress_cache_hit);
+ return 1;
+ }
+ }
uint8_t *buf;
unsigned long buf_len = ZSTD_compressBound(in_len);
@@ -54,6 +71,13 @@ compression_func_zstd(SSL * /* ssl */, CBB *out, const
uint8_t *in, size_t in_le
} else {
CBB_did_write(out, ret);
Metrics::Counter::increment(ssl_rsb.cert_compress_zstd);
+
+ if (cache) {
+ auto *fresh = new CertCompressionCache::Entry();
+ fresh->bytes.assign(buf, buf + ret);
+ cert_compress_cache_try_publish(cache->slots[CERT_COMPRESS_ALG_ZSTD],
fresh);
+ }
+
return 1;
}
}
diff --git a/src/iocore/net/TLSCertCompression_zstd.h
b/src/iocore/net/TLSCertCompression_zstd.h
index bde6ef6d7b..440ac397cd 100644
--- a/src/iocore/net/TLSCertCompression_zstd.h
+++ b/src/iocore/net/TLSCertCompression_zstd.h
@@ -26,5 +26,8 @@
#include <openssl/ssl.h>
#include <openssl/bytestring.h>
+// RFC 8879 algorithm ID for zstd
+constexpr int CERT_COMPRESS_ALG_ZSTD = 3;
+
int compression_func_zstd(SSL *ssl, CBB *out, const uint8_t *in, size_t
in_len);
int decompression_func_zstd(SSL *ssl, CRYPTO_BUFFER **out, size_t
uncompressed_len, const uint8_t *in, size_t in_len);
diff --git a/src/iocore/net/unit_tests/benchmark_TLSCertCompression.cc
b/src/iocore/net/unit_tests/benchmark_TLSCertCompression.cc
new file mode 100644
index 0000000000..554d27e103
--- /dev/null
+++ b/src/iocore/net/unit_tests/benchmark_TLSCertCompression.cc
@@ -0,0 +1,317 @@
+/** @file
+
+ Microbenchmark for the TLS Certificate Compression cache.
+
+ Drives the production compression callbacks
+ (compression_func_zlib/_brotli/_zstd) and the cache attach/invalidate
+ helpers exported by inknet — no logic is duplicated here. The benchmark
+ measures the three states the cache toggles between, per algorithm:
+
+ - disabled : cache=false at registration; callback always compresses
+ - cold : cache attached but empty (just invalidated); callback
+ compresses and publishes a new Entry
+ - warm : cache attached and populated; callback takes the
+ acquire-load + memcpy fast path
+
+ Run only the benchmarks: ./test_net "[!benchmark]"
+
+ @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 "tscore/ink_config.h"
+
+#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
+
+#include "../TLSCertCompression.h"
+#include "../TLSCertCompression_zlib.h"
+#if HAVE_BROTLI_ENCODE_H
+#include "../TLSCertCompression_brotli.h"
+#endif
+#if HAVE_ZSTD_H
+#include "../TLSCertCompression_zstd.h"
+#endif
+#include "../SSLStats.h"
+
+#include <openssl/ssl.h>
+#include <openssl/bytestring.h>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/benchmark/catch_benchmark.hpp>
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <cstdio>
+#include <random>
+#include <thread>
+#include <vector>
+
+namespace
+{
+// Realistic size for a server leaf + 2 intermediates with RSA keys.
+constexpr size_t CERT_BLOB_SIZE = 3 * 1024;
+// Generous upper bound on compressed output; CBB will grow if needed.
+constexpr size_t CBB_INITIAL_CAPACITY = 8 * 1024;
+
+std::vector<uint8_t>
+make_cert_blob()
+{
+ // Mix of structured (DER-like repetition) and pseudo-random bytes so
+ // compression ratios are non-trivial. Fixed seed for reproducibility.
+ std::vector<uint8_t> blob(CERT_BLOB_SIZE);
+ std::mt19937 rng(0xC0FFEE);
+ for (size_t i = 0; i < blob.size(); ++i) {
+ blob[i] = (i % 4 == 0) ? static_cast<uint8_t>(i & 0xFF) :
static_cast<uint8_t>(rng() & 0xFF);
+ }
+ return blob;
+}
+
+struct CtxBundle {
+ SSL_CTX *ctx{nullptr};
+ SSL *ssl{nullptr};
+
+ CtxBundle(std::string const &alg, bool cache_enabled)
+ {
+ static bool stats_initialized = false;
+ if (!stats_initialized) {
+ SSLInitializeStatistics();
+ stats_initialized = true;
+ }
+ cert_compress_cache_init();
+ ctx = SSL_CTX_new(TLS_method());
+ REQUIRE(ctx != nullptr);
+ REQUIRE(register_certificate_compression_preference(ctx, {alg},
cache_enabled) == 1);
+ ssl = SSL_new(ctx);
+ REQUIRE(ssl != nullptr);
+ }
+ ~CtxBundle()
+ {
+ if (ssl) {
+ SSL_free(ssl);
+ }
+ if (ctx) {
+ SSL_CTX_free(ctx);
+ }
+ }
+ CtxBundle(CtxBundle const &) = delete;
+ CtxBundle &operator=(CtxBundle const &) = delete;
+};
+
+// Single-shot compress through the production callback, into a fresh CBB.
+// Returns the compressed length.
+template <typename Fn>
+size_t
+run_callback(Fn fn, SSL *ssl, std::vector<uint8_t> const &input)
+{
+ CBB cbb;
+ REQUIRE(CBB_init(&cbb, CBB_INITIAL_CAPACITY) == 1);
+ int rv = fn(ssl, &cbb, input.data(), input.size());
+ REQUIRE(rv == 1);
+ size_t out_len = CBB_len(&cbb);
+ CBB_cleanup(&cbb);
+ return out_len;
+}
+
+// Saturate N threads on the warm path for a fixed duration and return total
ops.
+// Each thread gets its own SSL handle off the shared SSL_CTX so the SSL object
+// itself is not a contention point — only the cache's shared atomics are.
+template <typename Fn>
+uint64_t
+warm_throughput(Fn fn, SSL_CTX *ctx, std::vector<uint8_t> const &input,
unsigned n_threads, std::chrono::milliseconds duration)
+{
+ std::atomic<bool> go{false};
+ std::atomic<bool> stop{false};
+ std::vector<uint64_t> counts(n_threads, 0);
+ std::vector<std::thread> workers;
+ workers.reserve(n_threads);
+ for (unsigned t = 0; t < n_threads; ++t) {
+ workers.emplace_back([&, t] {
+ SSL *ssl = SSL_new(ctx);
+ while (!go.load(std::memory_order_acquire)) {}
+ uint64_t local = 0;
+ while (!stop.load(std::memory_order_relaxed)) {
+ (void)run_callback(fn, ssl, input);
+ ++local;
+ }
+ counts[t] = local;
+ SSL_free(ssl);
+ });
+ }
+ go.store(true, std::memory_order_release);
+ std::this_thread::sleep_for(duration);
+ stop.store(true, std::memory_order_relaxed);
+ for (auto &w : workers) {
+ w.join();
+ }
+ uint64_t total = 0;
+ for (auto c : counts) {
+ total += c;
+ }
+ return total;
+}
+} // namespace
+
+TEST_CASE("Cert compression cache: zlib", "[!benchmark][cert_compress][zlib]")
+{
+ const auto input = make_cert_blob();
+
+ SECTION("disabled")
+ {
+ CtxBundle b("zlib", /*cache=*/false);
+ BENCHMARK("zlib disabled")
+ {
+ return run_callback(compression_func_zlib, b.ssl, input);
+ };
+ }
+ SECTION("cold (invalidate per iteration)")
+ {
+ CtxBundle b("zlib", /*cache=*/true);
+ BENCHMARK("zlib cold")
+ {
+ cert_compress_cache_invalidate(b.ctx);
+ return run_callback(compression_func_zlib, b.ssl, input);
+ };
+ }
+ SECTION("warm")
+ {
+ CtxBundle b("zlib", /*cache=*/true);
+ // Prime the cache.
+ (void)run_callback(compression_func_zlib, b.ssl, input);
+ BENCHMARK("zlib warm")
+ {
+ return run_callback(compression_func_zlib, b.ssl, input);
+ };
+ }
+}
+
+#if HAVE_BROTLI_ENCODE_H
+TEST_CASE("Cert compression cache: brotli",
"[!benchmark][cert_compress][brotli]")
+{
+ const auto input = make_cert_blob();
+
+ SECTION("disabled")
+ {
+ CtxBundle b("brotli", /*cache=*/false);
+ BENCHMARK("brotli disabled")
+ {
+ return run_callback(compression_func_brotli, b.ssl, input);
+ };
+ }
+ SECTION("cold (invalidate per iteration)")
+ {
+ CtxBundle b("brotli", /*cache=*/true);
+ BENCHMARK("brotli cold")
+ {
+ cert_compress_cache_invalidate(b.ctx);
+ return run_callback(compression_func_brotli, b.ssl, input);
+ };
+ }
+ SECTION("warm")
+ {
+ CtxBundle b("brotli", /*cache=*/true);
+ (void)run_callback(compression_func_brotli, b.ssl, input);
+ BENCHMARK("brotli warm")
+ {
+ return run_callback(compression_func_brotli, b.ssl, input);
+ };
+ }
+}
+#endif
+
+#if HAVE_ZSTD_H
+TEST_CASE("Cert compression cache: zstd", "[!benchmark][cert_compress][zstd]")
+{
+ const auto input = make_cert_blob();
+
+ SECTION("disabled")
+ {
+ CtxBundle b("zstd", /*cache=*/false);
+ BENCHMARK("zstd disabled")
+ {
+ return run_callback(compression_func_zstd, b.ssl, input);
+ };
+ }
+ SECTION("cold (invalidate per iteration)")
+ {
+ CtxBundle b("zstd", /*cache=*/true);
+ BENCHMARK("zstd cold")
+ {
+ cert_compress_cache_invalidate(b.ctx);
+ return run_callback(compression_func_zstd, b.ssl, input);
+ };
+ }
+ SECTION("warm")
+ {
+ CtxBundle b("zstd", /*cache=*/true);
+ (void)run_callback(compression_func_zstd, b.ssl, input);
+ BENCHMARK("zstd warm")
+ {
+ return run_callback(compression_func_zstd, b.ssl, input);
+ };
+ }
+}
+#endif
+
+// Scaling test: N threads hammer the warm fast path against the same
+// SSL_CTX. Prints aggregate ops/sec and per-thread ops/sec so reader
+// contention on the cache's shared atomics is directly visible.
+TEST_CASE("Cert compression cache: warm scaling",
"[!benchmark][cert_compress][scaling]")
+{
+ using namespace std::chrono_literals;
+ const auto input = make_cert_blob();
+ const auto duration = 1000ms;
+ const unsigned hw = std::max(1u,
std::thread::hardware_concurrency());
+ std::vector<unsigned> thread_counts;
+ for (unsigned n : {1u, 2u, 4u, 8u, 16u, 32u}) {
+ if (n <= hw * 2) {
+ thread_counts.push_back(n);
+ }
+ }
+
+ auto run = [&](char const *label, auto fn, char const *alg) {
+ CtxBundle b(alg, /*cache=*/true);
+ (void)run_callback(fn, b.ssl, input); // prime
+ std::printf("\n[%s scaling] (hw_concurrency=%u, duration=%lldms)\n",
label, hw, static_cast<long long>(duration.count()));
+ std::printf(" threads | ops | ops/sec | ops/sec/thread |
scaling\n");
+ std::printf("
--------+--------------+----------------+----------------+--------\n");
+ double baseline = 0.0;
+ for (unsigned n : thread_counts) {
+ uint64_t ops = warm_throughput(fn, b.ctx, input, n, duration);
+ double rate = static_cast<double>(ops) * 1000.0 / duration.count();
+ double per_th = rate / n;
+ if (n == 1) {
+ baseline = per_th;
+ }
+ double scaling = baseline > 0 ? rate / (baseline * n) : 0.0;
+ std::printf(" %7u | %12llu | %14.0f | %14.0f | %6.2fx (vs ideal Nx)\n",
n, static_cast<unsigned long long>(ops), rate,
+ per_th, scaling);
+ }
+ };
+
+ run("zlib", compression_func_zlib, "zlib");
+#if HAVE_BROTLI_ENCODE_H
+ run("brotli", compression_func_brotli, "brotli");
+#endif
+#if HAVE_ZSTD_H
+ run("zstd", compression_func_zstd, "zstd");
+#endif
+}
+
+#endif // HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index 6fa2c18bc4..7588e5108d 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -1249,6 +1249,8 @@ static constexpr RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.ssl.server.cert_compression.algorithms",
RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.ssl.server.cert_compression.cache", RECD_INT,
"1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
+ ,
{RECT_CONFIG, "proxy.config.ssl.client.cert_compression.algorithms",
RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
//##############################################################################
diff --git a/tests/gold_tests/tls/replay/tls_cert_compression_cache.replay.yaml
b/tests/gold_tests/tls/replay/tls_cert_compression_cache.replay.yaml
new file mode 100644
index 0000000000..b4b58b793e
--- /dev/null
+++ b/tests/gold_tests/tls/replay/tls_cert_compression_cache.replay.yaml
@@ -0,0 +1,59 @@
+# 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.
+
+meta:
+ version: "1.0"
+
+sessions:
+- transactions:
+ - client-request:
+ method: GET
+ url: /cert-compression-cache-1
+ version: '1.1'
+ headers:
+ fields:
+ - [Host, example.com]
+ - [uuid, cert-compression-cache-1]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [Content-Length, "0"]
+
+ proxy-response:
+ status: 200
+
+- transactions:
+ - client-request:
+ method: GET
+ url: /cert-compression-cache-2
+ version: '1.1'
+ headers:
+ fields:
+ - [Host, example.com]
+ - [uuid, cert-compression-cache-2]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [Content-Length, "0"]
+
+ proxy-response:
+ status: 200
diff --git a/tests/gold_tests/tls/tls_cert_comp.test.py
b/tests/gold_tests/tls/tls_cert_comp.test.py
index 0bb17e9095..030028d3d3 100644
--- a/tests/gold_tests/tls/tls_cert_comp.test.py
+++ b/tests/gold_tests/tls/tls_cert_comp.test.py
@@ -68,6 +68,7 @@ ssl_multicert:
'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir,
'proxy.config.ssl.server.private_key.path':
ts.Variables.SSLDir,
'proxy.config.ssl.server.cert_compression.algorithms':
self._algorithm,
+ 'proxy.config.ssl.server.cert_compression.cache': 0,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'ssl_cert_compress',
})
diff --git a/tests/gold_tests/tls/tls_cert_comp.test.py
b/tests/gold_tests/tls/tls_cert_comp_cache.test.py
similarity index 59%
copy from tests/gold_tests/tls/tls_cert_comp.test.py
copy to tests/gold_tests/tls/tls_cert_comp_cache.test.py
index 0bb17e9095..4d96385e8d 100644
--- a/tests/gold_tests/tls/tls_cert_comp.test.py
+++ b/tests/gold_tests/tls/tls_cert_comp_cache.test.py
@@ -1,5 +1,5 @@
'''
-Verify TLS Certificate Compression (RFC 8879) between two ATS processes.
+Verify TLS Certificate Compression cache behavior.
'''
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -18,38 +18,38 @@ Verify TLS Certificate Compression (RFC 8879) between two
ATS processes.
# limitations under the License.
Test.Summary = '''
-Verify TLS Certificate Compression (RFC 8879) works between two ATS
-instances. An edge ATS (client) connects via HTTPS to a mid ATS (server)
-with cert compression enabled. The test verifies compression and
-decompression succeed by checking the ssl cert compression metrics.
+Verify that the certificate compression cache works correctly. When caching
+is enabled, the compressed result is reused across handshakes. When disabled,
+each handshake compresses independently and the cache_hit metric stays at 0.
'''
Test.SkipUnless(Condition.HasATSFeature('TS_HAS_CERT_COMPRESSION_CALLBACKS'))
-REPLAY_FILE = 'replay/tls_cert_compression.replay.yaml'
+REPLAY_FILE = 'replay/tls_cert_compression_cache.replay.yaml'
-class TestCertCompression:
+class TestCertCompressionCache:
server_counter: int = 0
ts_counter: int = 0
client_counter: int = 0
- def __init__(self, algorithm: str) -> None:
- self._algorithm = algorithm
+ def __init__(self, cache_enabled: bool) -> None:
+ self._cache_enabled = cache_enabled
+ self._algorithm = 'zlib'
self._server = self._configure_server()
self._ts_mid = self._configure_ts_mid()
self._ts_edge = self._configure_ts_edge()
def _configure_server(self) -> 'Process':
- name = f'server-{TestCertCompression.server_counter}'
- TestCertCompression.server_counter += 1
+ name = f'server-{TestCertCompressionCache.server_counter}'
+ TestCertCompressionCache.server_counter += 1
server = Test.MakeVerifierServerProcess(name, REPLAY_FILE)
return server
def _configure_ts_mid(self) -> 'Process':
"""Mid-tier ATS that terminates TLS and forwards to origin."""
- name = f'm{TestCertCompression.ts_counter}'
- TestCertCompression.ts_counter += 1
+ name = f'm{TestCertCompressionCache.ts_counter}'
+ TestCertCompressionCache.ts_counter += 1
ts = Test.MakeATSProcess(name, enable_tls=True, enable_cache=False)
ts.addDefaultSSLFiles()
@@ -68,6 +68,8 @@ ssl_multicert:
'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir,
'proxy.config.ssl.server.private_key.path':
ts.Variables.SSLDir,
'proxy.config.ssl.server.cert_compression.algorithms':
self._algorithm,
+ 'proxy.config.ssl.server.cert_compression.cache': 1 if
self._cache_enabled else 0,
+ 'proxy.config.ssl.server.session_ticket.enable': 0,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'ssl_cert_compress',
})
@@ -76,8 +78,8 @@ ssl_multicert:
def _configure_ts_edge(self) -> 'Process':
"""Edge ATS that connects to mid-tier via HTTPS."""
- name = f'e{TestCertCompression.ts_counter}'
- TestCertCompression.ts_counter += 1
+ name = f'e{TestCertCompressionCache.ts_counter}'
+ TestCertCompressionCache.ts_counter += 1
ts = Test.MakeATSProcess(name, enable_tls=True, enable_cache=False)
ts.addDefaultSSLFiles()
@@ -97,6 +99,7 @@ ssl_multicert:
'proxy.config.ssl.server.private_key.path':
ts.Variables.SSLDir,
'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
'proxy.config.ssl.client.cert_compression.algorithms':
self._algorithm,
+ 'proxy.config.http.keep_alive_enabled_out': 0,
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'ssl_cert_compress',
})
@@ -104,72 +107,77 @@ ssl_multicert:
return ts
def run(self) -> None:
- # Test run 1: Send traffic through the proxy chain.
- tr = Test.AddTestRun(f'Send request through edge->mid with
{self._algorithm} cert compression')
+ cache_label = 'enabled' if self._cache_enabled else 'disabled'
+
+ # Test run 1: Send 2 requests over 2 separate TLS connections.
+ tr = Test.AddTestRun(f'Send 2 requests with cache {cache_label}')
tr.Processes.Default.StartBefore(self._server)
tr.Processes.Default.StartBefore(self._ts_mid)
tr.Processes.Default.StartBefore(self._ts_edge)
- name = f'client-{TestCertCompression.client_counter}'
- TestCertCompression.client_counter += 1
+ name = f'client-{TestCertCompressionCache.client_counter}'
+ TestCertCompressionCache.client_counter += 1
tr.AddVerifierClientProcess(name, REPLAY_FILE,
http_ports=[self._ts_edge.Variables.port])
- # Test run 2: Check compression metric on the mid-tier (server side).
- tr = Test.AddTestRun(f'Verify {self._algorithm} compression metric on
mid-tier')
+ # Test run 2: Check compression count on mid-tier — should be 2.
+ tr = Test.AddTestRun(f'Verify compression count with cache
{cache_label}')
tr.Processes.Default.Command = (f'traffic_ctl metric get'
f'
proxy.process.ssl.cert_compress.{self._algorithm}')
tr.Processes.Default.Env = self._ts_mid.Env
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.All = Testers.ContainsExpression(
- f'proxy.process.ssl.cert_compress.{self._algorithm} 1',
- f'Certificate should have been compressed with {self._algorithm}')
+ f'proxy.process.ssl.cert_compress.{self._algorithm} 2', f'Should
have 2 {self._algorithm} compressions')
tr.StillRunningAfter = self._ts_mid
tr.StillRunningAfter = self._ts_edge
tr.StillRunningAfter = self._server
- # Test run 3: Check decompression metric on the edge (client side).
- tr = Test.AddTestRun(f'Verify {self._algorithm} decompression metric
on edge')
+ # Test run 3: Check decompression count on edge — should be 2.
+ tr = Test.AddTestRun(f'Verify decompression count with cache
{cache_label}')
tr.Processes.Default.Command = (f'traffic_ctl metric get'
f'
proxy.process.ssl.cert_decompress.{self._algorithm}')
tr.Processes.Default.Env = self._ts_edge.Env
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.All = Testers.ContainsExpression(
- f'proxy.process.ssl.cert_decompress.{self._algorithm} 1',
- f'Certificate should have been decompressed with
{self._algorithm}')
+ f'proxy.process.ssl.cert_decompress.{self._algorithm} 2', f'Should
have 2 {self._algorithm} decompressions')
tr.StillRunningAfter = self._ts_mid
tr.StillRunningAfter = self._ts_edge
tr.StillRunningAfter = self._server
- # Test run 4: Verify no failures on either side.
- tr = Test.AddTestRun(f'Verify no {self._algorithm} compression
failures on mid-tier')
+ # Test run 4: Verify no compression failures.
+ tr = Test.AddTestRun(f'Verify no compression failures with cache
{cache_label}')
tr.Processes.Default.Command = (f'traffic_ctl metric get'
f'
proxy.process.ssl.cert_compress.{self._algorithm}_failure')
tr.Processes.Default.Env = self._ts_mid.Env
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.All = Testers.ContainsExpression(
f'proxy.process.ssl.cert_compress.{self._algorithm}_failure 0',
- f'There should be no {self._algorithm} compression failures')
- tr.StillRunningAfter = self._ts_mid
- tr.StillRunningAfter = self._ts_edge
- tr.StillRunningAfter = self._server
-
- tr = Test.AddTestRun(f'Verify no {self._algorithm} decompression
failures on edge')
- tr.Processes.Default.Command = (f'traffic_ctl metric get'
- f'
proxy.process.ssl.cert_decompress.{self._algorithm}_failure')
- tr.Processes.Default.Env = self._ts_edge.Env
- tr.Processes.Default.ReturnCode = 0
- tr.Processes.Default.Streams.All = Testers.ContainsExpression(
- f'proxy.process.ssl.cert_decompress.{self._algorithm}_failure 0',
- f'There should be no {self._algorithm} decompression failures')
+ f'Should have no {self._algorithm} compression failures')
tr.StillRunningAfter = self._ts_mid
tr.StillRunningAfter = self._ts_edge
tr.StillRunningAfter = self._server
-
-algorithms = ['zlib']
-if Condition.HasATSFeature('TS_HAS_BROTLI'):
- algorithms.append('brotli')
-if Condition.HasATSFeature('TS_HAS_ZSTD'):
- algorithms.append('zstd')
-for algorithm in algorithms:
- TestCertCompression(algorithm).run()
+ # Test run 5: Check cache_hit metric.
+ if self._cache_enabled:
+ tr = Test.AddTestRun(f'Verify cache_hit is 1 with cache
{cache_label}')
+ tr.Processes.Default.Command = 'traffic_ctl metric get
proxy.process.ssl.cert_compress.cache_hit'
+ tr.Processes.Default.Env = self._ts_mid.Env
+ tr.Processes.Default.ReturnCode = 0
+ tr.Processes.Default.Streams.All = Testers.ContainsExpression(
+ 'proxy.process.ssl.cert_compress.cache_hit 1', 'cache_hit
should be 1 when caching is enabled (1 miss + 1 hit)')
+ tr.StillRunningAfter = self._ts_mid
+ tr.StillRunningAfter = self._ts_edge
+ tr.StillRunningAfter = self._server
+ else:
+ tr = Test.AddTestRun(f'Verify cache_hit is 0 with cache
{cache_label}')
+ tr.Processes.Default.Command = 'traffic_ctl metric get
proxy.process.ssl.cert_compress.cache_hit'
+ tr.Processes.Default.Env = self._ts_mid.Env
+ tr.Processes.Default.ReturnCode = 0
+ tr.Processes.Default.Streams.All = Testers.ContainsExpression(
+ 'proxy.process.ssl.cert_compress.cache_hit 0', 'cache_hit
should be 0 when caching is disabled')
+ tr.StillRunningAfter = self._ts_mid
+ tr.StillRunningAfter = self._ts_edge
+ tr.StillRunningAfter = self._server
+
+
+TestCertCompressionCache(cache_enabled=True).run()
+TestCertCompressionCache(cache_enabled=False).run()