diff --git a/modular/nginx-cp/284a0c73771e3a2c57af6e74d96d9a6878b2e7b4.patch b/modular/nginx-cp/284a0c73771e3a2c57af6e74d96d9a6878b2e7b4.patch deleted file mode 100644 index b2b11c9..0000000 --- a/modular/nginx-cp/284a0c73771e3a2c57af6e74d96d9a6878b2e7b4.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 284a0c73771e3a2c57af6e74d96d9a6878b2e7b4 Mon Sep 17 00:00:00 2001 -From: Maxim Dounin -Date: Tue, 17 Oct 2023 02:39:38 +0300 -Subject: [PATCH] Core: fixed memory leak on configuration reload with PCRE2. - -In ngx_regex_cleanup() allocator wasn't configured when calling -pcre2_compile_context_free() and pcre2_match_data_free(), resulting -in no ngx_free() call and leaked memory. Fix is ensure that allocator -is configured for global allocations, so that ngx_free() is actually -called to free memory. - -Additionally, ngx_regex_compile_context was cleared in -ngx_regex_module_init(). It should be either not cleared, so it will -be freed by ngx_regex_cleanup(), or properly freed. Fix is to -not clear it, so ngx_regex_cleanup() will be able to free it. - -Reported by ZhenZhong Wu, -https://mailman.nginx.org/pipermail/nginx-devel/2023-September/3Z5FIKUDRN2WBSL3JWTZJ7SXDA6YIWPB.html ---- - src/core/ngx_regex.c | 7 ++++--- - 1 file changed, 4 insertions(+), 3 deletions(-) - -diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c -index 91381f4994..5b13c5db38 100644 ---- a/src/core/ngx_regex.c -+++ b/src/core/ngx_regex.c -@@ -600,6 +600,8 @@ ngx_regex_cleanup(void *data) - * the new cycle, these will be re-allocated. - */ - -+ ngx_regex_malloc_init(NULL); -+ - if (ngx_regex_compile_context) { - pcre2_compile_context_free(ngx_regex_compile_context); - ngx_regex_compile_context = NULL; -@@ -611,6 +613,8 @@ ngx_regex_cleanup(void *data) - ngx_regex_match_data_size = 0; - } - -+ ngx_regex_malloc_done(); -+ - #endif - } - -@@ -706,9 +710,6 @@ ngx_regex_module_init(ngx_cycle_t *cycle) - ngx_regex_malloc_done(); - - ngx_regex_studies = NULL; --#if (NGX_PCRE2) -- ngx_regex_compile_context = NULL; --#endif - - return NGX_OK; - } diff --git a/modular/nginx-cp/CVE-2023-44487.patch b/modular/nginx-cp/CVE-2023-44487.patch deleted file mode 100644 index 3e73d8e..0000000 --- a/modular/nginx-cp/CVE-2023-44487.patch +++ /dev/null @@ -1,70 +0,0 @@ - -# HG changeset patch -# User Maxim Dounin -# Date 1696940019 -10800 -# Node ID cdda286c0f1b4b10f30d4eb6a63fefb9b8708ecc -# Parent 3db945fda515014d220151046d02f3960bcfca0a -HTTP/2: per-iteration stream handling limit. - -To ensure that attempts to flood servers with many streams are detected -early, a limit of no more than 2 * max_concurrent_streams new streams per one -event loop iteration was introduced. This limit is applied even if -max_concurrent_streams is not yet reached - for example, if corresponding -streams are handled synchronously or reset. - -Further, refused streams are now limited to maximum of max_concurrent_streams -and 100, similarly to priority_limit initial value, providing some tolerance -to clients trying to open several streams at the connection start, yet -low tolerance to flooding attempts. - -diff -r 3db945fda515 -r cdda286c0f1b src/http/v2/ngx_http_v2.c ---- a/src/http/v2/ngx_http_v2.c Fri Sep 22 19:23:57 2023 +0400 -+++ b/src/http/v2/ngx_http_v2.c Tue Oct 10 15:13:39 2023 +0300 -@@ -347,6 +347,7 @@ - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler"); - - h2c->blocked = 1; -+ h2c->new_streams = 0; - - if (c->close) { - c->close = 0; -@@ -1284,6 +1285,14 @@ - goto rst_stream; - } - -+ if (h2c->new_streams++ >= 2 * h2scf->concurrent_streams) { -+ ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, -+ "client sent too many streams at once"); -+ -+ status = NGX_HTTP_V2_REFUSED_STREAM; -+ goto rst_stream; -+ } -+ - if (!h2c->settings_ack - && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) - && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) -@@ -1349,6 +1358,12 @@ - - rst_stream: - -+ if (h2c->refused_streams++ > ngx_max(h2scf->concurrent_streams, 100)) { -+ ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, -+ "client sent too many refused streams"); -+ return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_NO_ERROR); -+ } -+ - if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, status) != NGX_OK) { - return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); - } -diff -r 3db945fda515 -r cdda286c0f1b src/http/v2/ngx_http_v2.h ---- a/src/http/v2/ngx_http_v2.h Fri Sep 22 19:23:57 2023 +0400 -+++ b/src/http/v2/ngx_http_v2.h Tue Oct 10 15:13:39 2023 +0300 -@@ -172,6 +172,8 @@ - ngx_uint_t processing; - ngx_uint_t frames; - ngx_uint_t idle; -+ ngx_uint_t new_streams; -+ ngx_uint_t refused_streams; - ngx_uint_t priority_limit; - - ngx_uint_t pushing; diff --git a/modular/nginx-cp/c93cb45ae30760b7cd4ce2d9e053a36449d4e233.patch b/modular/nginx-cp/c93cb45ae30760b7cd4ce2d9e053a36449d4e233.patch deleted file mode 100644 index 5803e0f..0000000 --- a/modular/nginx-cp/c93cb45ae30760b7cd4ce2d9e053a36449d4e233.patch +++ /dev/null @@ -1,119 +0,0 @@ -From c93cb45ae30760b7cd4ce2d9e053a36449d4e233 Mon Sep 17 00:00:00 2001 -From: Maxim Dounin -Date: Wed, 18 Oct 2023 04:30:11 +0300 -Subject: [PATCH] Core: changed ngx_queue_sort() to use merge sort. - -This improves nginx startup times significantly when using very large number -of locations due to computational complexity of the sorting algorithm being -used: insertion sort is O(n*n) on average, while merge sort is O(n*log(n)). -In particular, in a test configuration with 20k locations total startup -time is reduced from 8 seconds to 0.9 seconds. - -Prodded by Yusuke Nojima, -https://mailman.nginx.org/pipermail/nginx-devel/2023-September/NUL3Y2FPPFSHMPTFTL65KXSXNTX3NQMK.html ---- - src/core/ngx_queue.c | 52 +++++++++++++++++++++++++++++++++----------- - src/core/ngx_queue.h | 3 +++ - 2 files changed, 42 insertions(+), 13 deletions(-) - -diff --git a/src/core/ngx_queue.c b/src/core/ngx_queue.c -index 3cacaf3a88..3d1d589884 100644 ---- a/src/core/ngx_queue.c -+++ b/src/core/ngx_queue.c -@@ -9,6 +9,10 @@ - #include - - -+static void ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, -+ ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)); -+ -+ - /* - * find the middle queue element if the queue has odd number of elements - * or the first element of the queue's second part otherwise -@@ -45,13 +49,13 @@ ngx_queue_middle(ngx_queue_t *queue) - } - - --/* the stable insertion sort */ -+/* the stable merge sort */ - - void - ngx_queue_sort(ngx_queue_t *queue, - ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) - { -- ngx_queue_t *q, *prev, *next; -+ ngx_queue_t *q, tail; - - q = ngx_queue_head(queue); - -@@ -59,22 +63,44 @@ ngx_queue_sort(ngx_queue_t *queue, - return; - } - -- for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) { -+ q = ngx_queue_middle(queue); -+ -+ ngx_queue_split(queue, q, &tail); -+ -+ ngx_queue_sort(queue, cmp); -+ ngx_queue_sort(&tail, cmp); -+ -+ ngx_queue_merge(queue, &tail, cmp); -+} -+ - -- prev = ngx_queue_prev(q); -- next = ngx_queue_next(q); -+static void -+ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, -+ ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) -+{ -+ ngx_queue_t *q1, *q2; - -- ngx_queue_remove(q); -+ q1 = ngx_queue_head(queue); -+ q2 = ngx_queue_head(tail); - -- do { -- if (cmp(prev, q) <= 0) { -- break; -- } -+ for ( ;; ) { -+ if (q1 == ngx_queue_sentinel(queue)) { -+ ngx_queue_add(queue, tail); -+ break; -+ } - -- prev = ngx_queue_prev(prev); -+ if (q2 == ngx_queue_sentinel(tail)) { -+ break; -+ } -+ -+ if (cmp(q1, q2) <= 0) { -+ q1 = ngx_queue_next(q1); -+ continue; -+ } - -- } while (prev != ngx_queue_sentinel(queue)); -+ ngx_queue_remove(q2); -+ ngx_queue_insert_before(q1, q2); - -- ngx_queue_insert_after(prev, q); -+ q2 = ngx_queue_head(tail); - } - } -diff --git a/src/core/ngx_queue.h b/src/core/ngx_queue.h -index 038bf12113..0f82f173e4 100644 ---- a/src/core/ngx_queue.h -+++ b/src/core/ngx_queue.h -@@ -47,6 +47,9 @@ struct ngx_queue_s { - (h)->prev = x - - -+#define ngx_queue_insert_before ngx_queue_insert_tail -+ -+ - #define ngx_queue_head(h) \ - (h)->next - diff --git a/modular/nginx-cp/nginx-cp.spec b/modular/nginx-cp/nginx-cp.spec index 0afbb7e..a2a30d1 100644 --- a/modular/nginx-cp/nginx-cp.spec +++ b/modular/nginx-cp/nginx-cp.spec @@ -18,6 +18,7 @@ %bcond_without brotli %bcond_without fancyindex %bcond_without vts +%bcond_with http3 %if 0%{?rhel} >= 8 %bcond_with gperftools @@ -42,8 +43,8 @@ Name: nginx-cp Epoch: 1 -Version: 1.24.0 -Release: 5%{?dist} +Version: 1.26.0 +Release: 1%{?dist} Summary: A high performance web server and reverse proxy server Group: System Environment/Daemons @@ -97,8 +98,8 @@ Patch0: nginx-auto-cc-gcc.patch Patch1: 0002-fix-PIDFile-handling.patch %if %{with certmin_patches} -# 2 in 1 patch -Patch3: nginx__cloudflare_1.19+.patch +# Dynamic TLS records patch +Patch3: nginx_dynamic_tls_records.patch %endif %if %{with brotli} @@ -109,11 +110,6 @@ Patch4: ngx_brotli-config.patch Patch5: nginx-1.24-lua-mod-lowering-luajit-alert-severity.patch %endif -Patch6: CVE-2023-44487.patch - -Patch7: c93cb45ae30760b7cd4ce2d9e053a36449d4e233.patch -Patch8: 284a0c73771e3a2c57af6e74d96d9a6878b2e7b4.patch - BuildRequires: make binutils BuildRequires: gcc >= 4.8 @@ -121,11 +117,12 @@ BuildRequires: gcc >= 4.8 %if %{with gperftools} BuildRequires: gperftools-devel %endif -%if 0%{?rhel} <= 8 -BuildRequires: openssl3-devel >= %{openssl_ver} -Requires: openssl3-libs >= %{openssl_ver} + +%if %{with http3} +BuildRequires: pkgconfig(openssl) >= 3.8 %else -BuildRequires: openssl-devel +BuildRequires: pkgconfig(openssl) >= %{openssl_ver} +BuildRequires: pkgconfig(openssl) < 3.5 %endif BuildRequires: pcre2-devel @@ -238,7 +235,7 @@ modules and features: - ngx_http_geoip2_module - ngx_http_fancyindex_module - ngx_http_lua_module -includes dynamic-ssl-record-size, http2_hpack patches from certminmod +includes dynamic-ssl-record-size patch from certminmod TFO support enabled @@ -288,12 +285,6 @@ tar -xf %{SOURCE304} %patch5 -p1 -b .luajit %endif -%patch6 -p1 -b .CVE-2023-44487 -%patch7 -p1 -b .ngx_queue_sort -%if %{without lua} -%patch8 -p1 -b .pcre2-memleak -%endif - tar -xf %{SOURCE305} %if %{with fancyindex} @@ -327,12 +318,6 @@ sed -i '1 i\LUAJIT_LIB=%{_libdir}' lua-nginx-module-%{luamod_ver}/config %endif %build -# %if 0%{?rhel} <= 7 -# # Build with gcc8 -# %enable_devtoolset8 -# %endif - - # nginx does not utilize a standard configure script. It has its own # and the standard configure options cause the nginx configure script # to error out. This is is also the reason for the DESTDIR environment @@ -341,9 +326,13 @@ sed -i '1 i\LUAJIT_LIB=%{_libdir}' lua-nginx-module-%{luamod_ver}/config ngx_ldflags="$RPM_LD_FLAGS -Wl,-E" ngx_cflags="%{optflags} $(pcre2-config --cflags) -DTCP_FASTOPEN=23 -fPIC" +%if %{with http3} +ngx_cflags="-I%{ssl_prefix}/include/libressl $ngx_cflags" +%else %if 0%{?rhel} <= 8 ngx_cflags="-I%{ssl_prefix}/include/openssl3 $ngx_cflags" %endif +%endif %if %{with lua} ngx_cflags="-DNGX_LUA_ABORT_AT_PANIC $ngx_cflags" @@ -377,8 +366,8 @@ export DESTDIR=%{buildroot} --with-threads \ --with-http_ssl_module \ --with-http_v2_module \ -%if %{with certmin_patches} - --with-http_v2_hpack_enc \ +%if %{with http3} + --with-http_v3_module \ %endif --with-http_realip_module \ --with-http_addition_module \ @@ -681,6 +670,11 @@ fi %endif %changelog +* Wed Apr 24 2024 Raven - 1.26.0-1 +- new stable version +- get rid of http2 HPACK +- add http3 build ability + * Mon Apr 8 2024 Raven - 1.24.0-5 - upgrade lua-nginx-module to 0.10.26 diff --git a/modular/nginx-cp/nginx__cloudflare_1.19+.patch b/modular/nginx-cp/nginx__cloudflare_1.19+.patch deleted file mode 100644 index 080ec65..0000000 --- a/modular/nginx-cp/nginx__cloudflare_1.19+.patch +++ /dev/null @@ -1,1308 +0,0 @@ -Add HTTP2 HPACK Encoding Support. -Add Dynamic TLS Record support. - -Using: patch -p1 < nginx.patch - -diff -uNr a/auto/modules b/auto/modules ---- a/auto/modules 2020-03-03 23:04:21.000000000 +0800 -+++ b/auto/modules 2020-03-10 15:47:10.738727477 +0800 -@@ -423,6 +423,10 @@ - . auto/module - fi - -+ if [ $HTTP_V2_HPACK_ENC = YES ]; then -+ have=NGX_HTTP_V2_HPACK_ENC . auto/have -+ fi -+ - if :; then - ngx_module_name=ngx_http_static_module - ngx_module_incs= -diff -uNr a/auto/options b/auto/options ---- a/auto/options 2020-03-03 23:04:21.000000000 +0800 -+++ b/auto/options 2020-03-10 15:47:10.739727485 +0800 -@@ -59,6 +59,7 @@ - HTTP_GZIP=YES - HTTP_SSL=NO - HTTP_V2=NO -+HTTP_V2_HPACK_ENC=NO - HTTP_SSI=YES - HTTP_REALIP=NO - HTTP_XSLT=NO -@@ -224,6 +225,7 @@ - - --with-http_ssl_module) HTTP_SSL=YES ;; - --with-http_v2_module) HTTP_V2=YES ;; -+ --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; - --with-http_realip_module) HTTP_REALIP=YES ;; - --with-http_addition_module) HTTP_ADDITION=YES ;; - --with-http_xslt_module) HTTP_XSLT=YES ;; -@@ -439,6 +441,7 @@ - - --with-http_ssl_module enable ngx_http_ssl_module - --with-http_v2_module enable ngx_http_v2_module -+ --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc - --with-http_realip_module enable ngx_http_realip_module - --with-http_addition_module enable ngx_http_addition_module - --with-http_xslt_module enable ngx_http_xslt_module -diff -uNr a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c ---- a/src/core/ngx_murmurhash.c 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/core/ngx_murmurhash.c 2020-03-10 15:47:10.739727485 +0800 -@@ -50,3 +50,63 @@ - - return h; - } -+ -+ -+uint64_t -+ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed) -+{ -+ uint64_t h, k; -+ -+ h = seed ^ len; -+ -+ while (len >= 8) { -+ k = data[0]; -+ k |= data[1] << 8; -+ k |= data[2] << 16; -+ k |= data[3] << 24; -+ k |= (uint64_t)data[4] << 32; -+ k |= (uint64_t)data[5] << 40; -+ k |= (uint64_t)data[6] << 48; -+ k |= (uint64_t)data[7] << 56; -+ -+ k *= 0xc6a4a7935bd1e995ull; -+ k ^= k >> 47; -+ k *= 0xc6a4a7935bd1e995ull; -+ -+ h ^= k; -+ h *= 0xc6a4a7935bd1e995ull; -+ -+ data += 8; -+ len -= 8; -+ } -+ -+ switch (len) { -+ case 7: -+ h ^= (uint64_t)data[6] << 48; -+ /* fall through */ -+ case 6: -+ h ^= (uint64_t)data[5] << 40; -+ /* fall through */ -+ case 5: -+ h ^= (uint64_t)data[4] << 32; -+ /* fall through */ -+ case 4: -+ h ^= data[3] << 24; -+ /* fall through */ -+ case 3: -+ h ^= data[2] << 16; -+ /* fall through */ -+ case 2: -+ h ^= data[1] << 8; -+ /* fall through */ -+ case 1: -+ h ^= data[0]; -+ h *= 0xc6a4a7935bd1e995ull; -+ } -+ -+ h ^= h >> 47; -+ h *= 0xc6a4a7935bd1e995ull; -+ h ^= h >> 47; -+ -+ return h; -+} -diff -uNr a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h ---- a/src/core/ngx_murmurhash.h 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/core/ngx_murmurhash.h 2020-03-10 15:47:10.739727485 +0800 -@@ -15,5 +15,7 @@ - - uint32_t ngx_murmur_hash2(u_char *data, size_t len); - -+uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed); -+ - - #endif /* _NGX_MURMURHASH_H_INCLUDED_ */ -diff -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c ---- a/src/event/ngx_event_openssl.c 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/event/ngx_event_openssl.c 2020-03-10 15:47:11.441732971 +0800 -@@ -1507,6 +1507,7 @@ - - sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); - sc->buffer_size = ssl->buffer_size; -+ sc->dyn_rec = ssl->dyn_rec; - - sc->session_ctx = ssl->ctx; - -@@ -2404,6 +2405,41 @@ - - for ( ;; ) { - -+ /* Dynamic record resizing: -+ We want the initial records to fit into one TCP segment -+ so we don't get TCP HoL blocking due to TCP Slow Start. -+ A connection always starts with small records, but after -+ a given amount of records sent, we make the records larger -+ to reduce header overhead. -+ After a connection has idled for a given timeout, begin -+ the process from the start. The actual parameters are -+ configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ -+ -+ if (c->ssl->dyn_rec.timeout > 0 ) { -+ -+ if (ngx_current_msec - c->ssl->dyn_rec_last_write > -+ c->ssl->dyn_rec.timeout) -+ { -+ buf->end = buf->start + c->ssl->dyn_rec.size_lo; -+ c->ssl->dyn_rec_records_sent = 0; -+ -+ } else { -+ if (c->ssl->dyn_rec_records_sent > -+ c->ssl->dyn_rec.threshold * 2) -+ { -+ buf->end = buf->start + c->ssl->buffer_size; -+ -+ } else if (c->ssl->dyn_rec_records_sent > -+ c->ssl->dyn_rec.threshold) -+ { -+ buf->end = buf->start + c->ssl->dyn_rec.size_hi; -+ -+ } else { -+ buf->end = buf->start + c->ssl->dyn_rec.size_lo; -+ } -+ } -+ } -+ - while (in && buf->last < buf->end && send < limit) { - if (in->buf->last_buf || in->buf->flush) { - flush = 1; -@@ -2511,6 +2547,9 @@ - - if (n > 0) { - -+ c->ssl->dyn_rec_records_sent++; -+ c->ssl->dyn_rec_last_write = ngx_current_msec; -+ - if (c->ssl->saved_read_handler) { - - c->read->handler = c->ssl->saved_read_handler; -diff -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h ---- a/src/event/ngx_event_openssl.h 2021-04-20 19:35:47.000000000 +0600 -+++ b/src/event/ngx_event_openssl.h 2021-04-21 14:47:37.000000000 +0600 -@@ -66,11 +66,19 @@ - - typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; - -+typedef struct { -+ ngx_msec_t timeout; -+ ngx_uint_t threshold; -+ size_t size_lo; -+ size_t size_hi; -+} ngx_ssl_dyn_rec_t; -+ - - struct ngx_ssl_s { - SSL_CTX *ctx; - ngx_log_t *log; - size_t buffer_size; -+ ngx_ssl_dyn_rec_t dyn_rec; - }; - - -@@ -106,6 +114,10 @@ - unsigned in_ocsp:1; - unsigned early_preread:1; - unsigned write_blocked:1; -+ -+ ngx_ssl_dyn_rec_t dyn_rec; -+ ngx_msec_t dyn_rec_last_write; -+ ngx_uint_t dyn_rec_records_sent; - }; - - -@@ -115,7 +127,7 @@ - #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 - - --#define NGX_SSL_MAX_SESSION_SIZE 4096 -+#define NGX_SSL_MAX_SESSION_SIZE 16384 - - typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; - -diff -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c ---- a/src/http/modules/ngx_http_ssl_module.c 2021-04-20 19:35:47.000000000 +0600 -+++ b/src/http/modules/ngx_http_ssl_module.c 2021-04-21 14:55:37.000000000 +0600 -@@ -301,6 +301,41 @@ - offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), - NULL }, - -+ { ngx_string("ssl_dyn_rec_enable"), -+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, -+ ngx_conf_set_flag_slot, -+ NGX_HTTP_SRV_CONF_OFFSET, -+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), -+ NULL }, -+ -+ { ngx_string("ssl_dyn_rec_timeout"), -+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, -+ ngx_conf_set_msec_slot, -+ NGX_HTTP_SRV_CONF_OFFSET, -+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), -+ NULL }, -+ -+ { ngx_string("ssl_dyn_rec_size_lo"), -+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, -+ ngx_conf_set_size_slot, -+ NGX_HTTP_SRV_CONF_OFFSET, -+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), -+ NULL }, -+ -+ { ngx_string("ssl_dyn_rec_size_hi"), -+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, -+ ngx_conf_set_size_slot, -+ NGX_HTTP_SRV_CONF_OFFSET, -+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), -+ NULL }, -+ -+ { ngx_string("ssl_dyn_rec_threshold"), -+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, -+ ngx_conf_set_num_slot, -+ NGX_HTTP_SRV_CONF_OFFSET, -+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), -+ NULL }, -+ - ngx_null_command - }; - -@@ -637,6 +672,11 @@ - sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; - sscf->stapling = NGX_CONF_UNSET; - sscf->stapling_verify = NGX_CONF_UNSET; -+ sscf->dyn_rec_enable = NGX_CONF_UNSET; -+ sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; -+ sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; -+ sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; -+ sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; - - return sscf; - } -@@ -712,6 +752,20 @@ - ngx_conf_merge_str_value(conf->stapling_responder, - prev->stapling_responder, ""); - -+ ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); -+ ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, -+ 1000); -+ /* Default sizes for the dynamic record sizes are defined to fit maximal -+ TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: -+ 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ -+ ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, -+ 1369); -+ /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ -+ ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, -+ 4229); -+ ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, -+ 40); -+ - conf->ssl.log = cf->log; - - if (conf->enable) { -@@ -943,6 +997,28 @@ - return NGX_CONF_ERROR; - } - -+ if (conf->dyn_rec_enable) { -+ conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; -+ conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; -+ -+ if (conf->buffer_size > conf->dyn_rec_size_lo) { -+ conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; -+ -+ } else { -+ conf->ssl.dyn_rec.size_lo = conf->buffer_size; -+ } -+ -+ if (conf->buffer_size > conf->dyn_rec_size_hi) { -+ conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; -+ -+ } else { -+ conf->ssl.dyn_rec.size_hi = conf->buffer_size; -+ } -+ -+ } else { -+ conf->ssl.dyn_rec.timeout = 0; -+ } -+ - return NGX_CONF_OK; - } - -diff -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h ---- a/src/http/modules/ngx_http_ssl_module.h 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/http/modules/ngx_http_ssl_module.h 2020-03-10 15:47:11.442732979 +0800 -@@ -61,6 +61,12 @@ - - u_char *file; - ngx_uint_t line; -+ -+ ngx_flag_t dyn_rec_enable; -+ ngx_msec_t dyn_rec_timeout; -+ size_t dyn_rec_size_lo; -+ size_t dyn_rec_size_hi; -+ ngx_uint_t dyn_rec_threshold; - } ngx_http_ssl_srv_conf_t; - - -diff -uNr a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c ---- a/src/http/v2/ngx_http_v2.c 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/http/v2/ngx_http_v2.c 2020-03-10 15:47:10.741727500 +0800 -@@ -271,6 +271,8 @@ - - h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; - -+ h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; -+ - h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); - - h2c->concurrent_pushes = h2scf->concurrent_pushes; -@@ -2098,6 +2100,14 @@ - case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: - - h2c->table_update = 1; -+ -+ if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { -+ h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; -+ } else { -+ h2c->max_hpack_table_size = value; -+ } -+ -+ h2c->indicate_resize = 1; - break; - - default: -diff -uNr a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c ---- a/src/http/v2/ngx_http_v2_encode.c 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/http/v2/ngx_http_v2_encode.c 2020-03-10 15:47:10.742727508 +0800 -@@ -10,7 +10,7 @@ - #include - - --static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, -+u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, - ngx_uint_t value); - - -@@ -40,7 +40,7 @@ - } - - --static u_char * -+u_char * - ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) - { - if (value < prefix) { -diff -uNr a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c ---- a/src/http/v2/ngx_http_v2_filter_module.c 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/http/v2/ngx_http_v2_filter_module.c 2020-03-10 15:47:10.743727516 +0800 -@@ -23,10 +23,53 @@ - #define ngx_http_v2_literal_size(h) \ - (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) - -+#define ngx_http_v2_indexed(i) (128 + (i)) -+#define ngx_http_v2_inc_indexed(i) (64 + (i)) -+ -+#define NGX_HTTP_V2_ENCODE_RAW 0 -+#define NGX_HTTP_V2_ENCODE_HUFF 0x80 -+ -+#define NGX_HTTP_V2_AUTHORITY_INDEX 1 -+#define NGX_HTTP_V2_METHOD_GET_INDEX 2 -+#define NGX_HTTP_V2_PATH_INDEX 4 -+ -+#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 -+#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 -+ -+#define NGX_HTTP_V2_STATUS_INDEX 8 -+#define NGX_HTTP_V2_STATUS_200_INDEX 8 -+#define NGX_HTTP_V2_STATUS_204_INDEX 9 -+#define NGX_HTTP_V2_STATUS_206_INDEX 10 -+#define NGX_HTTP_V2_STATUS_304_INDEX 11 -+#define NGX_HTTP_V2_STATUS_400_INDEX 12 -+#define NGX_HTTP_V2_STATUS_404_INDEX 13 -+#define NGX_HTTP_V2_STATUS_500_INDEX 14 -+ -+#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 -+#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 -+#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 -+#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 -+#define NGX_HTTP_V2_DATE_INDEX 33 -+#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 -+#define NGX_HTTP_V2_LOCATION_INDEX 46 -+#define NGX_HTTP_V2_SERVER_INDEX 54 -+#define NGX_HTTP_V2_USER_AGENT_INDEX 58 -+#define NGX_HTTP_V2_VARY_INDEX 59 - - #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 - - -+static const struct { -+ u_char *name; -+ u_char const len; -+} push_header[] = { -+ { (u_char*)":authority" , 10 }, -+ { (u_char*)"accept-encoding" , 15 }, -+ { (u_char*)"accept-language" , 15 }, -+ { (u_char*)"user-agent" , 10 } -+}; -+ -+ - typedef struct { - ngx_str_t name; - u_char index; -@@ -155,11 +198,9 @@ - #endif - - static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); -- static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; - - static size_t nginx_ver_build_len = - ngx_http_v2_literal_size(NGINX_VER_BUILD); -- static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; - - stream = r->stream; - -@@ -435,7 +476,7 @@ - } - - tmp = ngx_palloc(r->pool, tmp_len); -- pos = ngx_pnalloc(r->pool, len); -+ pos = ngx_pnalloc(r->pool, len + 15 + 1); - - if (pos == NULL || tmp == NULL) { - return NGX_ERROR; -@@ -443,11 +484,16 @@ - - start = pos; - -- if (h2c->table_update) { -- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 table size update: 0"); -- *pos++ = (1 << 5) | 0; -- h2c->table_update = 0; -+ h2c = r->stream->connection; -+ -+ if (h2c->indicate_resize) { -+ *pos = 32; -+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), -+ h2c->max_hpack_table_size); -+ h2c->indicate_resize = 0; -+#if (NGX_HTTP_V2_HPACK_ENC) -+ ngx_http_v2_table_resize(h2c); -+#endif - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -@@ -458,67 +504,28 @@ - *pos++ = status; - - } else { -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); -- *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; -- pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); -+ ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); -+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", -+ sizeof(":status") - 1, pos + 8, 3, tmp); - } - - if (r->headers_out.server == NULL) { -- - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { -- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"server: %s\"", -- NGINX_VER); -- -- } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { -- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"server: %s\"", -- NGINX_VER_BUILD); -- -- } else { -- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"server: nginx\""); -- } -- -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); -- -- if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { -- if (nginx_ver[0] == '\0') { -- p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, -- sizeof(NGINX_VER) - 1, tmp); -- nginx_ver_len = p - nginx_ver; -- } -- -- pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); -+ pos = ngx_http_v2_write_header_str("server", NGINX_VER); - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { -- if (nginx_ver_build[0] == '\0') { -- p = ngx_http_v2_write_value(nginx_ver_build, -- (u_char *) NGINX_VER_BUILD, -- sizeof(NGINX_VER_BUILD) - 1, tmp); -- nginx_ver_build_len = p - nginx_ver_build; -- } -- -- pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); -+ pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); - - } else { -- pos = ngx_cpymem(pos, nginx, sizeof(nginx)); -+ pos = ngx_http_v2_write_header_str("server", "nginx"); - } - } - - if (r->headers_out.date == NULL) { -- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"date: %V\"", -- &ngx_cached_http_time); -- -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); -- pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, -- ngx_cached_http_time.len, tmp); -+ pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); - } - - if (r->headers_out.content_type.len) { -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); -- - if (r->headers_out.content_type_len == r->headers_out.content_type.len - && r->headers_out.charset.len) - { -@@ -544,64 +551,36 @@ - r->headers_out.content_type.data = p - len; - } - -- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"content-type: %V\"", -- &r->headers_out.content_type); -- -- pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, -- r->headers_out.content_type.len, tmp); -+ pos = ngx_http_v2_write_header_tbl("content-type", -+ r->headers_out.content_type); - } - - if (r->headers_out.content_length == NULL - && r->headers_out.content_length_n >= 0) - { -- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"content-length: %O\"", -- r->headers_out.content_length_n); -- -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); -- -- p = pos; -- pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); -- *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); -+ p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); -+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", -+ sizeof("content-length") - 1, pos + 15, -+ p - (pos + 15), tmp); - } - - if (r->headers_out.last_modified == NULL - && r->headers_out.last_modified_time != -1) - { -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); -- -- ngx_http_time(pos, r->headers_out.last_modified_time); -+ ngx_http_time(pos + 14, r->headers_out.last_modified_time); - len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; -- -- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"last-modified: %*s\"", -- len, pos); -- -- /* -- * Date will always be encoded using huffman in the temporary buffer, -- * so it's safe here to use src and dst pointing to the same address. -- */ -- pos = ngx_http_v2_write_value(pos, pos, len, tmp); -+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", -+ sizeof("last-modified") - 1, pos + 14, -+ len, tmp); - } - - if (r->headers_out.location && r->headers_out.location->value.len) { -- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"location: %V\"", -- &r->headers_out.location->value); -- -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); -- pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, -- r->headers_out.location->value.len, tmp); -+ pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); - } - - #if (NGX_HTTP_GZIP) - if (r->gzip_vary) { -- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"vary: Accept-Encoding\""); -- -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); -- pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); -+ pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); - } - #endif - -@@ -624,23 +603,10 @@ - continue; - } - --#if (NGX_DEBUG) -- if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { -- ngx_strlow(tmp, header[i].key.data, header[i].key.len); -- -- ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 output header: \"%*s: %V\"", -- header[i].key.len, tmp, &header[i].value); -- } --#endif -- -- *pos++ = 0; -- -- pos = ngx_http_v2_write_name(pos, header[i].key.data, -- header[i].key.len, tmp); -+ pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, -+ header[i].key.len, header[i].value.data, -+ header[i].value.len, tmp); - -- pos = ngx_http_v2_write_value(pos, header[i].value.data, -- header[i].value.len, tmp); - } - - fin = r->header_only -@@ -998,6 +964,7 @@ - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - len += binary[i].len; -+ len += push_header[i].len + 1; - } - - pos = ngx_pnalloc(r->pool, len); -@@ -1007,12 +974,17 @@ - - start = pos; - -- if (h2c->table_update) { -- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, -- "http2 table size update: 0"); -- *pos++ = (1 << 5) | 0; -- h2c->table_update = 0; -- } -+ h2c = r->stream->connection; -+ -+ if (h2c->indicate_resize) { -+ *pos = 32; -+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), -+ h2c->max_hpack_table_size); -+ h2c->indicate_resize = 0; -+#if (NGX_HTTP_V2_HPACK_ENC) -+ ngx_http_v2_table_resize(h2c); -+#endif -+ } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":method: GET\""); -@@ -1022,8 +994,7 @@ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":path: %V\"", path); - -- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); -- pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); -+ pos = ngx_http_v2_write_header_pot(":path", path); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":scheme: %V\"", &r->schema); -@@ -1048,11 +1019,15 @@ - continue; - } - -+ value = &(*h)->value; -+ - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \"%V: %V\"", - &ph[i].name, &(*h)->value); - -- pos = ngx_cpymem(pos, binary[i].data, binary[i].len); -+ pos = ngx_http_v2_write_header(h2c, pos, -+ push_header[i].name, push_header[i].len, value->data, value->len, -+ tmp); - } - - frame = ngx_http_v2_create_push_frame(r, start, pos); -diff -uNr a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h ---- a/src/http/v2/ngx_http_v2.h 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/http/v2/ngx_http_v2.h 2020-03-10 15:47:10.741727500 +0800 -@@ -52,6 +52,14 @@ - #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) - #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 - -+#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ -+#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) -+#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ -+#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ -+ -+#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 -+#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ -+ - #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 - - -@@ -115,6 +123,46 @@ - } ngx_http_v2_hpack_t; - - -+#if (NGX_HTTP_V2_HPACK_ENC) -+typedef struct { -+ uint64_t hash_val; -+ uint32_t index; -+ uint16_t pos; -+ uint16_t klen, vlen; -+ uint16_t size; -+ uint16_t next; -+} ngx_http_v2_hpack_enc_entry_t; -+ -+ -+typedef struct { -+ uint64_t hash_val; -+ uint32_t index; -+ uint16_t pos; -+ uint16_t klen; -+} ngx_http_v2_hpack_name_entry_t; -+ -+ -+typedef struct { -+ size_t size; /* size as defined in RFC 7541 */ -+ uint32_t top; /* the last entry */ -+ uint32_t pos; -+ uint16_t n_elems; /* number of elements */ -+ uint16_t base; /* index of the oldest entry */ -+ uint16_t last; /* index of the newest entry */ -+ -+ /* hash table for dynamic entries, instead using a generic hash table, -+ which would be too slow to process a significant amount of headers, -+ this table is not determenistic, and might ocasionally fail to insert -+ a value, at the cost of slightly worse compression, but significantly -+ faster performance */ -+ ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; -+ ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; -+ u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + -+ HPACK_ENC_MAX_ENTRY]; -+} ngx_http_v2_hpack_enc_t; -+#endif -+ -+ - struct ngx_http_v2_connection_s { - ngx_connection_t *connection; - ngx_http_connection_t *http_connection; -@@ -136,6 +184,8 @@ - - size_t frame_size; - -+ size_t max_hpack_table_size; -+ - ngx_queue_t waiting; - - ngx_http_v2_state_t state; -@@ -163,6 +213,11 @@ - unsigned blocked:1; - unsigned goaway:1; - unsigned push_disabled:1; -+ unsigned indicate_resize:1; -+ -+#if (NGX_HTTP_V2_HPACK_ENC) -+ ngx_http_v2_hpack_enc_t hpack_enc; -+#endif - }; - - -@@ -206,6 +261,8 @@ - - ngx_array_t *cookies; - -+ size_t header_limit; -+ - ngx_pool_t *pool; - - unsigned waiting:1; -@@ -418,4 +475,35 @@ - u_char *tmp, ngx_uint_t lower); - - -+u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, -+ u_char *tmp, ngx_uint_t lower); -+ -+u_char * -+ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); -+ -+#define ngx_http_v2_write_name(dst, src, len, tmp) \ -+ ngx_http_v2_string_encode(dst, src, len, tmp, 1) -+#define ngx_http_v2_write_value(dst, src, len, tmp) \ -+ ngx_http_v2_string_encode(dst, src, len, tmp, 0) -+ -+u_char * -+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, -+ u_char *key, size_t key_len, u_char *value, size_t value_len, -+ u_char *tmp); -+ -+void -+ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); -+ -+#define ngx_http_v2_write_header_str(key, value) \ -+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ -+ (u_char *) value, sizeof(value) - 1, tmp); -+ -+#define ngx_http_v2_write_header_tbl(key, val) \ -+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ -+ val.data, val.len, tmp); -+ -+#define ngx_http_v2_write_header_pot(key, val) \ -+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ -+ val->data, val->len, tmp); -+ - #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ -diff -uNr a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c ---- a/src/http/v2/ngx_http_v2_table.c 2020-03-03 23:04:21.000000000 +0800 -+++ b/src/http/v2/ngx_http_v2_table.c 2020-03-10 15:47:10.743727516 +0800 -@@ -361,3 +361,434 @@ - - return NGX_OK; - } -+ -+ -+#if (NGX_HTTP_V2_HPACK_ENC) -+ -+static ngx_int_t -+hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); -+ -+static ngx_int_t -+hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, -+ uint8_t *key, size_t key_len); -+ -+ -+void -+ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) -+{ -+ ngx_http_v2_hpack_enc_entry_t *table; -+ uint64_t idx; -+ -+ table = h2c->hpack_enc.htable; -+ -+ while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { -+ idx = h2c->hpack_enc.base; -+ h2c->hpack_enc.base = table[idx].next; -+ h2c->hpack_enc.size -= table[idx].size; -+ table[idx].hash_val = 0; -+ h2c->hpack_enc.n_elems--; -+ } -+} -+ -+ -+/* checks if a header is in the hpack table - if so returns the table entry, -+ otherwise encodes and inserts into the table and returns 0, -+ if failed to insert into table, returns -1 */ -+static ngx_int_t -+ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, -+ size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, -+ ngx_int_t *header_idx) -+{ -+ uint64_t hash_val, key_hash, idx, lru; -+ int i; -+ size_t size = key_len + val_len + 32; -+ uint8_t *storage = h2c->hpack_enc.storage; -+ -+ ngx_http_v2_hpack_enc_entry_t *table; -+ ngx_http_v2_hpack_name_entry_t *name; -+ -+ *header_idx = NGX_ERROR; -+ /* step 1: compute the hash value of header */ -+ if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { -+ return NGX_ERROR; -+ } -+ -+ key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); -+ hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); -+ -+ if (hash_val == 0) { -+ return NGX_ERROR; -+ } -+ -+ /* step 2: check if full header in the table */ -+ idx = hash_val; -+ i = -1; -+ while (idx) { -+ /* at most 8 locations are checked, but most will be done in 1 or 2 */ -+ table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; -+ if (table->hash_val == hash_val -+ && table->klen == key_len -+ && table->vlen == val_len -+ && ngx_memcmp(key, storage + table->pos, key_len) == 0 -+ && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) -+ { -+ return (h2c->hpack_enc.top - table->index) + 61; -+ } -+ -+ if (table->hash_val == 0 && i == -1) { -+ i = idx % HPACK_ENC_HTABLE_SZ; -+ break; -+ } -+ -+ idx >>= 8; -+ } -+ -+ /* step 3: check if key is in one of the tables */ -+ *header_idx = hpack_get_static_index(h2c, key, key_len); -+ -+ if (i == -1) { -+ return NGX_ERROR; -+ } -+ -+ if (*header_idx == NGX_ERROR) { -+ *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); -+ } -+ -+ /* step 4: store the new entry */ -+ table = h2c->hpack_enc.htable; -+ -+ if (h2c->hpack_enc.top == 0xffffffff) { -+ /* just to be on the safe side, avoid overflow */ -+ ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); -+ } -+ -+ while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) -+ || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { -+ /* make space for the new entry first */ -+ idx = h2c->hpack_enc.base; -+ h2c->hpack_enc.base = table[idx].next; -+ h2c->hpack_enc.size -= table[idx].size; -+ table[idx].hash_val = 0; -+ h2c->hpack_enc.n_elems--; -+ } -+ -+ table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, -+ .index = h2c->hpack_enc.top, -+ .pos = h2c->hpack_enc.pos, -+ .klen = key_len, -+ .vlen = val_len, -+ .size = size, -+ .next = 0}; -+ -+ table[h2c->hpack_enc.last].next = i; -+ if (h2c->hpack_enc.n_elems == 0) { -+ h2c->hpack_enc.base = i; -+ } -+ -+ h2c->hpack_enc.last = i; -+ h2c->hpack_enc.top++; -+ h2c->hpack_enc.size += size; -+ h2c->hpack_enc.n_elems++; -+ -+ /* update header name lookup */ -+ if (*header_idx == NGX_ERROR ) { -+ lru = h2c->hpack_enc.top; -+ -+ for (i=0; ihpack_enc.heads[i]; -+ -+ if ( name->hash_val == 0 || (name->hash_val == key_hash -+ && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) -+ { -+ name->hash_val = key_hash; -+ name->pos = h2c->hpack_enc.pos; -+ name->index = h2c->hpack_enc.top - 1; -+ break; -+ } -+ -+ if (lru > name->index) { -+ lru = name->index; -+ idx = i; -+ } -+ } -+ -+ if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { -+ name = &h2c->hpack_enc.heads[idx]; -+ name->hash_val = hash_val; -+ name->pos = h2c->hpack_enc.pos; -+ name->index = h2c->hpack_enc.top - 1; -+ } -+ } -+ -+ ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); -+ ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); -+ -+ h2c->hpack_enc.pos += size; -+ if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { -+ h2c->hpack_enc.pos = 0; -+ } -+ -+ return NGX_OK; -+} -+ -+ -+u_char * -+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, -+ u_char *key, size_t key_len, -+ u_char *value, size_t value_len, -+ u_char *tmp) -+{ -+ ngx_int_t idx, header_idx; -+ -+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 output header: %*s: %*s", key_len, key, value_len, -+ value); -+ -+ /* attempt to find the value in the dynamic table */ -+ idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, -+ &header_idx); -+ -+ if (idx > 0) { -+ /* positive index indicates success */ -+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 hpack encode: Indexed Header Field: %ud", idx); -+ -+ *pos = 128; -+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); -+ -+ } else { -+ -+ if (header_idx == NGX_ERROR) { /* if key is not present */ -+ -+ if (idx == NGX_ERROR) { /* if header was not added */ -+ *pos++ = 0; -+ -+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 hpack encode: Literal Header Field without" -+ " Indexing — New Name"); -+ } else { /* if header was added */ -+ *pos++ = 64; -+ -+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 hpack encode: Literal Header Field with " -+ "Incremental Indexing — New Name"); -+ } -+ -+ pos = ngx_http_v2_write_name(pos, key, key_len, tmp); -+ -+ } else { /* if key is present */ -+ -+ if (idx == NGX_ERROR) { -+ *pos = 0; -+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); -+ -+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 hpack encode: Literal Header Field without" -+ " Indexing — Indexed Name: %ud", header_idx); -+ } else { -+ *pos = 64; -+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); -+ -+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 hpack encode: Literal Header Field with " -+ "Incremental Indexing — Indexed Name: %ud", header_idx); -+ } -+ } -+ -+ pos = ngx_http_v2_write_value(pos, value, value_len, tmp); -+ } -+ -+ return pos; -+} -+ -+ -+static ngx_int_t -+hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, -+ uint8_t *key, size_t key_len) -+{ -+ ngx_http_v2_hpack_name_entry_t *name; -+ int i; -+ -+ for (i=0; ihpack_enc.heads[i]; -+ -+ if (name->hash_val == key_hash -+ && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) -+ { -+ if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { -+ return (h2c->hpack_enc.top - name->index) + 61; -+ } -+ break; -+ } -+ } -+ -+ return NGX_ERROR; -+} -+ -+ -+/* decide if a given header is present in the static dictionary, this could be -+ done in several ways, but it seems the fastest one is "exhaustive" search */ -+static ngx_int_t -+hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) -+{ -+ /* the static dictionary of response only headers, -+ although response headers can be put by origin, -+ that would be rare */ -+ static const struct { -+ u_char len; -+ const u_char val[28]; -+ u_char idx; -+ } server_headers[] = { -+ { 3, "age", 21},//0 -+ { 3, "via", 60}, -+ { 4, "date", 33},//2 -+ { 4, "etag", 34}, -+ { 4, "link", 45}, -+ { 4, "vary", 59}, -+ { 5, "allow", 22},//6 -+ { 6, "server", 54},//7 -+ { 7, "expires", 36},//8 -+ { 7, "refresh", 52}, -+ { 8, "location", 46},//10 -+ {10, "set-cookie", 55},//11 -+ {11, "retry-after", 53},//12 -+ {12, "content-type", 31},//13 -+ {13, "content-range", 30},//14 -+ {13, "accept-ranges", 18}, -+ {13, "cache-control", 24}, -+ {13, "last-modified", 44}, -+ {14, "content-length", 28},//18 -+ {16, "content-encoding", 26},//19 -+ {16, "content-language", 27}, -+ {16, "content-location", 29}, -+ {16, "www-authenticate", 61}, -+ {17, "transfer-encoding", 57},//23 -+ {18, "proxy-authenticate", 48},//24 -+ {19, "content-disposition", 25},//25 -+ {25, "strict-transport-security", 56},//26 -+ {27, "access-control-allow-origin", 20},//27 -+ {99, "", 99}, -+ }, *header; -+ -+ /* for a given length, where to start the search -+ since minimal length is 3, the table has a -3 -+ offset */ -+ static const int8_t start_at[] = { -+ [3-3] = 0, -+ [4-3] = 2, -+ [5-3] = 6, -+ [6-3] = 7, -+ [7-3] = 8, -+ [8-3] = 10, -+ [9-3] = -1, -+ [10-3] = 11, -+ [11-3] = 12, -+ [12-3] = 13, -+ [13-3] = 14, -+ [14-3] = 18, -+ [15-3] = -1, -+ [16-3] = 19, -+ [17-3] = 23, -+ [18-3] = 24, -+ [19-3] = 25, -+ [20-3] = -1, -+ [21-3] = -1, -+ [22-3] = -1, -+ [23-3] = -1, -+ [24-3] = -1, -+ [25-3] = 26, -+ [26-3] = -1, -+ [27-3] = 27, -+ }; -+ -+ uint64_t pref; -+ size_t save_len = len, i; -+ int8_t start; -+ -+ /* early exit for out of bounds lengths */ -+ if (len < 3 || len > 27) { -+ return NGX_ERROR; -+ } -+ -+ start = start_at[len - 3]; -+ if (start == -1) { -+ /* exit for non existent lengths */ -+ return NGX_ERROR; -+ } -+ -+ header = &server_headers[start_at[len - 3]]; -+ -+ /* load first 8 bytes of key, for fast comparison */ -+ if (len < 8) { -+ pref = 0; -+ if (len >= 4) { -+ pref = *(uint32_t *)(val + len - 4) | 0x20202020; -+ len -= 4; -+ } -+ while (len > 0) { /* 3 iterations at most */ -+ pref = (pref << 8) ^ (val[len - 1] | 0x20); -+ len--; -+ } -+ } else { -+ pref = *(uint64_t *)val | 0x2020202020202020; -+ len -= 8; -+ } -+ -+ /* iterate over headers with the right length */ -+ while (header->len == save_len) { -+ /* quickly compare the first 8 bytes, most tests will end here */ -+ if (pref != *(uint64_t *) header->val) { -+ header++; -+ continue; -+ } -+ -+ if (len == 0) { -+ /* len == 0, indicates prefix held the entire key */ -+ return header->idx; -+ } -+ /* for longer keys compare the rest */ -+ i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ -+ -+ while (i + 8 <= save_len) { /* 3 iterations at most */ -+ if ( *(uint64_t *)&header->val[i] -+ != (*(uint64_t *) &val[i]| 0x2020202020202020) ) -+ { -+ header++; -+ i = 0; -+ break; -+ } -+ i += 8; -+ } -+ -+ if (i == 0) { -+ continue; -+ } -+ -+ /* found the corresponding entry in the static dictionary */ -+ return header->idx; -+ } -+ -+ return NGX_ERROR; -+} -+ -+#else -+ -+u_char * -+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, -+ u_char *key, size_t key_len, -+ u_char *value, size_t value_len, -+ u_char *tmp) -+{ -+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, -+ "http2 output header: %*s: %*s", key_len, key, value_len, -+ value); -+ -+ *pos++ = 64; -+ pos = ngx_http_v2_write_name(pos, key, key_len, tmp); -+ pos = ngx_http_v2_write_value(pos, value, value_len, tmp); -+ -+ return pos; -+} -+ -+#endif diff --git a/modular/nginx-cp/nginx_dynamic_tls_records.patch b/modular/nginx-cp/nginx_dynamic_tls_records.patch new file mode 100644 index 0000000..55a0488 --- /dev/null +++ b/modular/nginx-cp/nginx_dynamic_tls_records.patch @@ -0,0 +1,254 @@ +What we do now: +We use a static record size of 4K. This gives a good balance of latency and +throughput. + +Optimize latency: +By initialy sending small (1 TCP segment) sized records, we are able to avoid +HoL blocking of the first byte. This means TTFB is sometime lower by a whole +RTT. + +Optimizing throughput: +By sending increasingly larger records later in the connection, when HoL is not +a problem, we reduce the overhead of TLS record (29 bytes per record with +GCM/CHACHA-POLY). + +Logic: +Start each connection with small records (1369 byte default, change with +ssl_dyn_rec_size_lo). After a given number of records (40, change with +ssl_dyn_rec_threshold) start sending larger records (4229, ssl_dyn_rec_size_hi). +Eventually after the same number of records, start sending the largest records +(ssl_buffer_size). +In case the connection idles for a given amount of time (1s, +ssl_dyn_rec_timeout), the process repeats itself (i.e. begin sending small +records again). + + +diff --color -uNr a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c +--- a/src/event/ngx_event_openssl.c 2023-06-13 23:08:10.000000000 +0800 ++++ b/src/event/ngx_event_openssl.c 2023-06-14 15:43:05.834243714 +0800 +@@ -1674,6 +1674,7 @@ + + sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); + sc->buffer_size = ssl->buffer_size; ++ sc->dyn_rec = ssl->dyn_rec; + + sc->session_ctx = ssl->ctx; + +@@ -2645,6 +2646,41 @@ + + for ( ;; ) { + ++ /* Dynamic record resizing: ++ We want the initial records to fit into one TCP segment ++ so we don't get TCP HoL blocking due to TCP Slow Start. ++ A connection always starts with small records, but after ++ a given amount of records sent, we make the records larger ++ to reduce header overhead. ++ After a connection has idled for a given timeout, begin ++ the process from the start. The actual parameters are ++ configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */ ++ ++ if (c->ssl->dyn_rec.timeout > 0 ) { ++ ++ if (ngx_current_msec - c->ssl->dyn_rec_last_write > ++ c->ssl->dyn_rec.timeout) ++ { ++ buf->end = buf->start + c->ssl->dyn_rec.size_lo; ++ c->ssl->dyn_rec_records_sent = 0; ++ ++ } else { ++ if (c->ssl->dyn_rec_records_sent > ++ c->ssl->dyn_rec.threshold * 2) ++ { ++ buf->end = buf->start + c->ssl->buffer_size; ++ ++ } else if (c->ssl->dyn_rec_records_sent > ++ c->ssl->dyn_rec.threshold) ++ { ++ buf->end = buf->start + c->ssl->dyn_rec.size_hi; ++ ++ } else { ++ buf->end = buf->start + c->ssl->dyn_rec.size_lo; ++ } ++ } ++ } ++ + while (in && buf->last < buf->end && send < limit) { + if (in->buf->last_buf || in->buf->flush) { + flush = 1; +@@ -2784,6 +2820,9 @@ + + if (n > 0) { + ++ c->ssl->dyn_rec_records_sent++; ++ c->ssl->dyn_rec_last_write = ngx_current_msec; ++ + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; +diff --color -uNr a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h +--- a/src/event/ngx_event_openssl.h 2023-06-13 23:08:10.000000000 +0800 ++++ b/src/event/ngx_event_openssl.h 2023-06-14 15:43:05.834243714 +0800 +@@ -86,10 +86,19 @@ + typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; + + ++typedef struct { ++ ngx_msec_t timeout; ++ ngx_uint_t threshold; ++ size_t size_lo; ++ size_t size_hi; ++} ngx_ssl_dyn_rec_t; ++ ++ + struct ngx_ssl_s { + SSL_CTX *ctx; + ngx_log_t *log; + size_t buffer_size; ++ ngx_ssl_dyn_rec_t dyn_rec; + }; + + +@@ -128,6 +137,10 @@ + unsigned in_ocsp:1; + unsigned early_preread:1; + unsigned write_blocked:1; ++ ++ ngx_ssl_dyn_rec_t dyn_rec; ++ ngx_msec_t dyn_rec_last_write; ++ ngx_uint_t dyn_rec_records_sent; + }; + + +@@ -137,7 +150,7 @@ + #define NGX_SSL_DFLT_BUILTIN_SCACHE -5 + + +-#define NGX_SSL_MAX_SESSION_SIZE 4096 ++#define NGX_SSL_MAX_SESSION_SIZE 16384 + + typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; + +diff --color -uNr a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c +--- a/src/http/modules/ngx_http_ssl_module.c 2023-06-13 23:08:10.000000000 +0800 ++++ b/src/http/modules/ngx_http_ssl_module.c 2023-06-14 15:43:05.834243714 +0800 +@@ -290,6 +290,41 @@ + offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), + NULL }, + ++ { ngx_string("ssl_dyn_rec_enable"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable), ++ NULL }, ++ ++ { ngx_string("ssl_dyn_rec_timeout"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_msec_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout), ++ NULL }, ++ ++ { ngx_string("ssl_dyn_rec_size_lo"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_size_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo), ++ NULL }, ++ ++ { ngx_string("ssl_dyn_rec_size_hi"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_size_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi), ++ NULL }, ++ ++ { ngx_string("ssl_dyn_rec_threshold"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold), ++ NULL }, ++ + ngx_null_command + }; + +@@ -629,6 +664,11 @@ + sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; + sscf->stapling = NGX_CONF_UNSET; + sscf->stapling_verify = NGX_CONF_UNSET; ++ sscf->dyn_rec_enable = NGX_CONF_UNSET; ++ sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC; ++ sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE; ++ sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE; ++ sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT; + + return sscf; + } +@@ -694,6 +734,20 @@ + ngx_conf_merge_str_value(conf->stapling_responder, + prev->stapling_responder, ""); + ++ ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0); ++ ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout, ++ 1000); ++ /* Default sizes for the dynamic record sizes are defined to fit maximal ++ TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi: ++ 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */ ++ ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo, ++ 1369); ++ /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */ ++ ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi, ++ 4229); ++ ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold, ++ 40); ++ + conf->ssl.log = cf->log; + + if (conf->certificates) { +@@ -890,6 +944,28 @@ + return NGX_CONF_ERROR; + } + ++ if (conf->dyn_rec_enable) { ++ conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout; ++ conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold; ++ ++ if (conf->buffer_size > conf->dyn_rec_size_lo) { ++ conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo; ++ ++ } else { ++ conf->ssl.dyn_rec.size_lo = conf->buffer_size; ++ } ++ ++ if (conf->buffer_size > conf->dyn_rec_size_hi) { ++ conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi; ++ ++ } else { ++ conf->ssl.dyn_rec.size_hi = conf->buffer_size; ++ } ++ ++ } else { ++ conf->ssl.dyn_rec.timeout = 0; ++ } ++ + return NGX_CONF_OK; + } + +diff --color -uNr a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h +--- a/src/http/modules/ngx_http_ssl_module.h 2023-06-13 23:08:10.000000000 +0800 ++++ b/src/http/modules/ngx_http_ssl_module.h 2023-06-14 15:43:38.264102815 +0800 +@@ -62,6 +62,12 @@ + ngx_flag_t stapling_verify; + ngx_str_t stapling_file; + ngx_str_t stapling_responder; ++ ++ ngx_flag_t dyn_rec_enable; ++ ngx_msec_t dyn_rec_timeout; ++ size_t dyn_rec_size_lo; ++ size_t dyn_rec_size_hi; ++ ngx_uint_t dyn_rec_threshold; + } ngx_http_ssl_srv_conf_t; + +