From Julio.Suarez at arm.com Fri Nov 3 11:47:10 2023 From: Julio.Suarez at arm.com (Julio Suarez) Date: Fri, 3 Nov 2023 11:47:10 +0000 Subject: Regarding patch " Added asm "pause" for ngx_cpu_pause() ...." Message-ID: Hello all, I sent a patch yesterday in error. I need to send it from a different email account which auto removes the "privileged information" disclaimer at the bottom. The patch is not privileged information and meant to be publicly shared on this list. Working on getting that bit fixed and will send again. Sorry all, I'm used to doing github PRs where this isn't an issue. Thanks, Julio Suarez IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you. -------------- next part -------------- An HTML attachment was scrubbed... URL: From xeioex at nginx.com Tue Nov 7 23:36:57 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 07 Nov 2023 23:36:57 +0000 Subject: [njs] Version bump. Message-ID: details: https://hg.nginx.org/njs/rev/9dc131d53eda branches: changeset: 2227:9dc131d53eda user: Dmitry Volyntsev date: Tue Nov 07 15:35:18 2023 -0800 description: Version bump. diffstat: src/njs.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r 096576dd8cb0 -r 9dc131d53eda src/njs.h --- a/src/njs.h Mon Oct 23 21:42:43 2023 -0700 +++ b/src/njs.h Tue Nov 07 15:35:18 2023 -0800 @@ -11,8 +11,8 @@ #include -#define NJS_VERSION "0.8.2" -#define NJS_VERSION_NUMBER 0x000802 +#define NJS_VERSION "0.8.3" +#define NJS_VERSION_NUMBER 0x000803 #include From xeioex at nginx.com Tue Nov 7 23:36:59 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 07 Nov 2023 23:36:59 +0000 Subject: [njs] Removed leftover njs directory include directory path. Message-ID: details: https://hg.nginx.org/njs/rev/eab7fa4b82b6 branches: changeset: 2228:eab7fa4b82b6 user: Dmitry Volyntsev date: Tue Nov 07 15:35:25 2023 -0800 description: Removed leftover njs directory include directory path. diffstat: auto/make | 12 ++++++------ 1 files changed, 6 insertions(+), 6 deletions(-) diffs (57 lines): diff -r 9dc131d53eda -r eab7fa4b82b6 auto/make --- a/auto/make Tue Nov 07 15:35:18 2023 -0800 +++ b/auto/make Tue Nov 07 15:35:25 2023 -0800 @@ -86,7 +86,7 @@ do $NJS_BUILD_DIR/$njs_obj: $njs_src \$(NJS_CC) -c \$(NJS_CFLAGS) $NJS_LIB_AUX_CFLAGS \\ - \$(NJS_LIB_INCS) -Injs \\ + \$(NJS_LIB_INCS) \\ -o $NJS_BUILD_DIR/$njs_obj \\ $njs_dep_flags \\ $njs_src @@ -106,7 +106,7 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/libnjs.a \\ external/njs_shell.c \$(NJS_LINK) -o $NJS_BUILD_DIR/njs \$(NJS_CFLAGS) \\ - $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) -Injs \\ + $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) \\ external/njs_shell.c \\ $NJS_BUILD_DIR/libnjs.a \\ $NJS_LD_OPT -lm $NJS_LIBS $NJS_LIB_AUX_LIBS $NJS_READLINE_LIB @@ -120,7 +120,7 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/njs_process_script_fuzzer.o: \\ external/njs_shell.c \$(NJS_CC) -c \$(CFLAGS) $NJS_LIB_AUX_CFLAGS \\ - \$(NJS_LIB_INCS) -Injs \\ + \$(NJS_LIB_INCS) \\ -DNJS_FUZZER_TARGET \\ -o $NJS_BUILD_DIR/njs_process_script_fuzzer.o \\ external/njs_shell.c @@ -129,7 +129,7 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/libnjs.a \\ $NJS_BUILD_DIR/njs_process_script_fuzzer.o \$(CXX) \$(CXXFLAGS) -o $NJS_BUILD_DIR/njs_process_script_fuzzer \\ - $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) -Injs \\ + $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) \\ \$(LIB_FUZZING_ENGINE) \\ $NJS_BUILD_DIR/njs_process_script_fuzzer.o \\ $NJS_BUILD_DIR/libnjs.a \\ @@ -176,7 +176,7 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/$njs_externals_obj: \\ $njs_src \$(NJS_CC) -c \$(NJS_CFLAGS) $NJS_LIB_AUX_CFLAGS \\ - \$(NJS_LIB_INCS) -Injs \\ + \$(NJS_LIB_INCS) \\ -o $NJS_BUILD_DIR/$njs_externals_obj \\ $njs_dep_flags \\ $njs_src @@ -202,7 +202,7 @@ do $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) \\ $njs_dep_flags \\ $NJS_BUILD_DIR/$njs_externals_obj \\ - -Injs $njs_src $NJS_BUILD_DIR/libnjs.a \\ + $njs_src $NJS_BUILD_DIR/libnjs.a \\ $NJS_LD_OPT -lm $NJS_LIBS $NJS_LIB_AUX_LIBS $njs_dep_post From xeioex at nginx.com Tue Nov 7 23:37:01 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 07 Nov 2023 23:37:01 +0000 Subject: [njs] Fixed include directories order. Message-ID: details: https://hg.nginx.org/njs/rev/d0f7f3c071ad branches: changeset: 2229:d0f7f3c071ad user: Dmitry Volyntsev date: Tue Nov 07 15:35:35 2023 -0800 description: Fixed include directories order. Previously, when the previous version of njs.h is installed, an outdated version of njs.h might be included causing build failures. The fix is to ensure that the local include directories are included first. diffstat: auto/make | 27 ++++++++++++++------------- 1 files changed, 14 insertions(+), 13 deletions(-) diffs (86 lines): diff -r eab7fa4b82b6 -r d0f7f3c071ad auto/make --- a/auto/make Tue Nov 07 15:35:25 2023 -0800 +++ b/auto/make Tue Nov 07 15:35:35 2023 -0800 @@ -53,6 +53,7 @@ NJS_CC = ${CC} NJS_STATIC_LINK = ${AR} -r -c NJS_LINK = ${CC} ${NJS_LD_OPT} NJS_CFLAGS = ${NJS_CFLAGS} ${NJS_CC_OPT} ${CFLAGS} +NJS_LIB_AUX_CFLAGS = ${NJS_LIB_AUX_CFLAGS} NJS_VER = $(grep NJS_VERSION src/njs.h | sed -e 's#.*"\(.*\)".*#\1#') NJS_TYPES_VER = \$(NJS_VER) @@ -85,8 +86,8 @@ do cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/$njs_obj: $njs_src - \$(NJS_CC) -c \$(NJS_CFLAGS) $NJS_LIB_AUX_CFLAGS \\ - \$(NJS_LIB_INCS) \\ + \$(NJS_CC) -c \$(NJS_LIB_INCS) \$(NJS_CFLAGS) \\ + \$(NJS_LIB_AUX_CFLAGS) \\ -o $NJS_BUILD_DIR/$njs_obj \\ $njs_dep_flags \\ $njs_src @@ -105,8 +106,8 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/njs: \\ $NJS_BUILD_DIR/libnjs.a \\ external/njs_shell.c - \$(NJS_LINK) -o $NJS_BUILD_DIR/njs \$(NJS_CFLAGS) \\ - $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) \\ + \$(NJS_LINK) -o $NJS_BUILD_DIR/njs \$(NJS_LIB_INCS) \\ + \$(NJS_CFLAGS) \$(NJS_LIB_AUX_CFLAGS)\\ external/njs_shell.c \\ $NJS_BUILD_DIR/libnjs.a \\ $NJS_LD_OPT -lm $NJS_LIBS $NJS_LIB_AUX_LIBS $NJS_READLINE_LIB @@ -119,8 +120,8 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/njs_process_script_fuzzer.o: \\ external/njs_shell.c - \$(NJS_CC) -c \$(CFLAGS) $NJS_LIB_AUX_CFLAGS \\ - \$(NJS_LIB_INCS) \\ + \$(NJS_CC) -c \$(NJS_LIB_INCS) \$(CFLAGS) \\ + \$(NJS_LIB_AUX_CFLAGS) \\ -DNJS_FUZZER_TARGET \\ -o $NJS_BUILD_DIR/njs_process_script_fuzzer.o \\ external/njs_shell.c @@ -129,7 +130,7 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/libnjs.a \\ $NJS_BUILD_DIR/njs_process_script_fuzzer.o \$(CXX) \$(CXXFLAGS) -o $NJS_BUILD_DIR/njs_process_script_fuzzer \\ - $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) \\ + \$(NJS_LIB_AUX_CFLAGS) \\ \$(LIB_FUZZING_ENGINE) \\ $NJS_BUILD_DIR/njs_process_script_fuzzer.o \\ $NJS_BUILD_DIR/libnjs.a \\ @@ -150,8 +151,8 @@ do $NJS_BUILD_DIR/$njs_bin: $njs_src \\ $NJS_BUILD_DIR/libnjs.a - \$(NJS_LINK) -o $NJS_BUILD_DIR/$njs_bin \$(NJS_CFLAGS) \\ - \$(NJS_LIB_INCS) $njs_dep_flags \\ + \$(NJS_LINK) -o $NJS_BUILD_DIR/$njs_bin \$(NJS_LIB_INCS) \\ + \$(NJS_CFLAGS) $njs_dep_flags \\ $njs_src $NJS_BUILD_DIR/libnjs.a \\ $njs_dep_post -lm $NJS_LD_OPT @@ -175,8 +176,8 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/$njs_externals_obj: \\ $njs_src - \$(NJS_CC) -c \$(NJS_CFLAGS) $NJS_LIB_AUX_CFLAGS \\ - \$(NJS_LIB_INCS) \\ + \$(NJS_CC) -c \$(NJS_LIB_INCS) \$(NJS_CFLAGS) \\ + \$(NJS_LIB_AUX_CFLAGS) \\ -o $NJS_BUILD_DIR/$njs_externals_obj \\ $njs_dep_flags \\ $njs_src @@ -198,8 +199,8 @@ do $NJS_BUILD_DIR/$njs_bin: $njs_src \\ $NJS_BUILD_DIR/libnjs.a \\ $NJS_BUILD_DIR/$njs_externals_obj - \$(NJS_LINK) -o $NJS_BUILD_DIR/$njs_bin \$(NJS_CFLAGS) \\ - $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) \\ + \$(NJS_LINK) -o $NJS_BUILD_DIR/$njs_bin \$(NJS_LIB_INCS) \\ + \$(NJS_CFLAGS) \$(NJS_LIB_AUX_CFLAGS) \\ $njs_dep_flags \\ $NJS_BUILD_DIR/$njs_externals_obj \\ $njs_src $NJS_BUILD_DIR/libnjs.a \\ From iam at arteomp.com Wed Nov 8 04:51:39 2023 From: iam at arteomp.com (=?iso-8859-1?q?Artem_Pogartsev?=) Date: Wed, 08 Nov 2023 07:51:39 +0300 Subject: [PATCH] Introduced worker_shutdown_idle_delay Message-ID: # HG changeset patch # User Artem Pogartsev # Date 1699416491 -10800 # Wed Nov 08 07:08:11 2023 +0300 # Node ID eb0dd3d903431f4dd7a62d629db457ecddeadd96 # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Introduced worker_shutdown_idle_delay. The directive configures a delay before closing idle connections to be used when gracefully shutting down worker processes. When the timer expires, nginx will attempt to close any remaining idle connections. The delay is only added for connections with the "shutdown_delay" flag, and currently it's only keepalive HTTP/1.1 connections. The behavior is not changed for protocols that have built-in graceful shutdown mechanisms, such as GOAWAY frames in HTTP2 and HTTP3. Although it's perfectly fine to close an HTTP/1.1 connection at any time according to RFC 9112, it may still be useful to delay closing a connection, wait for the next client request, and close the connection with a "Connection: close" header to avoid unnecessary retries by clients. This is especially important for environments with frequent nginx reloads and large amounts of non-idempotent requests which are quite problematic for automatic retries. Should be used carefully to not delay configuration reloads too much (and thus increase nginx resource usage), and ideally in combination with properly configured clients: client_idle_timeout < min(server_shutdown_delay, server_idle_timeout) diff -r 7ec761f0365f -r eb0dd3d90343 contrib/vim/syntax/nginx.vim --- a/contrib/vim/syntax/nginx.vim Thu Oct 26 23:35:09 2023 +0300 +++ b/contrib/vim/syntax/nginx.vim Wed Nov 08 07:08:11 2023 +0300 @@ -705,6 +705,7 @@ syn keyword ngxDirective contained worker_rlimit_core syn keyword ngxDirective contained worker_rlimit_nofile syn keyword ngxDirective contained worker_shutdown_timeout +syn keyword ngxDirective contained worker_shutdown_idle_delay syn keyword ngxDirective contained working_directory syn keyword ngxDirective contained xclient syn keyword ngxDirective contained xml_entities diff -r 7ec761f0365f -r eb0dd3d90343 src/core/nginx.c --- a/src/core/nginx.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/core/nginx.c Wed Nov 08 07:08:11 2023 +0300 @@ -132,6 +132,13 @@ offsetof(ngx_core_conf_t, shutdown_timeout), NULL }, + { ngx_string("worker_shutdown_idle_delay"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_core_conf_t, shutdown_idle_delay), + NULL }, + { ngx_string("working_directory"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, @@ -1115,6 +1122,7 @@ ccf->master = NGX_CONF_UNSET; ccf->timer_resolution = NGX_CONF_UNSET_MSEC; ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC; + ccf->shutdown_idle_delay = NGX_CONF_UNSET_MSEC; ccf->worker_processes = NGX_CONF_UNSET; ccf->debug_points = NGX_CONF_UNSET; @@ -1144,6 +1152,7 @@ ngx_conf_init_value(ccf->master, 1); ngx_conf_init_msec_value(ccf->timer_resolution, 0); ngx_conf_init_msec_value(ccf->shutdown_timeout, 0); + ngx_conf_init_msec_value(ccf->shutdown_idle_delay, 0); ngx_conf_init_value(ccf->worker_processes, 1); ngx_conf_init_value(ccf->debug_points, 0); diff -r 7ec761f0365f -r eb0dd3d90343 src/core/ngx_connection.c --- a/src/core/ngx_connection.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/core/ngx_connection.c Wed Nov 08 07:08:11 2023 +0300 @@ -1427,7 +1427,7 @@ void -ngx_close_idle_connections(ngx_cycle_t *cycle) +ngx_close_idle_connections(ngx_cycle_t *cycle, ngx_msec_t shutdown_delay) { ngx_uint_t i; ngx_connection_t *c; @@ -1439,10 +1439,15 @@ /* THREAD: lock */ if (c[i].fd != (ngx_socket_t) -1 && c[i].idle) { + if (shutdown_delay && c[i].shutdown_delay) { + continue; + } c[i].close = 1; c[i].read->handler(c[i].read); } } + + ngx_set_shutdown_idle_timer(cycle, shutdown_delay); } diff -r 7ec761f0365f -r eb0dd3d90343 src/core/ngx_connection.h --- a/src/core/ngx_connection.h Thu Oct 26 23:35:09 2023 +0300 +++ b/src/core/ngx_connection.h Wed Nov 08 07:08:11 2023 +0300 @@ -183,6 +183,7 @@ unsigned idle:1; unsigned reusable:1; unsigned close:1; + unsigned shutdown_delay:1; unsigned shared:1; unsigned sendfile:1; @@ -222,7 +223,7 @@ void ngx_configure_listening_sockets(ngx_cycle_t *cycle); void ngx_close_listening_sockets(ngx_cycle_t *cycle); void ngx_close_connection(ngx_connection_t *c); -void ngx_close_idle_connections(ngx_cycle_t *cycle); +void ngx_close_idle_connections(ngx_cycle_t *cycle, ngx_msec_t shutdown_delay); ngx_int_t ngx_connection_local_sockaddr(ngx_connection_t *c, ngx_str_t *s, ngx_uint_t port); ngx_int_t ngx_tcp_nodelay(ngx_connection_t *c); diff -r 7ec761f0365f -r eb0dd3d90343 src/core/ngx_cycle.c --- a/src/core/ngx_cycle.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/core/ngx_cycle.c Wed Nov 08 07:08:11 2023 +0300 @@ -16,6 +16,7 @@ static ngx_int_t ngx_test_lockfile(u_char *file, ngx_log_t *log); static void ngx_clean_old_cycles(ngx_event_t *ev); static void ngx_shutdown_timer_handler(ngx_event_t *ev); +static void ngx_shutdown_idle_timer_handler(ngx_event_t *ev); volatile ngx_cycle_t *ngx_cycle; @@ -24,6 +25,7 @@ static ngx_pool_t *ngx_temp_pool; static ngx_event_t ngx_cleaner_event; static ngx_event_t ngx_shutdown_event; +static ngx_event_t ngx_shutdown_idle_event; ngx_uint_t ngx_test_config; ngx_uint_t ngx_dump_config; @@ -1468,3 +1470,30 @@ c[i].read->handler(c[i].read); } } + + +void +ngx_set_shutdown_idle_timer(ngx_cycle_t *cycle, ngx_msec_t shutdown_delay) +{ + if (shutdown_delay) { + ngx_shutdown_idle_event.handler = ngx_shutdown_idle_timer_handler; + ngx_shutdown_idle_event.data = cycle; + ngx_shutdown_idle_event.log = cycle->log; + ngx_shutdown_idle_event.cancelable = 1; + + ngx_add_timer(&ngx_shutdown_idle_event, shutdown_delay); + } +} + + +static void +ngx_shutdown_idle_timer_handler(ngx_event_t *ev) +{ + ngx_cycle_t *cycle; + + cycle = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "shutdown idle delay timer"); + + ngx_close_idle_connections(cycle, 0); +} diff -r 7ec761f0365f -r eb0dd3d90343 src/core/ngx_cycle.h --- a/src/core/ngx_cycle.h Thu Oct 26 23:35:09 2023 +0300 +++ b/src/core/ngx_cycle.h Wed Nov 08 07:08:11 2023 +0300 @@ -92,6 +92,7 @@ ngx_msec_t timer_resolution; ngx_msec_t shutdown_timeout; + ngx_msec_t shutdown_idle_delay; ngx_int_t worker_processes; ngx_int_t debug_points; @@ -136,6 +137,7 @@ ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag); void ngx_set_shutdown_timer(ngx_cycle_t *cycle); +void ngx_set_shutdown_idle_timer(ngx_cycle_t *cycle, ngx_msec_t shutdown_delay); extern volatile ngx_cycle_t *ngx_cycle; diff -r 7ec761f0365f -r eb0dd3d90343 src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/ngx_http_request.c Wed Nov 08 07:08:11 2023 +0300 @@ -3282,6 +3282,7 @@ r->http_state = NGX_HTTP_KEEPALIVE_STATE; #endif + c->shutdown_delay = 1; c->idle = 1; ngx_reusable_connection(c, 1); diff -r 7ec761f0365f -r eb0dd3d90343 src/os/unix/ngx_process_cycle.c --- a/src/os/unix/ngx_process_cycle.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/os/unix/ngx_process_cycle.c Wed Nov 08 07:08:11 2023 +0300 @@ -698,6 +698,8 @@ static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) { + ngx_core_conf_t *ccf; + ngx_int_t worker = (intptr_t) data; ngx_process = NGX_PROCESS_WORKER; @@ -733,9 +735,10 @@ if (!ngx_exiting) { ngx_exiting = 1; + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_set_shutdown_timer(cycle); ngx_close_listening_sockets(cycle); - ngx_close_idle_connections(cycle); + ngx_close_idle_connections(cycle, ccf->shutdown_idle_delay); ngx_event_process_posted(cycle, &ngx_posted_events); } } diff -r 7ec761f0365f -r eb0dd3d90343 src/os/win32/ngx_process_cycle.c --- a/src/os/win32/ngx_process_cycle.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/os/win32/ngx_process_cycle.c Wed Nov 08 07:08:11 2023 +0300 @@ -762,9 +762,10 @@ static ngx_thread_value_t __stdcall ngx_worker_thread(void *data) { - ngx_int_t n; - ngx_time_t *tp; - ngx_cycle_t *cycle; + ngx_int_t n; + ngx_time_t *tp; + ngx_cycle_t *cycle; + ngx_core_conf_t *ccf; tp = ngx_timeofday(); srand((ngx_pid << 16) ^ (unsigned) tp->sec ^ tp->msec); @@ -801,9 +802,10 @@ if (!ngx_exiting) { ngx_exiting = 1; + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_set_shutdown_timer(cycle); ngx_close_listening_sockets(cycle); - ngx_close_idle_connections(cycle); + ngx_close_idle_connections(cycle, ccf->shutdown_idle_delay); ngx_event_process_posted(cycle, &ngx_posted_events); } } From iam at arteomp.com Wed Nov 8 04:58:29 2023 From: iam at arteomp.com (Artem Pogartsev) Date: Wed, 8 Nov 2023 07:58:29 +0300 Subject: [PATCH] Introduced worker_shutdown_idle_delay In-Reply-To: References: Message-ID: <20231108045829.a7lreoctaodseiow@arteomp-pc.> Hi, Sending a patch in previous email, I fully understand that similar proposals have already been discussed and rejected (those were implemented in a different approach though): https://trac.nginx.org/nginx/ticket/1022 https://mailman.nginx.org/pipermail/nginx-devel/2019-January/011804.html https://mailman.nginx.org/pipermail/nginx-devel/2021-December/014644.html Thus I understand that my patch might be rejected for similar reasons, so I'll try to be specific and clarify what problems I'm trying to solve. Problems: While it's perfectly fine to close idle HTTP/1.1 connections at any time according to RFC 9112, it can still cause unnecessary and potentially avoidable problems during configuration reloads, such as: - Retries for requests that are problematic for automatic retries (non-idempotent requests). Unfortunately, it's not always possible to make all requests idempotent and safe for automatic retries. As a result, some of the closed idle connections will cause errors in client applications that can only be resolved manually. This, in turn, leads to a poor user experience. - Unnecessary retries for all other types of requests. While the client should be prepared for this, it will still result in additional latency for retried requests that could be avoided (the more nginx reloads, the more unnecessary retries). - According to rfc9110 [1] it's not allowed to retry non-idempotent requests by proxy. So it's not possible to retry them by some intermediate proxy before nginx, and all those errors should be returned to the client - this will lead to an additional latency even if the client supports automatic retries for non-idempotent requests. - According to rfc9110 [1] a client should not automatically retry a failed automatic retry. There is a high probability that during nginx reloads, a client will retry some requests via closing connections because they all close simultaneously. This, again, will lead to a portion of errors that can only be resolved manually. The main idea: while clients should be prepared for unexpected errors (such as server crashes, lack of free connections, etc.), there is no point in deliberately creating such situations (especially in environments with frequent nginx reloads). With properly configured clients: client_idle_timeout < min(server_shutdown_delay, server_idle_timeout) this patch will allow to minimize the number of unnecessary retries and errors for environments where it's critical. Specifics of the environment: - Nginx hosts as frontend L7 balancers for all incoming external L7 traffic in the datacenter, and in some cases for east-west traffic between microservices. - Almost all of http connections are keep-alive HTTP/1.1 with 300s idle_client_timeout. - Frequent nginx configuration reloads. - Large amount of requests with non-idempotent methods. - Large amount of long-lived "in-progress" connections such as websockets (so in this situation there is no point to immediately shutdown idle HTTP/1.1 connections, because old nginx workers will continue to work anyway). - High diversity of client applications (different mobile platforms, different desktop platforms, different client libraries, different dev teams for those client applications). - We're currently using nginx-plus, so we can't just patch it, that's why it's important for us to propagate this or a similar change in the official release (we've also opened a ticket through the nginx-plus support channel, in case it matters). Additional comments: This patch doesn't change the default behavior and tries to be as unobtrusive as possible. With the option enabled, the configured shutdown delay will only be applied to idle keepalive HTTP/1.1 connections - all other protocols will continue to work as usual (for example, there is no point in delaying shutdown for HTTP/2 or HTTP/3 protocols - they have built-in graceful shutdown mechanisms). However, if needed, the same behavior could easily be added for other protocols in the future. If you don't mind adding the proposed behavior, but don't like the way it was done in the patch, then please advise how it could be reworked. I also understand that additional tests should be added to the nginx-tests repo. If you accept the patch, I will prepare the tests a bit later. Also, let me know if you need more details about use cases where this patch might be useful. [1] https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2-7 -- Artem Pogartsev From iam at arteomp.com Wed Nov 8 06:13:58 2023 From: iam at arteomp.com (Artem Pogartsev) Date: Wed, 8 Nov 2023 09:13:58 +0300 Subject: [PATCH] Introduced worker_shutdown_idle_delay In-Reply-To: References: Message-ID: <20231108061358.cwuvtcemifmemhsr@arteomp-pc.> On Wed, Nov 08, 2023 at 07:51:39AM +0300, Artem Pogartsev via nginx-devel wrote: > diff -r 7ec761f0365f -r eb0dd3d90343 src/os/unix/ngx_process_cycle.c > if (!ngx_exiting) { > ngx_exiting = 1; > + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); > ngx_set_shutdown_timer(cycle); > diff -r 7ec761f0365f -r eb0dd3d90343 src/os/win32/ngx_process_cycle.c > --- a/src/os/win32/ngx_process_cycle.c Thu Oct 26 23:35:09 2023 +0300 > +++ b/src/os/win32/ngx_process_cycle.c Wed Nov 08 07:08:11 2023 +0300 > if (!ngx_exiting) { > ngx_exiting = 1; > + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); > ngx_set_shutdown_timer(cycle); Sorry, code style violation was overlooked. The patch to address this (to be folded to the changeset): diff -r 8cb2c3d2a1ba -r 07e3b2efb78f src/os/unix/ngx_process_cycle.c --- a/src/os/unix/ngx_process_cycle.c Wed Nov 08 07:08:11 2023 +0300 +++ b/src/os/unix/ngx_process_cycle.c Wed Nov 08 09:08:46 2023 +0300 @@ -735,7 +735,8 @@ if (!ngx_exiting) { ngx_exiting = 1; - ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_core_module); ngx_set_shutdown_timer(cycle); ngx_close_listening_sockets(cycle); ngx_close_idle_connections(cycle, ccf->shutdown_idle_delay); diff -r 8cb2c3d2a1ba -r 07e3b2efb78f src/os/win32/ngx_process_cycle.c --- a/src/os/win32/ngx_process_cycle.c Wed Nov 08 07:08:11 2023 +0300 +++ b/src/os/win32/ngx_process_cycle.c Wed Nov 08 09:08:46 2023 +0300 @@ -802,7 +802,8 @@ if (!ngx_exiting) { ngx_exiting = 1; - ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_core_module); ngx_set_shutdown_timer(cycle); ngx_close_listening_sockets(cycle); ngx_close_idle_connections(cycle, ccf->shutdown_idle_delay); From vl at inspert.ru Thu Nov 9 15:14:08 2023 From: vl at inspert.ru (Vladimir Homutov) Date: Thu, 9 Nov 2023 18:14:08 +0300 Subject: [patch] quic PTO counter fixes In-Reply-To: <20231025230855.gkob3yoigbmtazcl@Y9MQ9X2QVV> References: <20231025230855.gkob3yoigbmtazcl@Y9MQ9X2QVV> Message-ID: > On Thu, Oct 26, 2023 at 03:08:55AM +0400, Sergey Kandaurov wrote: > > # HG changeset patch > > # User Vladimir Khomutov > > # Date 1697031803 -10800 > > # Wed Oct 11 16:43:23 2023 +0300 > > # Node ID 9ba2840e88f62343b3bd794e43900781dab43686 > > # Parent 1f188102fbd944df797e8710f70cccee76164add > > QUIC: fixed handling of PTO counter. > > > > The RFC 9002 clearly says in "6.2. Probe Timeout": > > ... > > As with loss detection, the PTO is per packet number space. > > That is, a PTO value is computed per packet number space. > > > > Despite that, current code is using per-connection PTO counter. > > For example, this may lead to situation when packet loss at handshake > > level will affect PTO calculation for initial packets, preventing > > send of new probes. > > Although PTO value is per packet number space, PTO backoff is not, > see "6.2.1 Computing PTO": > > : When ack-eliciting packets in multiple packet number spaces are in flight, the > : timer MUST be set to the earlier value of the Initial and Handshake packet > : number spaces. And I read this fragment as: - there are multiple timer values (i.e. per packet number space) (and value is pto * backoff) - we have to choose the earliest value The ngx_quic_pto() has nothing that depends on packet number space (with the minor exception that we add max_ack_delay at application level after the handshake) So pto_count is the only thing that can make timer values to be different in different packet number spaces. > > But: > > : When a PTO timer expires, the PTO backoff MUST be increased <..> > > : This exponential reduction in the sender's rate is important because consecutive > : PTOs might be caused by loss of packets or acknowledgments due to severe > : congestion. Even when there are ack-eliciting packets in flight in multiple > : packet number spaces, the exponential increase in PTO occurs across all spaces > : to prevent excess load on the network. For example, a timeout in the Initial > : packet number space doubles the length of the timeout in the Handshake packet > : number space. yes, this really looks like contradiction. At least I don't understand how it is possible to have PTO value different by packet number space given the way we calculate it. > Even if that would be proven otherwise, I don't think the description > provides detailed explanation. It describes a pretty specific use case, > when both Initial and Handshake packet number spaces have in-flight packets > with different PTO timeout (i.e. different f->last). Typically they are > sent coalesced (e.g. CRYPTO frames for ServerHello and (at least) > EncryptedExtensions TLS messages). > In interop tests, though, it might be different: such packets may be > sent separately, with Handshake packet thus having a later PTO timeout. > If such, PTO timer will first fire for the Initial packet, then for Handshake, > which will result in PTO backoff accumulated for each packet: > > t1: <- Initial (lost) > t2: <- Handshake (lost) > t1': pto(t1) timeout > <- Initial (pto_count=1) > t2': pto(t2) timeout > <- Handshake (pto_count=2) > t1'': pto(t1') timeout > <- Initial (pto_count=3) > > So, I would supplement the description with the phrase that that's > fair typically with uncoalesced packets seen in interop tests, and > that the same is true vice verse with packet loss at initial packet > number space affecting PTO backoff in handshake packet number space. > > But see above about PTO backoff increase across all spaces. I tend to think that it is better to leave things as is. maybe RFC needs some better wording in this case. I've checked ngtcp2 and msquic and it it looks like both handle pto counter per-connection too; (see pto_count in ngtcp2 and QUIC_LOSS_DETECTION.ProbeCount in msquic) > > Additionally, one case of successful ACK receiving was missing: > > PING frames are not stored in the ctx->sent queue, thus PTO was not > > reset when corresponding packets were acknowledged. > > See below. > > > > > diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c > > --- a/src/event/quic/ngx_event_quic.c > > +++ b/src/event/quic/ngx_event_quic.c > > @@ -1088,8 +1088,6 @@ ngx_quic_discard_ctx(ngx_connection_t *c > > > > ngx_quic_keys_discard(qc->keys, level); > > > > - qc->pto_count = 0; > > - > > ctx = ngx_quic_get_send_ctx(qc, level); > > > > ngx_quic_free_buffer(c, &ctx->crypto); > > @@ -1120,6 +1118,7 @@ ngx_quic_discard_ctx(ngx_connection_t *c > > } > > > > ctx->send_ack = 0; > > + ctx->pto_count = 0; > > > > ngx_quic_set_lost_timer(c); > > } > > diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c > > --- a/src/event/quic/ngx_event_quic_ack.c > > +++ b/src/event/quic/ngx_event_quic_ack.c > > @@ -286,8 +286,12 @@ ngx_quic_handle_ack_frame_range(ngx_conn > > if (!found) { > > > > if (max < ctx->pnum) { > > - /* duplicate ACK or ACK for non-ack-eliciting frame */ > > - return NGX_OK; > > + /* > > + * - ACK for frames not in sent queue (i.e. PING) > > + * - duplicate ACK > > + * - ACK for non-ack-eliciting frame > > + */ > > + goto done; > > } > > > > ngx_log_error(NGX_LOG_INFO, c->log, 0, > > @@ -300,11 +304,13 @@ ngx_quic_handle_ack_frame_range(ngx_conn > > return NGX_ERROR; > > } > > > > +done: > > + > > if (!qc->push.timer_set) { > > ngx_post_event(&qc->push, &ngx_posted_events); > > } > > > > - qc->pto_count = 0; > > + ctx->pto_count = 0; > > This part of the change to reset pto_count > for duplicate ACK or ACK for non-ack-eliciting frame > contradicts the OnAckReceived example in RFC 9002, > although I didn't found a format text in the RFC itself: > > OnAckReceived(ack, pn_space): > ... > // DetectAndRemoveAckedPackets finds packets that are newly > // acknowledged and removes them from sent_packets. > newly_acked_packets = > DetectAndRemoveAckedPackets(ack, pn_space) > // Nothing to do if there are no newly acked packets. > if (newly_acked_packets.empty()): > return > > // Update the RTT if the largest acknowledged is newly acked > // and at least one ack-eliciting was newly acked. > ... > > // Reset pto_count ... > > From which it follows that pto_count is reset > (and RTT updated) for newly ack'ed packets only. yes, agreed. the main issue is tracking of in-flight PING frames. > > I think the better fix would be to properly track in-flight PING frames. > Moreover, the current behaviour of not tracking PING frames in ctx->sent > prevents from a properly calculated PTO timeout: each time it is calculated > against the original packet (with increasingly receding time to the past) > that triggered the first PTO timeout, which doesn't result in exponentially > increased PTO period as expected, but rather some bogus value. Agree. I've created a patch that fixes this. -------------- next part -------------- A non-text attachment was scrubbed... Name: ping_frames_inflight.diff Type: text/x-diff Size: 3496 bytes Desc: not available URL: From xeioex at nginx.com Thu Nov 9 19:59:30 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Thu, 09 Nov 2023 19:59:30 +0000 Subject: [njs] Shell: unified normal mode and LLVMFuzzerTestOneInput(). Message-ID: details: https://hg.nginx.org/njs/rev/9a886a575f61 branches: changeset: 2230:9a886a575f61 user: Dmitry Volyntsev date: Thu Nov 09 11:11:19 2023 -0800 description: Shell: unified normal mode and LLVMFuzzerTestOneInput(). Now both main() and LLVMFuzzerTestOneInput() use njs_main() as a main routine. diffstat: external/njs_shell.c | 510 ++++++++++++++++++++++++-------------------------- 1 files changed, 245 insertions(+), 265 deletions(-) diffs (615 lines): diff -r d0f7f3c071ad -r 9a886a575f61 external/njs_shell.c --- a/external/njs_shell.c Tue Nov 07 15:35:35 2023 -0800 +++ b/external/njs_shell.c Thu Nov 09 11:11:19 2023 -0800 @@ -30,10 +30,6 @@ #endif -typedef void (*njs_console_output_pt)(njs_vm_t *vm, njs_value_t *value, - njs_int_t ret); - - typedef struct { uint8_t disassemble; uint8_t denormals; @@ -51,7 +47,7 @@ typedef struct { int stack_size; char *file; - char *command; + njs_str_t command; size_t n_paths; char **paths; char **argv; @@ -94,16 +90,20 @@ typedef struct { njs_queue_t labels; + njs_bool_t suppress_stdout; + njs_completion_t completion; } njs_console_t; +static njs_int_t njs_main(njs_opts_t *opts); static njs_int_t njs_console_init(njs_vm_t *vm, njs_console_t *console); static void njs_console_output(njs_vm_t *vm, njs_value_t *value, njs_int_t ret); static njs_int_t njs_externals_init(njs_vm_t *vm); static njs_vm_t *njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options); static void njs_process_output(njs_vm_t *vm, njs_value_t *value, njs_int_t ret); +static njs_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options); static njs_int_t njs_process_script(njs_vm_t *vm, void *runtime, const njs_str_t *script); @@ -111,7 +111,6 @@ static njs_int_t njs_process_script(njs_ static njs_int_t njs_options_parse(njs_opts_t *opts, int argc, char **argv); static void njs_options_free(njs_opts_t *opts); -static njs_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options); #ifdef NJS_HAVE_READLINE static njs_int_t njs_interactive_shell(njs_opts_t *opts, @@ -301,25 +300,97 @@ static njs_int_t njs_console_proto_ static njs_console_t njs_console; +static njs_int_t +njs_main(njs_opts_t *opts) +{ + njs_vm_t *vm; + njs_int_t ret; + njs_vm_opt_t vm_options; + + njs_mm_denormals(opts.denormals); + + njs_vm_opt_init(&vm_options); + + if (opts->file == NULL) { + if (opts->command.length != 0) { + opts->file = (char *) "string"; + } + +#ifdef NJS_HAVE_READLINE + else if (opts->interactive) { + opts->file = (char *) "shell"; + } +#endif + + if (opts->file == NULL) { + njs_stderror("file name is required in non-interactive mode\n"); + return NJS_ERROR; + } + } + + vm_options.file.start = (u_char *) opts->file; + vm_options.file.length = njs_strlen(opts->file); + + vm_options.init = 1; + vm_options.interactive = opts->interactive; + vm_options.disassemble = opts->disassemble; + vm_options.backtrace = 1; + vm_options.quiet = opts->quiet; + vm_options.sandbox = opts->sandbox; + vm_options.unsafe = !opts->safe; + vm_options.module = opts->module; +#ifdef NJS_DEBUG_GENERATOR + vm_options.generator_debug = opts->generator_debug; +#endif +#ifdef NJS_DEBUG_OPCODE + vm_options.opcode_debug = opts->opcode_debug; +#endif + + vm_options.ops = &njs_console_ops; + vm_options.addons = njs_console_addon_modules; + vm_options.external = &njs_console; + vm_options.argv = opts->argv; + vm_options.argc = opts->argc; + vm_options.ast = opts->ast; + vm_options.unhandled_rejection = opts->unhandled_rejection; + + if (opts->stack_size != 0) { + vm_options.max_stack_size = opts->stack_size; + } + +#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) + + if (opts->interactive) { + ret = njs_interactive_shell(opts, &vm_options); + + } else + +#endif + + if (opts->command.length != 0) { + vm = njs_create_vm(opts, &vm_options); + if (vm == NULL) { + return NJS_ERROR; + } + + ret = njs_process_script(vm, njs_vm_external_ptr(vm), &opts->command); + njs_vm_destroy(vm); + + } else { + ret = njs_process_file(opts, &vm_options); + } + + return ret; +} + + #ifndef NJS_FUZZER_TARGET int main(int argc, char **argv) { - njs_vm_t *vm; - njs_int_t ret; - njs_opts_t opts; - njs_str_t command; - njs_vm_opt_t vm_options; - - static uintptr_t uptr[] = { - (uintptr_t) njs_console_output, - }; - - static njs_vm_meta_t metas = { - .size = njs_nitems(uptr), - .values = uptr - }; + njs_int_t ret; + njs_opts_t opts; njs_memzero(&opts, sizeof(njs_opts_t)); opts.interactive = 1; @@ -336,79 +407,7 @@ main(int argc, char **argv) goto done; } - njs_mm_denormals(opts.denormals); - - njs_vm_opt_init(&vm_options); - - if (opts.file == NULL) { - if (opts.command != NULL) { - opts.file = (char *) "string"; - } - -#ifdef NJS_HAVE_READLINE - else if (opts.interactive) { - opts.file = (char *) "shell"; - } -#endif - - if (opts.file == NULL) { - njs_stderror("file name is required in non-interactive mode\n"); - goto done; - } - } - - vm_options.file.start = (u_char *) opts.file; - vm_options.file.length = njs_strlen(opts.file); - - vm_options.init = 1; - vm_options.interactive = opts.interactive; - vm_options.disassemble = opts.disassemble; - vm_options.backtrace = 1; - vm_options.quiet = opts.quiet; - vm_options.sandbox = opts.sandbox; - vm_options.unsafe = !opts.safe; - vm_options.module = opts.module; -#ifdef NJS_DEBUG_GENERATOR - vm_options.generator_debug = opts.generator_debug; -#endif -#ifdef NJS_DEBUG_OPCODE - vm_options.opcode_debug = opts.opcode_debug; -#endif - - vm_options.ops = &njs_console_ops; - vm_options.addons = njs_console_addon_modules; - vm_options.metas = &metas; - vm_options.external = &njs_console; - vm_options.argv = opts.argv; - vm_options.argc = opts.argc; - vm_options.ast = opts.ast; - vm_options.unhandled_rejection = opts.unhandled_rejection; - - if (opts.stack_size != 0) { - vm_options.max_stack_size = opts.stack_size; - } - -#ifdef NJS_HAVE_READLINE - - if (opts.interactive) { - ret = njs_interactive_shell(&opts, &vm_options); - - } else - -#endif - - if (opts.command) { - vm = njs_create_vm(&opts, &vm_options); - if (vm != NULL) { - command.start = (u_char *) opts.command; - command.length = njs_strlen(opts.command); - ret = njs_process_script(vm, njs_vm_external_ptr(vm), &command); - njs_vm_destroy(vm); - } - - } else { - ret = njs_process_file(&opts, &vm_options); - } + ret = njs_main(&opts); done: @@ -494,7 +493,8 @@ njs_options_parse(njs_opts_t *opts, int opts->interactive = 0; if (++i < argc) { - opts->command = argv[i]; + opts->command.start = (u_char *) argv[i]; + opts->command.length = njs_strlen(argv[i]); goto done; } @@ -638,163 +638,12 @@ njs_options_free(njs_opts_t *opts) } -static njs_int_t -njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options) -{ - int fd; - char *file; - u_char *p, *end, *start; - size_t size; - ssize_t n; - njs_vm_t *vm; - njs_int_t ret; - njs_str_t source, script; - struct stat sb; - u_char buf[4096]; - - file = opts->file; - - if (file[0] == '-' && file[1] == '\0') { - fd = STDIN_FILENO; - - } else { - fd = open(file, O_RDONLY); - if (fd == -1) { - njs_stderror("failed to open file: '%s' (%s)\n", - file, strerror(errno)); - return NJS_ERROR; - } - } - - if (fstat(fd, &sb) == -1) { - njs_stderror("fstat(%d) failed while reading '%s' (%s)\n", - fd, file, strerror(errno)); - ret = NJS_ERROR; - goto close_fd; - } - - size = sizeof(buf); - - if (S_ISREG(sb.st_mode) && sb.st_size) { - size = sb.st_size; - } - - vm = NULL; - - source.length = 0; - source.start = realloc(NULL, size); - if (source.start == NULL) { - njs_stderror("alloc failed while reading '%s'\n", file); - ret = NJS_ERROR; - goto done; - } - - p = source.start; - end = p + size; - - for ( ;; ) { - n = read(fd, buf, sizeof(buf)); - - if (n == 0) { - break; - } - - if (n < 0) { - njs_stderror("failed to read file: '%s' (%s)\n", - file, strerror(errno)); - ret = NJS_ERROR; - goto done; - } - - if (p + n > end) { - size *= 2; - - start = realloc(source.start, size); - if (start == NULL) { - njs_stderror("alloc failed while reading '%s'\n", file); - ret = NJS_ERROR; - goto done; - } - - source.start = start; - - p = source.start + source.length; - end = source.start + size; - } - - memcpy(p, buf, n); - - p += n; - source.length += n; - } - - vm = njs_create_vm(opts, vm_options); - if (vm == NULL) { - ret = NJS_ERROR; - goto done; - } - - script = source; - - /* shebang */ - - if (script.length > 2 && memcmp(script.start, "#!", 2) == 0) { - p = njs_strlchr(script.start, script.start + script.length, '\n'); - - if (p != NULL) { - script.length -= (p + 1 - script.start); - script.start = p + 1; - - } else { - script.length = 0; - } - } - - ret = njs_process_script(vm, vm_options->external, &script); - if (ret != NJS_OK) { - ret = NJS_ERROR; - goto done; - } - - ret = NJS_OK; - -done: - - if (vm != NULL) { - njs_vm_destroy(vm); - } - - if (source.start != NULL) { - free(source.start); - } - -close_fd: - - if (fd != STDIN_FILENO) { - (void) close(fd); - } - - return ret; -} - #else int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - njs_vm_t *vm; - njs_opts_t opts; - njs_str_t script; - njs_vm_opt_t vm_options; - - static uintptr_t uptr[] = { - (uintptr_t) NULL, - }; - - static njs_vm_meta_t metas = { - .size = njs_nitems(uptr), - .values = uptr - }; + njs_opts_t opts; if (size == 0) { return 0; @@ -802,24 +651,15 @@ LLVMFuzzerTestOneInput(const uint8_t* da njs_memzero(&opts, sizeof(njs_opts_t)); - njs_vm_opt_init(&vm_options); - - vm_options.init = 1; - vm_options.backtrace = 0; - vm_options.metas = &metas; - vm_options.ops = &njs_console_ops; - - vm = njs_create_vm(&opts, &vm_options); + opts.file = (char *) "fuzzer"; + opts.command.start = (u_char *) data; + opts.command.length = size; - if (njs_fast_path(vm != NULL)) { - script.length = size; - script.start = (u_char *) data; + njs_memzero(&njs_console, sizeof(njs_console_t)); - (void) njs_process_script(vm, NULL, &script); - njs_vm_destroy(vm); - } + njs_console.suppress_stdout = 1; - return 0; + return njs_main(&opts); } #endif @@ -1025,6 +865,146 @@ njs_process_events(void *runtime) static njs_int_t +njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options) +{ + int fd; + char *file; + u_char *p, *end, *start; + size_t size; + ssize_t n; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t source, script; + struct stat sb; + u_char buf[4096]; + + file = opts->file; + + if (file[0] == '-' && file[1] == '\0') { + fd = STDIN_FILENO; + + } else { + fd = open(file, O_RDONLY); + if (fd == -1) { + njs_stderror("failed to open file: '%s' (%s)\n", + file, strerror(errno)); + return NJS_ERROR; + } + } + + if (fstat(fd, &sb) == -1) { + njs_stderror("fstat(%d) failed while reading '%s' (%s)\n", + fd, file, strerror(errno)); + ret = NJS_ERROR; + goto close_fd; + } + + size = sizeof(buf); + + if (S_ISREG(sb.st_mode) && sb.st_size) { + size = sb.st_size; + } + + vm = NULL; + + source.length = 0; + source.start = realloc(NULL, size); + if (source.start == NULL) { + njs_stderror("alloc failed while reading '%s'\n", file); + ret = NJS_ERROR; + goto done; + } + + p = source.start; + end = p + size; + + for ( ;; ) { + n = read(fd, buf, sizeof(buf)); + + if (n == 0) { + break; + } + + if (n < 0) { + njs_stderror("failed to read file: '%s' (%s)\n", + file, strerror(errno)); + ret = NJS_ERROR; + goto done; + } + + if (p + n > end) { + size *= 2; + + start = realloc(source.start, size); + if (start == NULL) { + njs_stderror("alloc failed while reading '%s'\n", file); + ret = NJS_ERROR; + goto done; + } + + source.start = start; + + p = source.start + source.length; + end = source.start + size; + } + + memcpy(p, buf, n); + + p += n; + source.length += n; + } + + vm = njs_create_vm(opts, vm_options); + if (vm == NULL) { + ret = NJS_ERROR; + goto done; + } + + script = source; + + /* shebang */ + + if (script.length > 2 && memcmp(script.start, "#!", 2) == 0) { + p = njs_strlchr(script.start, script.start + script.length, '\n'); + + if (p != NULL) { + script.length -= (p + 1 - script.start); + script.start = p + 1; + + } else { + script.length = 0; + } + } + + ret = njs_process_script(vm, vm_options->external, &script); + if (ret != NJS_OK) { + ret = NJS_ERROR; + goto done; + } + + ret = NJS_OK; + +done: + + if (vm != NULL) { + njs_vm_destroy(vm); + } + + if (source.start != NULL) { + free(source.start); + } + +close_fd: + + if (fd != STDIN_FILENO) { + (void) close(fd); + } + + return ret; +} + + +static njs_int_t njs_process_script(njs_vm_t *vm, void *runtime, const njs_str_t *script) { u_char *start, *end; @@ -1091,12 +1071,12 @@ njs_process_script(njs_vm_t *vm, void *r static void njs_process_output(njs_vm_t *vm, njs_value_t *value, njs_int_t ret) { - njs_console_output_pt pt; + njs_console_t *console; - pt = (njs_console_output_pt) njs_vm_meta(vm, 0); + console = njs_vm_external_ptr(vm); - if (pt != NULL) { - pt(vm, value, ret); + if (!console->suppress_stdout) { + njs_console_output(vm, value, ret); } } From xeioex at nginx.com Thu Nov 9 19:59:32 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Thu, 09 Nov 2023 19:59:32 +0000 Subject: [njs] Shell: moving njs_vm_opt_t initialisation to njs_create_vm(). Message-ID: details: https://hg.nginx.org/njs/rev/f6e0a7270492 branches: changeset: 2231:f6e0a7270492 user: Dmitry Volyntsev date: Thu Nov 09 11:49:25 2023 -0800 description: Shell: moving njs_vm_opt_t initialisation to njs_create_vm(). diffstat: external/njs_shell.c | 109 +++++++++++++++++++++++++------------------------- 1 files changed, 54 insertions(+), 55 deletions(-) diffs (202 lines): diff -r 9a886a575f61 -r f6e0a7270492 external/njs_shell.c --- a/external/njs_shell.c Thu Nov 09 11:11:19 2023 -0800 +++ b/external/njs_shell.c Thu Nov 09 11:49:25 2023 -0800 @@ -101,9 +101,9 @@ static njs_int_t njs_console_init(njs_vm static void njs_console_output(njs_vm_t *vm, njs_value_t *value, njs_int_t ret); static njs_int_t njs_externals_init(njs_vm_t *vm); -static njs_vm_t *njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options); +static njs_vm_t *njs_create_vm(njs_opts_t *opts); static void njs_process_output(njs_vm_t *vm, njs_value_t *value, njs_int_t ret); -static njs_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options); +static njs_int_t njs_process_file(njs_opts_t *opts); static njs_int_t njs_process_script(njs_vm_t *vm, void *runtime, const njs_str_t *script); @@ -113,8 +113,7 @@ static njs_int_t njs_options_parse(njs_o static void njs_options_free(njs_opts_t *opts); #ifdef NJS_HAVE_READLINE -static njs_int_t njs_interactive_shell(njs_opts_t *opts, - njs_vm_opt_t *vm_options); +static njs_int_t njs_interactive_shell(njs_opts_t *opts); static njs_int_t njs_editline_init(void); static char *njs_completion_generator(const char *text, int state); #endif @@ -303,13 +302,10 @@ static njs_console_t njs_console; static njs_int_t njs_main(njs_opts_t *opts) { - njs_vm_t *vm; - njs_int_t ret; - njs_vm_opt_t vm_options; + njs_vm_t *vm; + njs_int_t ret; - njs_mm_denormals(opts.denormals); - - njs_vm_opt_init(&vm_options); + njs_mm_denormals(opts->denormals); if (opts->file == NULL) { if (opts->command.length != 0) { @@ -328,47 +324,17 @@ njs_main(njs_opts_t *opts) } } - vm_options.file.start = (u_char *) opts->file; - vm_options.file.length = njs_strlen(opts->file); - - vm_options.init = 1; - vm_options.interactive = opts->interactive; - vm_options.disassemble = opts->disassemble; - vm_options.backtrace = 1; - vm_options.quiet = opts->quiet; - vm_options.sandbox = opts->sandbox; - vm_options.unsafe = !opts->safe; - vm_options.module = opts->module; -#ifdef NJS_DEBUG_GENERATOR - vm_options.generator_debug = opts->generator_debug; -#endif -#ifdef NJS_DEBUG_OPCODE - vm_options.opcode_debug = opts->opcode_debug; -#endif - - vm_options.ops = &njs_console_ops; - vm_options.addons = njs_console_addon_modules; - vm_options.external = &njs_console; - vm_options.argv = opts->argv; - vm_options.argc = opts->argc; - vm_options.ast = opts->ast; - vm_options.unhandled_rejection = opts->unhandled_rejection; - - if (opts->stack_size != 0) { - vm_options.max_stack_size = opts->stack_size; - } - #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) if (opts->interactive) { - ret = njs_interactive_shell(opts, &vm_options); + ret = njs_interactive_shell(opts); } else #endif if (opts->command.length != 0) { - vm = njs_create_vm(opts, &vm_options); + vm = njs_create_vm(opts); if (vm == NULL) { return NJS_ERROR; } @@ -377,7 +343,7 @@ njs_main(njs_opts_t *opts) njs_vm_destroy(vm); } else { - ret = njs_process_file(opts, &vm_options); + ret = njs_process_file(opts); } return ret; @@ -751,15 +717,48 @@ njs_externals_init(njs_vm_t *vm) static njs_vm_t * -njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options) +njs_create_vm(njs_opts_t *opts) { - u_char *p, *start; - njs_vm_t *vm; - njs_int_t ret; - njs_str_t path; - njs_uint_t i; + u_char *p, *start; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t path; + njs_uint_t i; + njs_vm_opt_t vm_options; + + njs_vm_opt_init(&vm_options); + + vm_options.file.start = (u_char *) opts->file; + vm_options.file.length = njs_strlen(opts->file); - vm = njs_vm_create(vm_options); + vm_options.init = 1; + vm_options.interactive = opts->interactive; + vm_options.disassemble = opts->disassemble; + vm_options.backtrace = 1; + vm_options.quiet = opts->quiet; + vm_options.sandbox = opts->sandbox; + vm_options.unsafe = !opts->safe; + vm_options.module = opts->module; +#ifdef NJS_DEBUG_GENERATOR + vm_options.generator_debug = opts->generator_debug; +#endif +#ifdef NJS_DEBUG_OPCODE + vm_options.opcode_debug = opts->opcode_debug; +#endif + + vm_options.ops = &njs_console_ops; + vm_options.addons = njs_console_addon_modules; + vm_options.external = &njs_console; + vm_options.argv = opts->argv; + vm_options.argc = opts->argc; + vm_options.ast = opts->ast; + vm_options.unhandled_rejection = opts->unhandled_rejection; + + if (opts->stack_size != 0) { + vm_options.max_stack_size = opts->stack_size; + } + + vm = njs_vm_create(&vm_options); if (vm == NULL) { njs_stderror("failed to create vm\n"); return NULL; @@ -865,7 +864,7 @@ njs_process_events(void *runtime) static njs_int_t -njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options) +njs_process_file(njs_opts_t *opts) { int fd; char *file; @@ -954,7 +953,7 @@ njs_process_file(njs_opts_t *opts, njs_v source.length += n; } - vm = njs_create_vm(opts, vm_options); + vm = njs_create_vm(opts); if (vm == NULL) { ret = NJS_ERROR; goto done; @@ -976,7 +975,7 @@ njs_process_file(njs_opts_t *opts, njs_v } } - ret = njs_process_script(vm, vm_options->external, &script); + ret = njs_process_script(vm, njs_vm_external_ptr(vm), &script); if (ret != NJS_OK) { ret = NJS_ERROR; goto done; @@ -1133,7 +1132,7 @@ free_line: static njs_int_t -njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options) +njs_interactive_shell(njs_opts_t *opts) { int flags; fd_set fds; @@ -1146,7 +1145,7 @@ njs_interactive_shell(njs_opts_t *opts, return NJS_ERROR; } - vm = njs_create_vm(opts, vm_options); + vm = njs_create_vm(opts); if (vm == NULL) { return NJS_ERROR; } From xeioex at nginx.com Fri Nov 10 01:27:40 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Fri, 10 Nov 2023 01:27:40 +0000 Subject: [njs] Shell: refactored njs_process_file(). Message-ID: details: https://hg.nginx.org/njs/rev/f936754f6f62 branches: changeset: 2232:f936754f6f62 user: Dmitry Volyntsev date: Thu Nov 09 17:09:16 2023 -0800 description: Shell: refactored njs_process_file(). The reading part is separated into njs_read_file(). diffstat: external/njs_shell.c | 79 ++++++++++++++++++++++++++++++--------------------- 1 files changed, 47 insertions(+), 32 deletions(-) diffs (152 lines): diff -r f6e0a7270492 -r f936754f6f62 external/njs_shell.c --- a/external/njs_shell.c Thu Nov 09 11:49:25 2023 -0800 +++ b/external/njs_shell.c Thu Nov 09 17:09:16 2023 -0800 @@ -864,18 +864,15 @@ njs_process_events(void *runtime) static njs_int_t -njs_process_file(njs_opts_t *opts) +njs_read_file(njs_opts_t *opts, njs_str_t *content) { int fd; char *file; u_char *p, *end, *start; size_t size; ssize_t n; - njs_vm_t *vm; njs_int_t ret; - njs_str_t source, script; struct stat sb; - u_char buf[4096]; file = opts->file; @@ -898,27 +895,25 @@ njs_process_file(njs_opts_t *opts) goto close_fd; } - size = sizeof(buf); + size = 4096; if (S_ISREG(sb.st_mode) && sb.st_size) { size = sb.st_size; } - vm = NULL; - - source.length = 0; - source.start = realloc(NULL, size); - if (source.start == NULL) { + content->length = 0; + content->start = realloc(NULL, size); + if (content->start == NULL) { njs_stderror("alloc failed while reading '%s'\n", file); ret = NJS_ERROR; - goto done; + goto close_fd; } - p = source.start; + p = content->start; end = p + size; for ( ;; ) { - n = read(fd, buf, sizeof(buf)); + n = read(fd, p, end - p); if (n == 0) { break; @@ -928,34 +923,54 @@ njs_process_file(njs_opts_t *opts) njs_stderror("failed to read file: '%s' (%s)\n", file, strerror(errno)); ret = NJS_ERROR; - goto done; + goto close_fd; } - if (p + n > end) { + if (p + n == end) { size *= 2; - start = realloc(source.start, size); + start = realloc(content->start, size); if (start == NULL) { njs_stderror("alloc failed while reading '%s'\n", file); ret = NJS_ERROR; - goto done; + goto close_fd; } - source.start = start; + content->start = start; - p = source.start + source.length; - end = source.start + size; + p = content->start + content->length; + end = content->start + size; } - memcpy(p, buf, n); + p += n; + content->length += n; + } - p += n; - source.length += n; + ret = NJS_OK; + +close_fd: + + if (fd != STDIN_FILENO) { + (void) close(fd); } - vm = njs_create_vm(opts); - if (vm == NULL) { - ret = NJS_ERROR; + return ret; +} + + +static njs_int_t +njs_process_file(njs_opts_t *opts) +{ + u_char *p; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t source, script; + + vm = NULL; + source.start = NULL; + + ret = njs_read_file(opts, &source); + if (ret != NJS_OK) { goto done; } @@ -975,6 +990,12 @@ njs_process_file(njs_opts_t *opts) } } + vm = njs_create_vm(opts); + if (vm == NULL) { + ret = NJS_ERROR; + goto done; + } + ret = njs_process_script(vm, njs_vm_external_ptr(vm), &script); if (ret != NJS_OK) { ret = NJS_ERROR; @@ -993,12 +1014,6 @@ done: free(source.start); } -close_fd: - - if (fd != STDIN_FILENO) { - (void) close(fd); - } - return ret; } From thresh at nginx.com Fri Nov 10 03:43:09 2023 From: thresh at nginx.com (=?iso-8859-1?q?Konstantin_Pavlov?=) Date: Thu, 09 Nov 2023 19:43:09 -0800 Subject: [PATCH] Linux packages: added Ubuntu 23.04 "mantic" Message-ID: # HG changeset patch # User Konstantin Pavlov # Date 1699587725 28800 # Thu Nov 09 19:42:05 2023 -0800 # Node ID d9dba9159ddf3adaf0263f17f3ed69228aa6c972 # Parent 5cfaf094e2a041d3fa6eaf58799f575295e451ab Linux packages: added Ubuntu 23.04 "mantic". diff -r 5cfaf094e2a0 -r d9dba9159ddf xml/en/linux_packages.xml --- a/xml/en/linux_packages.xml Tue Oct 24 15:16:17 2023 -0700 +++ b/xml/en/linux_packages.xml Thu Nov 09 19:42:05 2023 -0800 @@ -7,7 +7,7 @@
+ rev="92">
@@ -92,6 +92,11 @@ versions: x86_64, aarch64/arm64 + +23.10 “mantic” +x86_64, aarch64/arm64 + + diff -r 5cfaf094e2a0 -r d9dba9159ddf xml/ru/linux_packages.xml --- a/xml/ru/linux_packages.xml Tue Oct 24 15:16:17 2023 -0700 +++ b/xml/ru/linux_packages.xml Thu Nov 09 19:42:05 2023 -0800 @@ -7,7 +7,7 @@
+ rev="92">
@@ -92,6 +92,11 @@ x86_64, aarch64/arm64 + +23.10 “mantic” +x86_64, aarch64/arm64 + + From thresh at nginx.com Fri Nov 10 03:51:20 2023 From: thresh at nginx.com (Konstantin Pavlov) Date: Thu, 9 Nov 2023 19:51:20 -0800 Subject: [PATCH] Linux packages: documented nginx-module-otel package In-Reply-To: References: Message-ID: Hi, On 26/10/2023 10:26 AM, Maxim Dounin wrote: > Note that "nginx-authored" here looks misleading, as no nginx core > developers work on this module. > > Overall, I do support the clear distinction between nginx's own > modules and 3rd-party modules provided in the packages repository. > (But, as correctly noted by Konstantin, this should include njs as > well.) Indeed, I'll send patches to clarify the differences. -------------- next part -------------- An HTML attachment was scrubbed... URL: From vl at inspert.ru Fri Nov 10 09:11:55 2023 From: vl at inspert.ru (=?iso-8859-1?q?Vladimir_Homutov?=) Date: Fri, 10 Nov 2023 12:11:55 +0300 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start In-Reply-To: References: Message-ID: <6f957e137407d8f3f7e3.1699607515@vlws> It is no longer used since the refactoring in 8e5bf1bc87e2 (2008). src/http/ngx_http_request.c | 3 +-- src/http/ngx_http_request.h | 1 - 2 files changed, 1 insertions(+), 3 deletions(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: nginx-2.patch Type: text/x-patch Size: 1237 bytes Desc: not available URL: From vl at inspert.ru Fri Nov 10 09:11:53 2023 From: vl at inspert.ru (=?iso-8859-1?q?Vladimir_Homutov?=) Date: Fri, 10 Nov 2023 12:11:53 +0300 Subject: [PATCH 0 of 2] [patch] some issues found by gcc undef sanitizer In-Reply-To: References: Message-ID: > As already noted off-list, this is certainly not the only field > which might be not yet set when > ngx_http_alloc_large_header_buffer() is called. From the patch > context as shown, at least r->method_end and r->uri_start might > not be set as well, leading to similar overflows. And certainly > there are other fields as well. Agreed, there is a clear pattern in this case. I have updated the patch to test other cases as well. Also, I've created a separate patch to remove r->port_start, which is actually unused and looks like remnant of old refactoring. From vl at inspert.ru Fri Nov 10 09:11:54 2023 From: vl at inspert.ru (=?iso-8859-1?q?Vladimir_Homutov?=) Date: Fri, 10 Nov 2023 12:11:54 +0300 Subject: [PATCH 1 of 2] HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer In-Reply-To: References: Message-ID: <505e927eb7a75f0fdce4.1699607514@vlws> If URI is not fully parsed yet, some pointers are not set. As a result, the calculation of "new + (ptr - old)" expression may overflow. In such a case, just avoid calculating it, as value will be set correctly later by the parser in any case. The issue was found by GCC undefined behaviour sanitizer. src/http/ngx_http_request.c | 34 ++++++++++++++++++++++++++-------- 1 files changed, 26 insertions(+), 8 deletions(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: nginx-1.patch Type: text/x-patch Size: 2277 bytes Desc: not available URL: From arut at nginx.com Fri Nov 10 10:07:18 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Fri, 10 Nov 2023 14:07:18 +0400 Subject: [PATCH 0 of 3] Stream connection handling updates Message-ID: The patches introduce updates in Stream connection handling. - first patch implements peek mode for preread - second patch adds virtual servers support to Stream - third patch adds a "pass" directive which allows to jump from Stream to any other nginx listen -- Roman Arutyunyan From arut at nginx.com Fri Nov 10 10:07:19 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Fri, 10 Nov 2023 14:07:19 +0400 Subject: [PATCH 1 of 3] Stream: socket peek in preread phase In-Reply-To: References: Message-ID: <966331bb4936888ef2f0.1699610839@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1699456644 -14400 # Wed Nov 08 19:17:24 2023 +0400 # Node ID 966331bb4936888ef2f034aa2700c130514d0b57 # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Stream: socket peek in preread phase. Previously, preread buffer was always read out from socket, which made it impossible to terminate SSL on the connection without introducing additional SSL BIOs. The following patches will rely on this. Now, when possible, recv(MSG_PEEK) is used instead, which keeps data in socket. It's called if SSL is not already terminated and if an egde-triggered event method is used. For epoll, EPOLLRDHUP support is also required. diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -10,6 +10,10 @@ #include +static ngx_int_t ngx_stream_preread_peek(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); +static ngx_int_t ngx_stream_preread(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf); static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf); static char *ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf); @@ -203,8 +207,6 @@ ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) { - size_t size; - ssize_t n; ngx_int_t rc; ngx_connection_t *c; ngx_stream_core_srv_conf_t *cscf; @@ -217,56 +219,40 @@ ngx_stream_core_preread_phase(ngx_stream if (c->read->timedout) { rc = NGX_STREAM_OK; + goto done; + } - } else if (c->read->timer_set) { - rc = NGX_AGAIN; + if (!c->read->timer_set) { + rc = ph->handler(s); - } else { - rc = ph->handler(s); + if (rc != NGX_AGAIN) { + goto done; + } } - while (rc == NGX_AGAIN) { - + if (c->buffer == NULL) { + c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); if (c->buffer == NULL) { - c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); - if (c->buffer == NULL) { - rc = NGX_ERROR; - break; - } + rc = NGX_ERROR; + goto done; } - - size = c->buffer->end - c->buffer->last; - - if (size == 0) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); - rc = NGX_STREAM_BAD_REQUEST; - break; - } + } - if (c->read->eof) { - rc = NGX_STREAM_OK; - break; - } - - if (!c->read->ready) { - break; - } - - n = c->recv(c, c->buffer->last, size); + if (c->ssl == NULL + && (ngx_event_flags & NGX_USE_CLEAR_EVENT) + && ((ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0 +#if (NGX_HAVE_EPOLLRDHUP) + || ngx_use_epoll_rdhup +#endif + )) + { + rc = ngx_stream_preread_peek(s, ph); - if (n == NGX_ERROR || n == 0) { - rc = NGX_STREAM_OK; - break; - } + } else { + rc = ngx_stream_preread(s, ph); + } - if (n == NGX_AGAIN) { - break; - } - - c->buffer->last += n; - - rc = ph->handler(s); - } +done: if (rc == NGX_AGAIN) { if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -311,6 +297,95 @@ ngx_stream_core_preread_phase(ngx_stream } +static ngx_int_t +ngx_stream_preread_peek(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) +{ + ssize_t n; + ngx_int_t rc; + ngx_err_t err; + ngx_connection_t *c; + + c = s->connection; + + n = recv(c->fd, (char *) c->buffer->last, + c->buffer->end - c->buffer->last, MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "stream recv(MSG_PEEK): %z", n); + + if (n == -1) { + if (err == NGX_EAGAIN) { + return NGX_AGAIN; + } + + ngx_connection_error(c, err, "recv() failed"); + return NGX_STREAM_OK; + } + + if (n == 0) { + return NGX_STREAM_OK; + } + + c->buffer->last += n; + + rc = ph->handler(s); + + if (rc == NGX_AGAIN && c->buffer->last == c->buffer->end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + return NGX_STREAM_BAD_REQUEST; + } + + if (rc == NGX_AGAIN && c->read->pending_eof) { + return NGX_STREAM_OK; + } + + c->buffer->last = c->buffer->pos; + + return rc; +} + + +static ngx_int_t +ngx_stream_preread(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) +{ + ssize_t n; + ngx_int_t rc; + ngx_connection_t *c; + + c = s->connection; + + while (c->read->ready) { + + n = c->recv(c, c->buffer->last, c->buffer->end - c->buffer->last); + + if (n == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (n == NGX_ERROR || n == 0) { + return NGX_STREAM_OK; + } + + c->buffer->last += n; + + rc = ph->handler(s); + + if (rc != NGX_AGAIN) { + return rc; + } + + if (c->buffer->last == c->buffer->end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + return NGX_STREAM_BAD_REQUEST; + } + } + + return NGX_AGAIN; +} + + ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) From arut at nginx.com Fri Nov 10 10:07:20 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Fri, 10 Nov 2023 14:07:20 +0400 Subject: [PATCH 2 of 3] Stream: virtual servers In-Reply-To: References: Message-ID: <1d3464283405a4d8ac54.1699610840@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1699035295 -14400 # Fri Nov 03 22:14:55 2023 +0400 # Node ID 1d3464283405a4d8ac54caae9bf1815c723f04c5 # Parent 966331bb4936888ef2f034aa2700c130514d0b57 Stream: virtual servers. Server name is taken either from ngx_stream_ssl_module or ngx_stream_ssl_preread_module. diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -16,16 +16,34 @@ static ngx_int_t ngx_stream_init_phases( ngx_stream_core_main_conf_t *cmcf); static ngx_int_t ngx_stream_init_phase_handlers(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf); -static ngx_int_t ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports, - ngx_stream_listen_t *listen); -static char *ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports); + +static ngx_int_t ngx_stream_add_addresses(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_port_t *port, + ngx_stream_listen_opt_t *lsopt); +static ngx_int_t ngx_stream_add_address(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_port_t *port, + ngx_stream_listen_opt_t *lsopt); +static ngx_int_t ngx_stream_add_server(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_addr_t *addr); + +static ngx_int_t ngx_stream_optimize_servers(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf, ngx_array_t *ports); +static ngx_int_t ngx_stream_server_names(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf, ngx_stream_conf_addr_t *addr); +static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two); +static int ngx_libc_cdecl ngx_stream_cmp_dns_wildcards(const void *one, + const void *two); + +static ngx_int_t ngx_stream_init_listening(ngx_conf_t *cf, + ngx_stream_conf_port_t *port); +static ngx_listening_t *ngx_stream_add_listening(ngx_conf_t *cf, + ngx_stream_conf_addr_t *addr); static ngx_int_t ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr); #if (NGX_HAVE_INET6) static ngx_int_t ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr); #endif -static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two); ngx_uint_t ngx_stream_max_module; @@ -74,10 +92,8 @@ static char * ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; - ngx_uint_t i, m, mi, s; + ngx_uint_t m, mi, s; ngx_conf_t pcf; - ngx_array_t ports; - ngx_stream_listen_t *listen; ngx_stream_module_t *module; ngx_stream_conf_ctx_t *ctx; ngx_stream_core_srv_conf_t **cscfp; @@ -251,21 +267,13 @@ ngx_stream_block(ngx_conf_t *cf, ngx_com return NGX_CONF_ERROR; } - if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_stream_conf_port_t)) - != NGX_OK) - { + /* optimize the lists of ports, addresses and server names */ + + if (ngx_stream_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { return NGX_CONF_ERROR; } - listen = cmcf->listen.elts; - - for (i = 0; i < cmcf->listen.nelts; i++) { - if (ngx_stream_add_ports(cf, &ports, &listen[i]) != NGX_OK) { - return NGX_CONF_ERROR; - } - } - - return ngx_stream_optimize_servers(cf, &ports); + return NGX_CONF_OK; } @@ -377,73 +385,295 @@ ngx_stream_init_phase_handlers(ngx_conf_ } -static ngx_int_t -ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports, - ngx_stream_listen_t *listen) +ngx_int_t +ngx_stream_add_listen(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_listen_opt_t *lsopt) { - in_port_t p; - ngx_uint_t i; - struct sockaddr *sa; - ngx_stream_conf_port_t *port; - ngx_stream_conf_addr_t *addr; + in_port_t p; + ngx_uint_t i; + struct sockaddr *sa; + ngx_stream_conf_port_t *port; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); - sa = listen->sockaddr; + if (cmcf->ports == NULL) { + cmcf->ports = ngx_array_create(cf->temp_pool, 2, + sizeof(ngx_stream_conf_port_t)); + if (cmcf->ports == NULL) { + return NGX_ERROR; + } + } + + sa = lsopt->sockaddr; p = ngx_inet_get_port(sa); - port = ports->elts; - for (i = 0; i < ports->nelts; i++) { + port = cmcf->ports->elts; + for (i = 0; i < cmcf->ports->nelts; i++) { - if (p == port[i].port - && listen->type == port[i].type - && sa->sa_family == port[i].family) + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) { - /* a port is already in the port list */ + continue; + } - port = &port[i]; - goto found; - } + /* a port is already in the port list */ + + return ngx_stream_add_addresses(cf, cscf, &port[i], lsopt); } /* add a port to the port list */ - port = ngx_array_push(ports); + port = ngx_array_push(cmcf->ports); if (port == NULL) { return NGX_ERROR; } port->family = sa->sa_family; - port->type = listen->type; + port->type = lsopt->type; port->port = p; + port->addrs.elts = NULL; + + return ngx_stream_add_address(cf, cscf, port, lsopt); +} + + +static ngx_int_t +ngx_stream_add_addresses(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_port_t *port, ngx_stream_listen_opt_t *lsopt) +{ + ngx_uint_t i, default_server, proxy_protocol, + protocols, protocols_prev; + ngx_stream_conf_addr_t *addr; +#if (NGX_STREAM_SSL) + ngx_uint_t ssl; +#endif + + /* + * we cannot compare whole sockaddr struct's as kernel + * may fill some fields in inherited sockaddr struct's + */ + + addr = port->addrs.elts; + + for (i = 0; i < port->addrs.nelts; i++) { + + if (ngx_cmp_sockaddr(lsopt->sockaddr, lsopt->socklen, + addr[i].opt.sockaddr, + addr[i].opt.socklen, 0) + != NGX_OK) + { + continue; + } + + /* the address is already in the address list */ + + if (ngx_stream_add_server(cf, cscf, &addr[i]) != NGX_OK) { + return NGX_ERROR; + } + + /* preserve default_server bit during listen options overwriting */ + default_server = addr[i].opt.default_server; + + proxy_protocol = lsopt->proxy_protocol || addr[i].opt.proxy_protocol; + protocols = lsopt->proxy_protocol; + protocols_prev = addr[i].opt.proxy_protocol; + +#if (NGX_STREAM_SSL) + ssl = lsopt->ssl || addr[i].opt.ssl; + protocols |= lsopt->ssl << 1; + protocols_prev |= addr[i].opt.ssl << 1; +#endif + + if (lsopt->set) { + + if (addr[i].opt.set) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate listen options for %V", + &addr[i].opt.addr_text); + return NGX_ERROR; + } + + addr[i].opt = *lsopt; + } + + /* check the duplicate "default" server for this address:port */ - if (ngx_array_init(&port->addrs, cf->temp_pool, 2, - sizeof(ngx_stream_conf_addr_t)) - != NGX_OK) - { - return NGX_ERROR; + if (lsopt->default_server) { + + if (default_server) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate default server for %V", + &addr[i].opt.addr_text); + return NGX_ERROR; + } + + default_server = 1; + addr[i].default_server = cscf; + } + + /* check for conflicting protocol options */ + + if ((protocols | protocols_prev) != protocols_prev) { + + /* options added */ + + if ((addr[i].opt.set && !lsopt->set) + || addr[i].protocols_changed + || (protocols | protocols_prev) != protocols) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols_prev; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else if ((protocols_prev | protocols) != protocols) { + + /* options removed */ + + if (lsopt->set + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else { + + /* the same options */ + + if ((lsopt->set && addr[i].protocols_changed) + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + } + + addr[i].opt.default_server = default_server; + addr[i].opt.proxy_protocol = proxy_protocol; +#if (NGX_STREAM_SSL) + addr[i].opt.ssl = ssl; +#endif + + return NGX_OK; } -found: + /* add the address to the addresses list that bound to this port */ + + return ngx_stream_add_address(cf, cscf, port, lsopt); +} + + +/* + * add the server address, the server names and the server core module + * configurations to the port list + */ + +static ngx_int_t +ngx_stream_add_address(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_port_t *port, ngx_stream_listen_opt_t *lsopt) +{ + ngx_stream_conf_addr_t *addr; + + if (port->addrs.elts == NULL) { + if (ngx_array_init(&port->addrs, cf->temp_pool, 4, + sizeof(ngx_stream_conf_addr_t)) + != NGX_OK) + { + return NGX_ERROR; + } + } addr = ngx_array_push(&port->addrs); if (addr == NULL) { return NGX_ERROR; } - addr->opt = *listen; + addr->opt = *lsopt; + addr->protocols = 0; + addr->protocols_set = 0; + addr->protocols_changed = 0; + addr->hash.buckets = NULL; + addr->hash.size = 0; + addr->wc_head = NULL; + addr->wc_tail = NULL; +#if (NGX_PCRE) + addr->nregex = 0; + addr->regex = NULL; +#endif + addr->default_server = cscf; + addr->servers.elts = NULL; + + return ngx_stream_add_server(cf, cscf, addr); +} + + +/* add the server core module configuration to the address:port */ + +static ngx_int_t +ngx_stream_add_server(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + ngx_stream_core_srv_conf_t **server; + + if (addr->servers.elts == NULL) { + if (ngx_array_init(&addr->servers, cf->temp_pool, 4, + sizeof(ngx_stream_core_srv_conf_t *)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + server = addr->servers.elts; + for (i = 0; i < addr->servers.nelts; i++) { + if (server[i] == cscf) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate listen %V", + &addr->opt.addr_text); + return NGX_ERROR; + } + } + } + + server = ngx_array_push(&addr->servers); + if (server == NULL) { + return NGX_ERROR; + } + + *server = cscf; return NGX_OK; } -static char * -ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) +static ngx_int_t +ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf, + ngx_array_t *ports) { - ngx_uint_t i, p, last, bind_wildcard; - ngx_listening_t *ls; - ngx_stream_port_t *stport; - ngx_stream_conf_port_t *port; - ngx_stream_conf_addr_t *addr; - ngx_stream_core_srv_conf_t *cscf; + ngx_uint_t p, a; + ngx_stream_conf_port_t *port; + ngx_stream_conf_addr_t *addr; + + if (ports == NULL) { + return NGX_OK; + } port = ports->elts; for (p = 0; p < ports->nelts; p++) { @@ -451,175 +681,191 @@ ngx_stream_optimize_servers(ngx_conf_t * ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts, sizeof(ngx_stream_conf_addr_t), ngx_stream_cmp_conf_addrs); - addr = port[p].addrs.elts; - last = port[p].addrs.nelts; - /* - * if there is the binding to the "*:port" then we need to bind() - * to the "*:port" only and ignore the other bindings + * check whether all name-based servers have the same + * configuration as a default server for given address:port */ - if (addr[last - 1].opt.wildcard) { - addr[last - 1].opt.bind = 1; - bind_wildcard = 1; + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { - } else { - bind_wildcard = 0; + if (addr[a].servers.nelts > 1 +#if (NGX_PCRE) + || addr[a].default_server->captures +#endif + ) + { + if (ngx_stream_server_names(cf, cmcf, &addr[a]) != NGX_OK) { + return NGX_ERROR; + } + } } - i = 0; - - while (i < last) { - - if (bind_wildcard && !addr[i].opt.bind) { - i++; - continue; - } - - ls = ngx_create_listening(cf, addr[i].opt.sockaddr, - addr[i].opt.socklen); - if (ls == NULL) { - return NGX_CONF_ERROR; - } - - ls->addr_ntop = 1; - ls->handler = ngx_stream_init_connection; - ls->pool_size = 256; - ls->type = addr[i].opt.type; - - cscf = addr->opt.ctx->srv_conf[ngx_stream_core_module.ctx_index]; - - ls->logp = cscf->error_log; - ls->log.data = &ls->addr_text; - ls->log.handler = ngx_accept_log_error; - - ls->backlog = addr[i].opt.backlog; - ls->rcvbuf = addr[i].opt.rcvbuf; - ls->sndbuf = addr[i].opt.sndbuf; - - ls->wildcard = addr[i].opt.wildcard; - - ls->keepalive = addr[i].opt.so_keepalive; -#if (NGX_HAVE_KEEPALIVE_TUNABLE) - ls->keepidle = addr[i].opt.tcp_keepidle; - ls->keepintvl = addr[i].opt.tcp_keepintvl; - ls->keepcnt = addr[i].opt.tcp_keepcnt; -#endif - -#if (NGX_HAVE_INET6) - ls->ipv6only = addr[i].opt.ipv6only; -#endif - -#if (NGX_HAVE_TCP_FASTOPEN) - ls->fastopen = addr[i].opt.fastopen; -#endif - -#if (NGX_HAVE_REUSEPORT) - ls->reuseport = addr[i].opt.reuseport; -#endif - - stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); - if (stport == NULL) { - return NGX_CONF_ERROR; - } - - ls->servers = stport; - - stport->naddrs = i + 1; - - switch (ls->sockaddr->sa_family) { -#if (NGX_HAVE_INET6) - case AF_INET6: - if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) { - return NGX_CONF_ERROR; - } - break; -#endif - default: /* AF_INET */ - if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) { - return NGX_CONF_ERROR; - } - break; - } - - addr++; - last--; + if (ngx_stream_init_listening(cf, &port[p]) != NGX_OK) { + return NGX_ERROR; } } - return NGX_CONF_OK; -} - - -static ngx_int_t -ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, - ngx_stream_conf_addr_t *addr) -{ - ngx_uint_t i; - struct sockaddr_in *sin; - ngx_stream_in_addr_t *addrs; - - stport->addrs = ngx_pcalloc(cf->pool, - stport->naddrs * sizeof(ngx_stream_in_addr_t)); - if (stport->addrs == NULL) { - return NGX_ERROR; - } - - addrs = stport->addrs; - - for (i = 0; i < stport->naddrs; i++) { - - sin = (struct sockaddr_in *) addr[i].opt.sockaddr; - addrs[i].addr = sin->sin_addr.s_addr; - - addrs[i].conf.ctx = addr[i].opt.ctx; -#if (NGX_STREAM_SSL) - addrs[i].conf.ssl = addr[i].opt.ssl; -#endif - addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; - addrs[i].conf.addr_text = addr[i].opt.addr_text; - } - return NGX_OK; } -#if (NGX_HAVE_INET6) - static ngx_int_t -ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, +ngx_stream_server_names(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf, ngx_stream_conf_addr_t *addr) { - ngx_uint_t i; - struct sockaddr_in6 *sin6; - ngx_stream_in6_addr_t *addrs6; + ngx_int_t rc; + ngx_uint_t n, s; + ngx_hash_init_t hash; + ngx_hash_keys_arrays_t ha; + ngx_stream_server_name_t *name; + ngx_stream_core_srv_conf_t **cscfp; +#if (NGX_PCRE) + ngx_uint_t regex, i; - stport->addrs = ngx_pcalloc(cf->pool, - stport->naddrs * sizeof(ngx_stream_in6_addr_t)); - if (stport->addrs == NULL) { + regex = 0; +#endif + + ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t)); + + ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ha.temp_pool == NULL) { return NGX_ERROR; } - addrs6 = stport->addrs; + ha.pool = cf->pool; + + if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { + goto failed; + } + + cscfp = addr->servers.elts; + + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { - for (i = 0; i < stport->naddrs; i++) { +#if (NGX_PCRE) + if (name[n].regex) { + regex++; + continue; + } +#endif - sin6 = (struct sockaddr_in6 *) addr[i].opt.sockaddr; - addrs6[i].addr6 = sin6->sin6_addr; + rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, + NGX_HASH_WILDCARD_KEY); + + if (rc == NGX_ERROR) { + goto failed; + } - addrs6[i].conf.ctx = addr[i].opt.ctx; -#if (NGX_STREAM_SSL) - addrs6[i].conf.ssl = addr[i].opt.ssl; -#endif - addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; - addrs6[i].conf.addr_text = addr[i].opt.addr_text; + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid server name or wildcard \"%V\" on %V", + &name[n].name, &addr->opt.addr_text); + goto failed; + } + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "conflicting server name \"%V\" on %V, ignored", + &name[n].name, &addr->opt.addr_text); + } + } + } + + hash.key = ngx_hash_key_lc; + hash.max_size = cmcf->server_names_hash_max_size; + hash.bucket_size = cmcf->server_names_hash_bucket_size; + hash.name = "server_names_hash"; + hash.pool = cf->pool; + + if (ha.keys.nelts) { + hash.hash = &addr->hash; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { + goto failed; + } } - return NGX_OK; -} + if (ha.dns_wc_head.nelts) { + + ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_stream_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, + ha.dns_wc_head.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ha.dns_wc_tail.nelts) { + + ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_stream_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, + ha.dns_wc_tail.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash; + } + + ngx_destroy_pool(ha.temp_pool); + +#if (NGX_PCRE) + + if (regex == 0) { + return NGX_OK; + } + + addr->nregex = regex; + addr->regex = ngx_palloc(cf->pool, + regex * sizeof(ngx_stream_server_name_t)); + if (addr->regex == NULL) { + return NGX_ERROR; + } + + i = 0; + + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + if (name[n].regex) { + addr->regex[i++] = name[n]; + } + } + } #endif + return NGX_OK; + +failed: + + ngx_destroy_pool(ha.temp_pool); + + return NGX_ERROR; +} + static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two) @@ -630,12 +876,12 @@ ngx_stream_cmp_conf_addrs(const void *on second = (ngx_stream_conf_addr_t *) two; if (first->opt.wildcard) { - /* a wildcard must be the last resort, shift it to the end */ + /* a wildcard address must be the last resort, shift it to the end */ return 1; } if (second->opt.wildcard) { - /* a wildcard must be the last resort, shift it to the end */ + /* a wildcard address must be the last resort, shift it to the end */ return -1; } @@ -653,3 +899,289 @@ ngx_stream_cmp_conf_addrs(const void *on return 0; } + + +static int ngx_libc_cdecl +ngx_stream_cmp_dns_wildcards(const void *one, const void *two) +{ + ngx_hash_key_t *first, *second; + + first = (ngx_hash_key_t *) one; + second = (ngx_hash_key_t *) two; + + return ngx_dns_strcmp(first->key.data, second->key.data); +} + + +static ngx_int_t +ngx_stream_init_listening(ngx_conf_t *cf, ngx_stream_conf_port_t *port) +{ + ngx_uint_t i, last, bind_wildcard; + ngx_listening_t *ls; + ngx_stream_port_t *hport; + ngx_stream_conf_addr_t *addr; + + addr = port->addrs.elts; + last = port->addrs.nelts; + + /* + * If there is a binding to an "*:port" then we need to bind() to + * the "*:port" only and ignore other implicit bindings. The bindings + * have been already sorted: explicit bindings are on the start, then + * implicit bindings go, and wildcard binding is in the end. + */ + + if (addr[last - 1].opt.wildcard) { + addr[last - 1].opt.bind = 1; + bind_wildcard = 1; + + } else { + bind_wildcard = 0; + } + + i = 0; + + while (i < last) { + + if (bind_wildcard && !addr[i].opt.bind) { + i++; + continue; + } + + ls = ngx_stream_add_listening(cf, &addr[i]); + if (ls == NULL) { + return NGX_ERROR; + } + + hport = ngx_pcalloc(cf->pool, sizeof(ngx_stream_port_t)); + if (hport == NULL) { + return NGX_ERROR; + } + + ls->servers = hport; + + hport->naddrs = i + 1; + + switch (ls->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + if (ngx_stream_add_addrs6(cf, hport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; +#endif + default: /* AF_INET */ + if (ngx_stream_add_addrs(cf, hport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; + } + + addr++; + last--; + } + + return NGX_OK; +} + + +static ngx_listening_t * +ngx_stream_add_listening(ngx_conf_t *cf, ngx_stream_conf_addr_t *addr) +{ + ngx_listening_t *ls; + ngx_stream_core_srv_conf_t *cscf; + + ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen); + if (ls == NULL) { + return NULL; + } + + ls->addr_ntop = 1; + + ls->handler = ngx_stream_init_connection; + + cscf = addr->default_server; + ls->pool_size = 256; + + ls->logp = cscf->error_log; + ls->log.data = &ls->addr_text; + ls->log.handler = ngx_accept_log_error; + +#if (NGX_WIN32) + { + ngx_iocp_conf_t *iocpcf = NULL; + + if (ngx_get_conf(cf->cycle->conf_ctx, ngx_events_module)) { + iocpcf = ngx_event_get_conf(cf->cycle->conf_ctx, ngx_iocp_module); + } + if (iocpcf && iocpcf->acceptex_read) { + ls->post_accept_buffer_size = cscf->client_header_buffer_size; + } + } +#endif + + ls->type = addr->opt.type; + ls->backlog = addr->opt.backlog; + ls->rcvbuf = addr->opt.rcvbuf; + ls->sndbuf = addr->opt.sndbuf; + + ls->keepalive = addr->opt.so_keepalive; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = addr->opt.tcp_keepidle; + ls->keepintvl = addr->opt.tcp_keepintvl; + ls->keepcnt = addr->opt.tcp_keepcnt; +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + ls->accept_filter = addr->opt.accept_filter; +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + ls->deferred_accept = addr->opt.deferred_accept; +#endif + +#if (NGX_HAVE_INET6) + ls->ipv6only = addr->opt.ipv6only; +#endif + +#if (NGX_HAVE_SETFIB) + ls->setfib = addr->opt.setfib; +#endif + +#if (NGX_HAVE_TCP_FASTOPEN) + ls->fastopen = addr->opt.fastopen; +#endif + +#if (NGX_HAVE_REUSEPORT) + ls->reuseport = addr->opt.reuseport; +#endif + + ls->wildcard = addr->opt.wildcard; + + return ls; +} + + +static ngx_int_t +ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *hport, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + struct sockaddr_in *sin; + ngx_stream_in_addr_t *addrs; + ngx_stream_virtual_names_t *vn; + + hport->addrs = ngx_pcalloc(cf->pool, + hport->naddrs * sizeof(ngx_stream_in_addr_t)); + if (hport->addrs == NULL) { + return NGX_ERROR; + } + + addrs = hport->addrs; + + for (i = 0; i < hport->naddrs; i++) { + + sin = (struct sockaddr_in *) addr[i].opt.sockaddr; + addrs[i].addr = sin->sin_addr.s_addr; + addrs[i].conf.default_server = addr[i].default_server; +#if (NGX_STREAM_SSL) + addrs[i].conf.ssl = addr[i].opt.ssl; +#endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_palloc(cf->pool, sizeof(ngx_stream_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif + } + + return NGX_OK; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *hport, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + struct sockaddr_in6 *sin6; + ngx_stream_in6_addr_t *addrs6; + ngx_stream_virtual_names_t *vn; + + hport->addrs = ngx_pcalloc(cf->pool, + hport->naddrs * sizeof(ngx_stream_in6_addr_t)); + if (hport->addrs == NULL) { + return NGX_ERROR; + } + + addrs6 = hport->addrs; + + for (i = 0; i < hport->naddrs; i++) { + + sin6 = (struct sockaddr_in6 *) addr[i].opt.sockaddr; + addrs6[i].addr6 = sin6->sin6_addr; + addrs6[i].conf.default_server = addr[i].default_server; +#if (NGX_STREAM_SSL) + addrs6[i].conf.ssl = addr[i].opt.ssl; +#endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_palloc(cf->pool, sizeof(ngx_stream_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs6[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif + } + + return NGX_OK; +} + +#endif diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h --- a/src/stream/ngx_stream.h +++ b/src/stream/ngx_stream.h @@ -45,74 +45,39 @@ typedef struct { socklen_t socklen; ngx_str_t addr_text; - /* server ctx */ - ngx_stream_conf_ctx_t *ctx; - + unsigned set:1; + unsigned default_server:1; unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif + unsigned deferred_accept:1; unsigned reuseport:1; unsigned so_keepalive:2; unsigned proxy_protocol:1; + + int backlog; + int rcvbuf; + int sndbuf; + int type; +#if (NGX_HAVE_SETFIB) + int setfib; +#endif +#if (NGX_HAVE_TCP_FASTOPEN) + int fastopen; +#endif #if (NGX_HAVE_KEEPALIVE_TUNABLE) int tcp_keepidle; int tcp_keepintvl; int tcp_keepcnt; #endif - int backlog; - int rcvbuf; - int sndbuf; -#if (NGX_HAVE_TCP_FASTOPEN) - int fastopen; + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + char *accept_filter; #endif - int type; -} ngx_stream_listen_t; - - -typedef struct { - ngx_stream_conf_ctx_t *ctx; - ngx_str_t addr_text; - unsigned ssl:1; - unsigned proxy_protocol:1; -} ngx_stream_addr_conf_t; - -typedef struct { - in_addr_t addr; - ngx_stream_addr_conf_t conf; -} ngx_stream_in_addr_t; - - -#if (NGX_HAVE_INET6) - -typedef struct { - struct in6_addr addr6; - ngx_stream_addr_conf_t conf; -} ngx_stream_in6_addr_t; - -#endif - - -typedef struct { - /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ - void *addrs; - ngx_uint_t naddrs; -} ngx_stream_port_t; - - -typedef struct { - int family; - int type; - in_port_t port; - ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ -} ngx_stream_conf_port_t; - - -typedef struct { - ngx_stream_listen_t opt; -} ngx_stream_conf_addr_t; +} ngx_stream_listen_opt_t; typedef enum { @@ -153,7 +118,6 @@ typedef struct { typedef struct { ngx_array_t servers; /* ngx_stream_core_srv_conf_t */ - ngx_array_t listen; /* ngx_stream_listen_t */ ngx_stream_phase_engine_t phase_engine; @@ -163,16 +127,24 @@ typedef struct { ngx_array_t prefix_variables; /* ngx_stream_variable_t */ ngx_uint_t ncaptures; + ngx_uint_t server_names_hash_max_size; + ngx_uint_t server_names_hash_bucket_size; + ngx_uint_t variables_hash_max_size; ngx_uint_t variables_hash_bucket_size; ngx_hash_keys_arrays_t *variables_keys; + ngx_array_t *ports; + ngx_stream_phase_t phases[NGX_STREAM_LOG_PHASE + 1]; } ngx_stream_core_main_conf_t; typedef struct { + /* array of the ngx_stream_server_name_t, "server_name" directive */ + ngx_array_t server_names; + ngx_stream_content_handler_pt handler; ngx_stream_conf_ctx_t *ctx; @@ -180,6 +152,8 @@ typedef struct { u_char *file_name; ngx_uint_t line; + ngx_str_t server_name; + ngx_flag_t tcp_nodelay; size_t preread_buffer_size; ngx_msec_t preread_timeout; @@ -191,10 +165,99 @@ typedef struct { ngx_msec_t proxy_protocol_timeout; - ngx_uint_t listen; /* unsigned listen:1; */ + unsigned listen:1; +#if (NGX_PCRE) + unsigned captures:1; +#endif } ngx_stream_core_srv_conf_t; +/* list of structures to find core_srv_conf quickly at run time */ + + +typedef struct { +#if (NGX_PCRE) + ngx_stream_regex_t *regex; +#endif + ngx_stream_core_srv_conf_t *server; /* virtual name server conf */ + ngx_str_t name; +} ngx_stream_server_name_t; + + +typedef struct { + ngx_hash_combined_t names; + + ngx_uint_t nregex; + ngx_stream_server_name_t *regex; +} ngx_stream_virtual_names_t; + + +typedef struct { + /* the default server configuration for this address:port */ + ngx_stream_core_srv_conf_t *default_server; + + ngx_stream_virtual_names_t *virtual_names; + + ngx_str_t addr_text; + unsigned ssl:1; + unsigned proxy_protocol:1; +} ngx_stream_addr_conf_t; + + +typedef struct { + in_addr_t addr; + ngx_stream_addr_conf_t conf; +} ngx_stream_in_addr_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr6; + ngx_stream_addr_conf_t conf; +} ngx_stream_in6_addr_t; + +#endif + + +typedef struct { + /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ + void *addrs; + ngx_uint_t naddrs; +} ngx_stream_port_t; + + +typedef struct { + int family; + int type; + in_port_t port; + ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ +} ngx_stream_conf_port_t; + + +typedef struct { + ngx_stream_listen_opt_t opt; + + unsigned protocols:3; + unsigned protocols_set:1; + unsigned protocols_changed:1; + + ngx_hash_t hash; + ngx_hash_wildcard_t *wc_head; + ngx_hash_wildcard_t *wc_tail; + +#if (NGX_PCRE) + ngx_uint_t nregex; + ngx_stream_server_name_t *regex; +#endif + + /* the default server configuration for this address:port */ + ngx_stream_core_srv_conf_t *default_server; + ngx_array_t servers; + /* array of ngx_stream_core_srv_conf_t */ +} ngx_stream_conf_addr_t; + + struct ngx_stream_session_s { uint32_t signature; /* "STRM" */ @@ -210,6 +273,8 @@ struct ngx_stream_session_s { void **main_conf; void **srv_conf; + ngx_stream_virtual_names_t *virtual_names; + ngx_stream_upstream_t *upstream; ngx_array_t *upstream_states; /* of ngx_stream_upstream_state_t */ @@ -283,6 +348,8 @@ typedef struct { #define NGX_STREAM_WRITE_BUFFERED 0x10 +ngx_int_t ngx_stream_add_listen(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_listen_opt_t *lsopt); void ngx_stream_core_run_phases(ngx_stream_session_t *s); ngx_int_t ngx_stream_core_generic_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph); @@ -290,6 +357,10 @@ ngx_int_t ngx_stream_core_preread_phase( ngx_stream_phase_handler_t *ph); ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph); +ngx_int_t ngx_stream_find_virtual_server(ngx_stream_session_t *s, + ngx_str_t *host, ngx_stream_core_srv_conf_t **cscfp); +ngx_int_t ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); void ngx_stream_init_connection(ngx_connection_t *c); diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -26,6 +26,8 @@ static char *ngx_stream_core_server(ngx_ void *conf); static char *ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -46,6 +48,20 @@ static ngx_command_t ngx_stream_core_co offsetof(ngx_stream_core_main_conf_t, variables_hash_bucket_size), NULL }, + { ngx_string("server_names_hash_max_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, server_names_hash_max_size), + NULL }, + + { ngx_string("server_names_hash_bucket_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, server_names_hash_bucket_size), + NULL }, + { ngx_string("server"), NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_stream_core_server, @@ -60,6 +76,13 @@ static ngx_command_t ngx_stream_core_co 0, NULL }, + { ngx_string("server_name"), + NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_core_server_name, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("error_log"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_stream_core_error_log, @@ -413,6 +436,149 @@ ngx_stream_core_content_phase(ngx_stream } +ngx_int_t +ngx_stream_find_virtual_server(ngx_stream_session_t *s, + ngx_str_t *host, ngx_stream_core_srv_conf_t **cscfp) +{ + ngx_stream_core_srv_conf_t *cscf; + + if (s->virtual_names == NULL) { + return NGX_DECLINED; + } + + cscf = ngx_hash_find_combined(&s->virtual_names->names, + ngx_hash_key(host->data, host->len), + host->data, host->len); + + if (cscf) { + *cscfp = cscf; + return NGX_OK; + } + +#if (NGX_PCRE) + + if (host->len && s->virtual_names->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_stream_server_name_t *sn; + + sn = s->virtual_names->regex; + + for (i = 0; i < s->virtual_names->nregex; i++) { + + n = ngx_stream_regex_exec(s, sn[i].regex, host); + + if (n == NGX_DECLINED) { + continue; + } + + if (n == NGX_OK) { + *cscfp = sn[i].server; + return NGX_OK; + } + + return NGX_ERROR; + } + } + +#endif /* NGX_PCRE */ + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) +{ + u_char *h, ch; + size_t i, dot_pos, host_len; + + enum { + sw_usual = 0, + sw_literal, + sw_rest + } state; + + dot_pos = host->len; + host_len = host->len; + + h = host->data; + + state = sw_usual; + + for (i = 0; i < host->len; i++) { + ch = h[i]; + + switch (ch) { + + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + + case ':': + if (state == sw_usual) { + host_len = i; + state = sw_rest; + } + break; + + case '[': + if (i == 0) { + state = sw_literal; + } + break; + + case ']': + if (state == sw_literal) { + host_len = i + 1; + state = sw_rest; + } + break; + + default: + + if (ngx_path_separator(ch)) { + return NGX_DECLINED; + } + + if (ch <= 0x20 || ch == 0x7f) { + return NGX_DECLINED; + } + + if (ch >= 'A' && ch <= 'Z') { + alloc = 1; + } + + break; + } + } + + if (dot_pos == host_len - 1) { + host_len--; + } + + if (host_len == 0) { + return NGX_DECLINED; + } + + if (alloc) { + host->data = ngx_pnalloc(pool, host_len); + if (host->data == NULL) { + return NGX_ERROR; + } + + ngx_strlow(host->data, h, host_len); + } + + host->len = host_len; + + return NGX_OK; +} + + static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf) { @@ -437,11 +603,8 @@ ngx_stream_core_create_main_conf(ngx_con return NULL; } - if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_stream_listen_t)) - != NGX_OK) - { - return NULL; - } + cmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT; + cmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT; cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT; cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT; @@ -455,6 +618,14 @@ ngx_stream_core_init_main_conf(ngx_conf_ { ngx_stream_core_main_conf_t *cmcf = conf; + ngx_conf_init_uint_value(cmcf->server_names_hash_max_size, 512); + ngx_conf_init_uint_value(cmcf->server_names_hash_bucket_size, + ngx_cacheline_size); + + cmcf->server_names_hash_bucket_size = + ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size); + + ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024); ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64); @@ -486,6 +657,13 @@ ngx_stream_core_create_srv_conf(ngx_conf * cscf->error_log = NULL; */ + if (ngx_array_init(&cscf->server_names, cf->temp_pool, 4, + sizeof(ngx_stream_server_name_t)) + != NGX_OK) + { + return NULL; + } + cscf->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; cscf->resolver_timeout = NGX_CONF_UNSET_MSEC; @@ -504,6 +682,9 @@ ngx_stream_core_merge_srv_conf(ngx_conf_ ngx_stream_core_srv_conf_t *prev = parent; ngx_stream_core_srv_conf_t *conf = child; + ngx_str_t name; + ngx_stream_server_name_t *sn; + ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout, 30000); @@ -551,6 +732,37 @@ ngx_stream_core_merge_srv_conf(ngx_conf_ ngx_conf_merge_msec_value(conf->preread_timeout, prev->preread_timeout, 30000); + if (conf->server_names.nelts == 0) { + /* the array has 4 empty preallocated elements, so push cannot fail */ + sn = ngx_array_push(&conf->server_names); +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = conf; + ngx_str_set(&sn->name, ""); + } + + sn = conf->server_names.elts; + name = sn[0].name; + +#if (NGX_PCRE) + if (sn->regex) { + name.len++; + name.data--; + } else +#endif + + if (name.data[0] == '.') { + name.len--; + name.data++; + } + + conf->server_name.len = name.len; + conf->server_name.data = ngx_pstrdup(cf->pool, &name); + if (conf->server_name.data == NULL) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -650,11 +862,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, n { ngx_stream_core_srv_conf_t *cscf = conf; - ngx_str_t *value, size; - ngx_url_t u; - ngx_uint_t i, n, backlog; - ngx_stream_listen_t *ls, *als, *nls; - ngx_stream_core_main_conf_t *cmcf; + ngx_str_t *value, size; + ngx_url_t u; + ngx_uint_t i, n, backlog; + ngx_stream_listen_opt_t lsopt; cscf->listen = 1; @@ -675,51 +886,48 @@ ngx_stream_core_listen(ngx_conf_t *cf, n return NGX_CONF_ERROR; } - cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); - - ls = ngx_array_push(&cmcf->listen); - if (ls == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(ls, sizeof(ngx_stream_listen_t)); + ngx_memzero(&lsopt, sizeof(ngx_stream_listen_opt_t)); - ls->backlog = NGX_LISTEN_BACKLOG; - ls->rcvbuf = -1; - ls->sndbuf = -1; - ls->type = SOCK_STREAM; - ls->ctx = cf->ctx; - + lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; + lsopt.rcvbuf = -1; + lsopt.sndbuf = -1; #if (NGX_HAVE_TCP_FASTOPEN) - ls->fastopen = -1; + lsopt.fastopen = -1; #endif - #if (NGX_HAVE_INET6) - ls->ipv6only = 1; + lsopt.ipv6only = 1; #endif backlog = 0; for (i = 2; i < cf->args->nelts; i++) { + if (ngx_strcmp(value[i].data, "default_server") == 0 + || ngx_strcmp(value[i].data, "default") == 0) + { + lsopt.default_server = 1; + continue; + } + #if !(NGX_WIN32) if (ngx_strcmp(value[i].data, "udp") == 0) { - ls->type = SOCK_DGRAM; + lsopt.type = SOCK_DGRAM; continue; } #endif if (ngx_strcmp(value[i].data, "bind") == 0) { - ls->bind = 1; + lsopt.bind = 1; continue; } #if (NGX_HAVE_TCP_FASTOPEN) if (ngx_strncmp(value[i].data, "fastopen=", 9) == 0) { - ls->fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); - ls->bind = 1; + lsopt.fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); + lsopt.bind = 1; - if (ls->fastopen == NGX_ERROR) { + if (lsopt.fastopen == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid fastopen \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -730,10 +938,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, n #endif if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) { - ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); - ls->bind = 1; + lsopt.backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); + lsopt.bind = 1; - if (ls->backlog == NGX_ERROR || ls->backlog == 0) { + if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid backlog \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -748,10 +956,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, n size.len = value[i].len - 7; size.data = value[i].data + 7; - ls->rcvbuf = ngx_parse_size(&size); - ls->bind = 1; + lsopt.rcvbuf = ngx_parse_size(&size); + lsopt.bind = 1; - if (ls->rcvbuf == NGX_ERROR) { + if (lsopt.rcvbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid rcvbuf \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -764,10 +972,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, n size.len = value[i].len - 7; size.data = value[i].data + 7; - ls->sndbuf = ngx_parse_size(&size); - ls->bind = 1; + lsopt.sndbuf = ngx_parse_size(&size); + lsopt.bind = 1; - if (ls->sndbuf == NGX_ERROR) { + if (lsopt.sndbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid sndbuf \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -779,10 +987,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, n if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) if (ngx_strcmp(&value[i].data[10], "n") == 0) { - ls->ipv6only = 1; + lsopt.ipv6only = 1; } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { - ls->ipv6only = 0; + lsopt.ipv6only = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -791,7 +999,7 @@ ngx_stream_core_listen(ngx_conf_t *cf, n return NGX_CONF_ERROR; } - ls->bind = 1; + lsopt.bind = 1; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -803,8 +1011,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, n if (ngx_strcmp(value[i].data, "reuseport") == 0) { #if (NGX_HAVE_REUSEPORT) - ls->reuseport = 1; - ls->bind = 1; + lsopt.reuseport = 1; + lsopt.bind = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "reuseport is not supported " @@ -824,7 +1032,7 @@ ngx_stream_core_listen(ngx_conf_t *cf, n sslcf->file = cf->conf_file->file.name.data; sslcf->line = cf->conf_file->line; - ls->ssl = 1; + lsopt.ssl = 1; continue; #else @@ -838,10 +1046,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, n if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { - ls->so_keepalive = 1; + lsopt.so_keepalive = 1; } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { - ls->so_keepalive = 2; + lsopt.so_keepalive = 2; } else { @@ -860,8 +1068,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, n if (p > s.data) { s.len = p - s.data; - ls->tcp_keepidle = ngx_parse_time(&s, 1); - if (ls->tcp_keepidle == (time_t) NGX_ERROR) { + lsopt.tcp_keepidle = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepidle == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } @@ -876,8 +1084,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, n if (p > s.data) { s.len = p - s.data; - ls->tcp_keepintvl = ngx_parse_time(&s, 1); - if (ls->tcp_keepintvl == (time_t) NGX_ERROR) { + lsopt.tcp_keepintvl = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepintvl == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } @@ -887,19 +1095,19 @@ ngx_stream_core_listen(ngx_conf_t *cf, n if (s.data < end) { s.len = end - s.data; - ls->tcp_keepcnt = ngx_atoi(s.data, s.len); - if (ls->tcp_keepcnt == NGX_ERROR) { + lsopt.tcp_keepcnt = ngx_atoi(s.data, s.len); + if (lsopt.tcp_keepcnt == NGX_ERROR) { goto invalid_so_keepalive; } } - if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0 - && ls->tcp_keepcnt == 0) + if (lsopt.tcp_keepidle == 0 && lsopt.tcp_keepintvl == 0 + && lsopt.tcp_keepcnt == 0) { goto invalid_so_keepalive; } - ls->so_keepalive = 1; + lsopt.so_keepalive = 1; #else @@ -911,7 +1119,7 @@ ngx_stream_core_listen(ngx_conf_t *cf, n #endif } - ls->bind = 1; + lsopt.bind = 1; continue; @@ -926,7 +1134,7 @@ ngx_stream_core_listen(ngx_conf_t *cf, n } if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { - ls->proxy_protocol = 1; + lsopt.proxy_protocol = 1; continue; } @@ -935,27 +1143,27 @@ ngx_stream_core_listen(ngx_conf_t *cf, n return NGX_CONF_ERROR; } - if (ls->type == SOCK_DGRAM) { + if (lsopt.type == SOCK_DGRAM) { if (backlog) { return "\"backlog\" parameter is incompatible with \"udp\""; } #if (NGX_STREAM_SSL) - if (ls->ssl) { + if (lsopt.ssl) { return "\"ssl\" parameter is incompatible with \"udp\""; } #endif - if (ls->so_keepalive) { + if (lsopt.so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; } - if (ls->proxy_protocol) { + if (lsopt.proxy_protocol) { return "\"proxy_protocol\" parameter is incompatible with \"udp\""; } #if (NGX_HAVE_TCP_FASTOPEN) - if (ls->fastopen != -1) { + if (lsopt.fastopen != -1) { return "\"fastopen\" parameter is incompatible with \"udp\""; } #endif @@ -972,40 +1180,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, n } } - if (n != 0) { - nls = ngx_array_push(&cmcf->listen); - if (nls == NULL) { - return NGX_CONF_ERROR; - } - - *nls = *ls; - - } else { - nls = ls; - } - - nls->sockaddr = u.addrs[n].sockaddr; - nls->socklen = u.addrs[n].socklen; - nls->addr_text = u.addrs[n].name; - nls->wildcard = ngx_inet_wildcard(nls->sockaddr); + lsopt.sockaddr = u.addrs[n].sockaddr; + lsopt.socklen = u.addrs[n].socklen; + lsopt.addr_text = u.addrs[n].name; + lsopt.wildcard = ngx_inet_wildcard(lsopt.sockaddr); - als = cmcf->listen.elts; - - for (i = 0; i < cmcf->listen.nelts - 1; i++) { - if (nls->type != als[i].type) { - continue; - } - - if (ngx_cmp_sockaddr(als[i].sockaddr, als[i].socklen, - nls->sockaddr, nls->socklen, 1) - != NGX_OK) - { - continue; - } - - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "duplicate \"%V\" address and port pair", - &nls->addr_text); + if (ngx_stream_add_listen(cf, cscf, &lsopt) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1018,6 +1198,107 @@ ngx_stream_core_listen(ngx_conf_t *cf, n static char * +ngx_stream_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf = conf; + + u_char ch; + ngx_str_t *value; + ngx_uint_t i; + ngx_stream_server_name_t *sn; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + ch = value[i].data[0]; + + if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.')) + || (ch == '.' && value[i].len < 2)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "server name \"%V\" is invalid", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_strchr(value[i].data, '/')) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "server name \"%V\" has suspicious symbols", + &value[i]); + } + + sn = ngx_array_push(&cscf->server_names); + if (sn == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = cscf; + + if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) { + sn->name = cf->cycle->hostname; + + } else { + sn->name = value[i]; + } + + if (value[i].data[0] != '~') { + ngx_strlow(sn->name.data, sn->name.data, sn->name.len); + continue; + } + +#if (NGX_PCRE) + { + u_char *p; + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + if (value[i].len == 1) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty regex in server name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + value[i].len--; + value[i].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[i]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + for (p = value[i].data; p < value[i].data + value[i].len; p++) { + if (*p >= 'A' && *p <= 'Z') { + rc.options = NGX_REGEX_CASELESS; + break; + } + } + + sn->regex = ngx_stream_regex_compile(cf, &rc); + if (sn->regex == NULL) { + return NGX_CONF_ERROR; + } + + sn->name = value[i]; + cscf->captures = (rc.captures > 0); + } +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" " + "requires PCRE library", &value[i]); + + return NGX_CONF_ERROR; +#endif + } + + return NGX_CONF_OK; +} + + +static char * ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_stream_core_srv_conf_t *cscf = conf; diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -30,6 +30,7 @@ ngx_stream_init_connection(ngx_connectio struct sockaddr_in *sin; ngx_stream_in_addr_t *addr; ngx_stream_session_t *s; + ngx_stream_conf_ctx_t *ctx; ngx_stream_addr_conf_t *addr_conf; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; @@ -121,9 +122,12 @@ ngx_stream_init_connection(ngx_connectio return; } + ctx = addr_conf->default_server->ctx; + s->signature = NGX_STREAM_MODULE; - s->main_conf = addr_conf->ctx->main_conf; - s->srv_conf = addr_conf->ctx->srv_conf; + s->main_conf = ctx->main_conf; + s->srv_conf = ctx->srv_conf; + s->virtual_names = addr_conf->virtual_names; #if (NGX_STREAM_SSL) s->ssl = addr_conf->ssl; @@ -144,7 +148,7 @@ ngx_stream_init_connection(ngx_connectio ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA %sclient %*s connected to %V", c->number, c->type == SOCK_DGRAM ? "udp " : "", - len, text, &addr_conf->addr_text); + len, text, &c->listening->addr_text); c->log->connection = c->number; c->log->handler = ngx_stream_log_error; diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -458,7 +458,104 @@ ngx_stream_ssl_handshake_handler(ngx_con static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) { + ngx_int_t rc; + ngx_str_t host; + const char *servername; + ngx_connection_t *c; + ngx_stream_session_t *s; + ngx_stream_ssl_conf_t *sscf; + ngx_stream_core_srv_conf_t *cscf; + + c = ngx_ssl_get_connection(ssl_conn); + + if (c->ssl->handshaked) { + *ad = SSL_AD_NO_RENEGOTIATION; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + s = c->data; + + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: \"%s\"", servername); + + host.len = ngx_strlen(servername); + + if (host.len == 0) { + goto done; + } + + host.data = (u_char *) servername; + + rc = ngx_stream_validate_host(&host, c->pool, 1); + + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_DECLINED) { + goto done; + } + + rc = ngx_stream_find_virtual_server(s, &host, &cscf); + + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_DECLINED) { + goto done; + } + + s->srv_conf = cscf->ctx->srv_conf; + + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + ngx_set_connection_log(c, cscf->error_log); + + if (sscf->ssl.ctx) { + if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { + goto error; + } + + /* + * SSL_set_SSL_CTX() only changes certs as of 1.0.0d + * adjust other things we care about + */ + + SSL_set_verify(ssl_conn, SSL_CTX_get_verify_mode(sscf->ssl.ctx), + SSL_CTX_get_verify_callback(sscf->ssl.ctx)); + + SSL_set_verify_depth(ssl_conn, SSL_CTX_get_verify_depth(sscf->ssl.ctx)); + +#if OPENSSL_VERSION_NUMBER >= 0x009080dfL + /* only in 0.9.8m+ */ + SSL_clear_options(ssl_conn, SSL_get_options(ssl_conn) & + ~SSL_CTX_get_options(sscf->ssl.ctx)); +#endif + + SSL_set_options(ssl_conn, SSL_CTX_get_options(sscf->ssl.ctx)); + +#ifdef SSL_OP_NO_RENEGOTIATION + SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); +#endif + } + +done: + return SSL_TLSEXT_ERR_OK; + +error: + + *ad = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; } #endif diff --git a/src/stream/ngx_stream_ssl_preread_module.c b/src/stream/ngx_stream_ssl_preread_module.c --- a/src/stream/ngx_stream_ssl_preread_module.c +++ b/src/stream/ngx_stream_ssl_preread_module.c @@ -33,6 +33,8 @@ typedef struct { static ngx_int_t ngx_stream_ssl_preread_handler(ngx_stream_session_t *s); static ngx_int_t ngx_stream_ssl_preread_parse_record( ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last); +static ngx_int_t ngx_stream_ssl_preread_servername(ngx_stream_session_t *s, + ngx_str_t *servername); static ngx_int_t ngx_stream_ssl_preread_protocol_variable( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_preread_server_name_variable( @@ -187,6 +189,10 @@ ngx_stream_ssl_preread_handler(ngx_strea return NGX_DECLINED; } + if (rc == NGX_OK) { + return ngx_stream_ssl_preread_servername(s, &ctx->host); + } + if (rc != NGX_AGAIN) { return rc; } @@ -404,9 +410,6 @@ ngx_stream_ssl_preread_parse_record(ngx_ case sw_sni_host: ctx->host.len = (p[1] << 8) + p[2]; - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, - "ssl preread: SNI hostname \"%V\"", &ctx->host); - state = sw_ext; dst = NULL; size = ext; @@ -497,6 +500,56 @@ ngx_stream_ssl_preread_parse_record(ngx_ static ngx_int_t +ngx_stream_ssl_preread_servername(ngx_stream_session_t *s, + ngx_str_t *servername) +{ + ngx_int_t rc; + ngx_str_t host; + ngx_connection_t *c; + ngx_stream_core_srv_conf_t *cscf; + + c = s->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL preread server name: \"%V\"", servername); + + if (servername->len == 0) { + return NGX_OK; + } + + host = *servername; + + rc = ngx_stream_validate_host(&host, c->pool, 1); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + rc = ngx_stream_find_virtual_server(s, &host, &cscf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + s->srv_conf = cscf->ctx->srv_conf; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + ngx_set_connection_log(c, cscf->error_log); + + return NGX_OK; +} + + +static ngx_int_t ngx_stream_ssl_preread_protocol_variable(ngx_stream_session_t *s, ngx_variable_value_t *v, uintptr_t data) { From arut at nginx.com Fri Nov 10 10:07:21 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Fri, 10 Nov 2023 14:07:21 +0400 Subject: [PATCH 3 of 3] Stream: ngx_stream_pass_module In-Reply-To: References: Message-ID: <3cab85fe55272835674b.1699610841@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1699543504 -14400 # Thu Nov 09 19:25:04 2023 +0400 # Node ID 3cab85fe55272835674b7f1c296796955256d019 # Parent 1d3464283405a4d8ac54caae9bf1815c723f04c5 Stream: ngx_stream_pass_module. The module allows to pass connections from Stream to other modules such as HTTP or Mail, as well as back to Stream. Previously, this was only possible with proxying. Connections with preread buffer read out from socket cannot be passed. The module allows to terminate SSL selectively based on SNI. stream { server { listen 8000 default_server; ssl_preread on; ... } server { listen 8000; server_name foo.example.com; pass 8001; # to HTTP } server { listen 8000; server_name bar.example.com; ... } } http { server { listen 8001 ssl; ... location / { root html; } } } diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -1166,6 +1166,16 @@ if [ $STREAM != NO ]; then . auto/module fi + if [ $STREAM_PASS = YES ]; then + ngx_module_name=ngx_stream_pass_module + ngx_module_deps= + ngx_module_srcs=src/stream/ngx_stream_pass_module.c + ngx_module_libs= + ngx_module_link=$STREAM_PASS + + . auto/module + fi + if [ $STREAM_SET = YES ]; then ngx_module_name=ngx_stream_set_module ngx_module_deps= diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -127,6 +127,7 @@ STREAM_GEOIP=NO STREAM_MAP=YES STREAM_SPLIT_CLIENTS=YES STREAM_RETURN=YES +STREAM_PASS=YES STREAM_SET=YES STREAM_UPSTREAM_HASH=YES STREAM_UPSTREAM_LEAST_CONN=YES @@ -337,6 +338,7 @@ use the \"--with-mail_ssl_module\" optio --without-stream_split_clients_module) STREAM_SPLIT_CLIENTS=NO ;; --without-stream_return_module) STREAM_RETURN=NO ;; + --without-stream_pass_module) STREAM_PASS=NO ;; --without-stream_set_module) STREAM_SET=NO ;; --without-stream_upstream_hash_module) STREAM_UPSTREAM_HASH=NO ;; @@ -556,6 +558,7 @@ cat << END --without-stream_split_clients_module disable ngx_stream_split_clients_module --without-stream_return_module disable ngx_stream_return_module + --without-stream_pass_module disable ngx_stream_pass_module --without-stream_set_module disable ngx_stream_set_module --without-stream_upstream_hash_module disable ngx_stream_upstream_hash_module diff --git a/src/stream/ngx_stream_pass_module.c b/src/stream/ngx_stream_pass_module.c new file mode 100644 --- /dev/null +++ b/src/stream/ngx_stream_pass_module.c @@ -0,0 +1,245 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_addr_t *addr; + ngx_stream_complex_value_t *addr_value; +} ngx_stream_pass_srv_conf_t; + + +static void ngx_stream_pass_handler(ngx_stream_session_t *s); +static void *ngx_stream_pass_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_stream_pass_commands[] = { + + { ngx_string("pass"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_pass, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_pass_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_pass_create_srv_conf, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_pass_module = { + NGX_MODULE_V1, + &ngx_stream_pass_module_ctx, /* module conaddr */ + ngx_stream_pass_commands, /* module directives */ + NGX_STREAM_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_stream_pass_handler(ngx_stream_session_t *s) +{ + ngx_url_t u; + ngx_str_t url; + ngx_addr_t *addr; + ngx_uint_t i; + ngx_listening_t *ls; + ngx_connection_t *c; + ngx_stream_pass_srv_conf_t *pscf; + + c = s->connection; + + c->log->action = "passing connection to another module"; + + if (c->buffer && c->buffer->pos != c->buffer->last) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "cannot pass connection with preread data"); + goto failed; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_pass_module); + + addr = pscf->addr; + + if (addr == NULL) { + if (ngx_stream_complex_value(s, pscf->addr_value, &url) != NGX_OK) { + goto failed; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = url; + u.listen = 1; + u.no_resolve = 1; + + if (ngx_parse_url(s->connection->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "%s in pass \"%V\"", u.err, &u.url); + } + + goto failed; + } + + if (u.naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "no addresses in pass \"%V\"", &u.url); + goto failed; + } + + addr = &u.addrs[0]; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream pass addr: \"%V\"", &addr->name); + + ls = ngx_cycle->listening.elts; + + for (i = 0; i < ngx_cycle->listening.nelts; i++) { + if (ngx_cmp_sockaddr(ls[i].sockaddr, ls[i].socklen, + addr->sockaddr, addr->socklen, 1) + == NGX_OK) + { + c->listening = &ls[i]; + + c->data = NULL; + c->buffer = NULL; + + *c->log = c->listening->log; + c->log->handler = NULL; + c->log->data = NULL; + + c->listening->handler(c); + + return; + } + } + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "listen not found for \"%V\"", &addr->name); + + ngx_stream_finalize_session(s, NGX_STREAM_OK); + + return; + +failed: + + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); +} + + +static void * +ngx_stream_pass_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_pass_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_pass_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->addr = NULL; + * conf->addr_value = NULL; + */ + + return conf; +} + + +static char * +ngx_stream_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_pass_srv_conf_t *pscf = conf; + + ngx_url_t u; + ngx_str_t *value, *url; + ngx_stream_complex_value_t cv; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_compile_complex_value_t ccv; + + if (pscf->addr || pscf->addr_value) { + return "is duplicate"; + } + + cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module); + + cscf->handler = ngx_stream_pass_handler; + + value = cf->args->elts; + + url = &value[1]; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = url; + ccv.complex_value = &cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths) { + pscf->addr_value = ngx_palloc(cf->pool, + sizeof(ngx_stream_complex_value_t)); + if (pscf->addr_value == NULL) { + return NGX_CONF_ERROR; + } + + *pscf->addr_value = cv; + + return NGX_CONF_OK; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *url; + u.listen = 1; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"pass\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + if (u.naddrs == 0) { + return "has no addresses"; + } + + pscf->addr = &u.addrs[0]; + + return NGX_CONF_OK; +} From mdounin at mdounin.ru Sat Nov 11 17:37:31 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 11 Nov 2023 20:37:31 +0300 Subject: [PATCH] Linux packages: added Ubuntu 23.04 "mantic" In-Reply-To: References: Message-ID: Hello! On Thu, Nov 09, 2023 at 07:43:09PM -0800, Konstantin Pavlov wrote: > # HG changeset patch > # User Konstantin Pavlov > # Date 1699587725 28800 > # Thu Nov 09 19:42:05 2023 -0800 > # Node ID d9dba9159ddf3adaf0263f17f3ed69228aa6c972 > # Parent 5cfaf094e2a041d3fa6eaf58799f575295e451ab > Linux packages: added Ubuntu 23.04 "mantic". > > diff -r 5cfaf094e2a0 -r d9dba9159ddf xml/en/linux_packages.xml > --- a/xml/en/linux_packages.xml Tue Oct 24 15:16:17 2023 -0700 > +++ b/xml/en/linux_packages.xml Thu Nov 09 19:42:05 2023 -0800 > @@ -7,7 +7,7 @@ >
link="/en/linux_packages.html" > lang="en" > - rev="91"> > + rev="92"> > >
> > @@ -92,6 +92,11 @@ versions: > x86_64, aarch64/arm64 > > > + > +23.10 “mantic” > +x86_64, aarch64/arm64 > + > + > > > > diff -r 5cfaf094e2a0 -r d9dba9159ddf xml/ru/linux_packages.xml > --- a/xml/ru/linux_packages.xml Tue Oct 24 15:16:17 2023 -0700 > +++ b/xml/ru/linux_packages.xml Thu Nov 09 19:42:05 2023 -0800 > @@ -7,7 +7,7 @@ >
link="/ru/linux_packages.html" > lang="ru" > - rev="91"> > + rev="92"> > >
> > @@ -92,6 +92,11 @@ > x86_64, aarch64/arm64 > > > + > +23.10 “mantic” > +x86_64, aarch64/arm64 > + > + > > Looks good. -- Maxim Dounin http://mdounin.ru/ From pluknet at nginx.com Tue Nov 14 11:57:24 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 14 Nov 2023 15:57:24 +0400 Subject: QUIC: improved huffman decode debug tracing. In-Reply-To: <4d14bfec.6a40.18b85d7527d.Coremail.winshining@163.com> References: <4d14bfec.6a40.18b85d7527d.Coremail.winshining@163.com> Message-ID: <20231114115724.ub2d6qp5cbmsfj7x@Y9MQ9X2QVV> On Tue, Oct 31, 2023 at 09:06:03PM +0800, winshining wrote: > Previously, only HTTP2 used huffman encoding (gRPC is util now > HTTP2 based), as HTTP3 becomes available, both of them uses huffman > encoding. But existed debug log in huffman decode function is hard > coded using "http2" prefixes, if a client transports an incorrect > huffman encoded field value in an HTTP3 request, it will give an > erroneous log. With the patch, it will properly log a bad field value. While indeed it makes sense to adjust hardcoded "http2" prefixes, I don't think this is a reason to break ngx_http_huff_decode() API. (It was once broken when moving Huffman coding out of HTTP/2, but that was quite justified). > Alternatively, removing "http2" prefixes only is ok, but it can not > differentiate whether it is caused by an HTTP2 or an HTTP3 request. Affected messages are logged at the debug level anyway, so this should not be a problem, it can be easily seen from the context - by looking at the adjacent messages belonging to the connection. [ Note that your attachment is sent as application/octet-stream, I had to adjust my mailcap in order to include it in the reply.] > # HG changeset patch > # User XingY Wang > # Date 1698714765 -28800 > # Tue Oct 31 09:12:45 2023 +0800 > # Node ID 760857905505f91c9627f3bfeb5b6e496f4992a8 > # Parent 7ec761f0365f418511e30b82e9adf80bc56681df > QUIC: improved huffman decode debug tracing. > > Previously, only HTTP2 used huffman encoding (gRPC is util now > HTTP2 based), as HTTP3 becomes available, both of them uses huffman > encoding. But existed debug log in huffman decode function is hard > coded using "http2" prefixes, if a client transports an incorrect > huffman encoded field value in an HTTP3 request, it will give an > erroneous log. With the patch, it will properly log a bad field value. > Alternatively, removing "http2" prefixes only is ok, but it can not > differentiate whether it is caused by an HTTP2 or an HTTP3 request. > > diff -r 7ec761f0365f -r 760857905505 src/http/modules/ngx_http_grpc_module.c > --- a/src/http/modules/ngx_http_grpc_module.c Thu Oct 26 23:35:09 2023 +0300 > +++ b/src/http/modules/ngx_http_grpc_module.c Tue Oct 31 09:12:45 2023 +0800 > @@ -3189,6 +3189,7 @@ > if (ngx_http_huff_decode(&ctx->field_state, p, size, > &ctx->field_end, > ctx->field_rest == 0, > + NGX_HTTP_VERSION_20, > r->connection->log) > != NGX_OK) > { > @@ -3298,6 +3299,7 @@ > if (ngx_http_huff_decode(&ctx->field_state, p, size, > &ctx->field_end, > ctx->field_rest == 0, > + NGX_HTTP_VERSION_20, > r->connection->log) > != NGX_OK) > { > diff -r 7ec761f0365f -r 760857905505 src/http/ngx_http.h > --- a/src/http/ngx_http.h Thu Oct 26 23:35:09 2023 +0300 > +++ b/src/http/ngx_http.h Tue Oct 31 09:12:45 2023 +0800 > @@ -179,7 +179,7 @@ > > #if (NGX_HTTP_V2 || NGX_HTTP_V3) > ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, > - u_char **dst, ngx_uint_t last, ngx_log_t *log); > + u_char **dst, ngx_uint_t last, ngx_uint_t http_version, ngx_log_t *log); > size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, > ngx_uint_t lower); > #endif > diff -r 7ec761f0365f -r 760857905505 src/http/ngx_http_huff_decode.c > --- a/src/http/ngx_http_huff_decode.c Thu Oct 26 23:35:09 2023 +0300 > +++ b/src/http/ngx_http_huff_decode.c Tue Oct 31 09:12:45 2023 +0800 > @@ -2641,9 +2641,22 @@ > > ngx_int_t > ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, > - ngx_uint_t last, ngx_log_t *log) > + ngx_uint_t last, ngx_uint_t http_version, ngx_log_t *log) > { > u_char *end, ch, ending; > +#if (NGX_DEBUG) > + char *from = NULL; > + > + switch (http_version) { > + > + case NGX_HTTP_VERSION_30: > + from = "http3"; > + break; > + > + default: > + from = "http2"; > + } > +#endif > > ch = 0; > ending = 1; > @@ -2656,9 +2669,9 @@ > if (ngx_http_huff_decode_bits(state, &ending, ch >> 4, dst) > != NGX_OK) > { > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > - "http2 huffman decoding error at state %d: " > - "bad code 0x%Xd", *state, ch >> 4); > + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0, > + "%s huffman decoding error at state %d: " > + "bad code 0x%Xd", from, *state, ch >> 4); > > return NGX_ERROR; > } > @@ -2666,9 +2679,9 @@ > if (ngx_http_huff_decode_bits(state, &ending, ch & 0xf, dst) > != NGX_OK) > { > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > - "http2 huffman decoding error at state %d: " > - "bad code 0x%Xd", *state, ch & 0xf); > + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0, > + "%s huffman decoding error at state %d: " > + "bad code 0x%Xd", from, *state, ch & 0xf); > > return NGX_ERROR; > } > @@ -2676,9 +2689,9 @@ > > if (last) { > if (!ending) { > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, > - "http2 huffman decoding error: " > - "incomplete code 0x%Xd", ch); > + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > + "%s huffman decoding error: " > + "incomplete code 0x%Xd", from, ch); > > return NGX_ERROR; > } > diff -r 7ec761f0365f -r 760857905505 src/http/v2/ngx_http_v2.c > --- a/src/http/v2/ngx_http_v2.c Thu Oct 26 23:35:09 2023 +0300 > +++ b/src/http/v2/ngx_http_v2.c Tue Oct 31 09:12:45 2023 +0800 > @@ -1579,6 +1579,7 @@ > if (ngx_http_huff_decode(&h2c->state.field_state, pos, size, > &h2c->state.field_end, > h2c->state.field_rest == 0, > + NGX_HTTP_VERSION_20, > h2c->connection->log) > != NGX_OK) > { > diff -r 7ec761f0365f -r 760857905505 src/http/v3/ngx_http_v3_parse.c > --- a/src/http/v3/ngx_http_v3_parse.c Thu Oct 26 23:35:09 2023 +0300 > +++ b/src/http/v3/ngx_http_v3_parse.c Tue Oct 31 09:12:45 2023 +0800 > @@ -647,7 +647,8 @@ > > if (st->huffman) { > if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, > - st->length == 1, c->log) > + st->length == 1, NGX_HTTP_VERSION_30, > + c->log) > != NGX_OK) > { > return NGX_ERROR; This introduces complexity just for debugging purpose. Removing any mention of HTTP protocol version should be enough. Huffman coding routines do not depend on HTTP protocol versions, both HPACK and QPACK refer to the same part of specification, that is Appendix B of RFC 7541. Also, while looking at ngx_http_huff_decode() use-cases, I noticed that HTTP/3 doesn't log Huffman decoding errors, which it probably should because this is a user-induced error, and for consistency with other nginx core modules. # HG changeset patch # User Sergey Kandaurov # Date 1699959003 -14400 # Tue Nov 14 14:50:03 2023 +0400 # Node ID c458cd00bb0bca8804ed831474533a813bcfd134 # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Adjusted Huffman coding debug logging, missed in 7977:336084ff943b. Spotted by XingY Wang. diff --git a/src/http/ngx_http_huff_decode.c b/src/http/ngx_http_huff_decode.c --- a/src/http/ngx_http_huff_decode.c +++ b/src/http/ngx_http_huff_decode.c @@ -2657,7 +2657,7 @@ ngx_http_huff_decode(u_char *state, u_ch != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch >> 4); return NGX_ERROR; @@ -2667,7 +2667,7 @@ ngx_http_huff_decode(u_char *state, u_ch != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch & 0xf); return NGX_ERROR; @@ -2677,7 +2677,7 @@ ngx_http_huff_decode(u_char *state, u_ch if (last) { if (!ending) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error: " + "http huffman decoding error: " "incomplete code 0x%Xd", ch); return NGX_ERROR; # HG changeset patch # User Sergey Kandaurov # Date 1699961162 -14400 # Tue Nov 14 15:26:02 2023 +0400 # Node ID f366007dd23a6ce8e8427c1b3042781b618a2ade # Parent c458cd00bb0bca8804ed831474533a813bcfd134 HTTP/3: added Huffman decoding error logging. diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -650,6 +650,8 @@ ngx_http_v3_parse_literal(ngx_connection st->length == 1, c->log) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid encoded field line"); return NGX_ERROR; } From pluknet at nginx.com Tue Nov 14 12:34:50 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 14 Nov 2023 16:34:50 +0400 Subject: [PATCH] Linux packages: added Ubuntu 23.04 "mantic" In-Reply-To: References: Message-ID: <2DF04F45-4F5F-4A12-856E-DB208670C6D8@nginx.com> > On 10 Nov 2023, at 07:43, Konstantin Pavlov wrote: > > # HG changeset patch > # User Konstantin Pavlov > # Date 1699587725 28800 > # Thu Nov 09 19:42:05 2023 -0800 > # Node ID d9dba9159ddf3adaf0263f17f3ed69228aa6c972 > # Parent 5cfaf094e2a041d3fa6eaf58799f575295e451ab > Linux packages: added Ubuntu 23.04 "mantic". I'm pretty sure you meant 23.10. > > diff -r 5cfaf094e2a0 -r d9dba9159ddf xml/en/linux_packages.xml > --- a/xml/en/linux_packages.xml Tue Oct 24 15:16:17 2023 -0700 > +++ b/xml/en/linux_packages.xml Thu Nov 09 19:42:05 2023 -0800 > @@ -7,7 +7,7 @@ >
link="/en/linux_packages.html" > lang="en" > - rev="91"> > + rev="92"> > >
> > @@ -92,6 +92,11 @@ versions: > x86_64, aarch64/arm64 > > > + > +23.10 “mantic” > +x86_64, aarch64/arm64 > + > + > > > > diff -r 5cfaf094e2a0 -r d9dba9159ddf xml/ru/linux_packages.xml > --- a/xml/ru/linux_packages.xml Tue Oct 24 15:16:17 2023 -0700 > +++ b/xml/ru/linux_packages.xml Thu Nov 09 19:42:05 2023 -0800 > @@ -7,7 +7,7 @@ >
link="/ru/linux_packages.html" > lang="ru" > - rev="91"> > + rev="92"> > >
> > @@ -92,6 +92,11 @@ > x86_64, aarch64/arm64 > > > + > +23.10 “mantic” > +x86_64, aarch64/arm64 > + > + > > > -- Sergey Kandaurov From pluknet at nginx.com Tue Nov 14 13:10:00 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 14 Nov 2023 17:10:00 +0400 Subject: [patch] quic PTO counter fixes In-Reply-To: References: <20231025230855.gkob3yoigbmtazcl@Y9MQ9X2QVV> Message-ID: <5E0629DF-B0DC-4177-A3A3-72C96D495D71@nginx.com> > On 9 Nov 2023, at 19:14, Vladimir Homutov wrote: > >> On Thu, Oct 26, 2023 at 03:08:55AM +0400, Sergey Kandaurov wrote: >>> # HG changeset patch >>> # User Vladimir Khomutov >>> # Date 1697031803 -10800 >>> # Wed Oct 11 16:43:23 2023 +0300 >>> # Node ID 9ba2840e88f62343b3bd794e43900781dab43686 >>> # Parent 1f188102fbd944df797e8710f70cccee76164add >>> QUIC: fixed handling of PTO counter. >>> >>> The RFC 9002 clearly says in "6.2. Probe Timeout": >>> ... >>> As with loss detection, the PTO is per packet number space. >>> That is, a PTO value is computed per packet number space. >>> >>> Despite that, current code is using per-connection PTO counter. >>> For example, this may lead to situation when packet loss at handshake >>> level will affect PTO calculation for initial packets, preventing >>> send of new probes. >> >> Although PTO value is per packet number space, PTO backoff is not, >> see "6.2.1 Computing PTO": >> >> : When ack-eliciting packets in multiple packet number spaces are in flight, the >> : timer MUST be set to the earlier value of the Initial and Handshake packet >> : number spaces. > > And I read this fragment as: > - there are multiple timer values (i.e. per packet number space) > (and value is pto * backoff) > - we have to choose the earliest value > > The ngx_quic_pto() has nothing that depends on packet number space > (with the minor exception that we add max_ack_delay at application level > after the handshake) > So pto_count is the only thing that can make timer values to be > different in different packet number spaces. While ngx_quic_pto() doesn't depend on space (except max_ack_delay), it is currently implemented such way that it returns just duration, and it is the caller's obligation to apply it to the time of the last ack-eliciting packet (earliest among spaces) to get pto timeout. This is done in ngx_quic_set_lost_timer() and ngx_quic_pto_handler(), and seemingly missed for path validation in ngx_quic_set_path_timer() and for 3*PTO connection tear down in ngx_quic_close_connection(). Probably ngx_quic_pto() should be refactored to include this "duration => pto_timeout" calculation. This won't even break its signature since it's already passed ctx, which seems to be enough to get the earliest ack-eliciting packet among spaces. > >> >> But: >> >> : When a PTO timer expires, the PTO backoff MUST be increased <..> >> >> : This exponential reduction in the sender's rate is important because consecutive >> : PTOs might be caused by loss of packets or acknowledgments due to severe >> : congestion. Even when there are ack-eliciting packets in flight in multiple >> : packet number spaces, the exponential increase in PTO occurs across all spaces >> : to prevent excess load on the network. For example, a timeout in the Initial >> : packet number space doubles the length of the timeout in the Handshake packet >> : number space. > > yes, this really looks like contradiction. > At least I don't understand how it is possible to have PTO value > different by packet number space given the way we calculate it. > >> Even if that would be proven otherwise, I don't think the description >> provides detailed explanation. It describes a pretty specific use case, >> when both Initial and Handshake packet number spaces have in-flight packets >> with different PTO timeout (i.e. different f->last). Typically they are >> sent coalesced (e.g. CRYPTO frames for ServerHello and (at least) >> EncryptedExtensions TLS messages). >> In interop tests, though, it might be different: such packets may be >> sent separately, with Handshake packet thus having a later PTO timeout. >> If such, PTO timer will first fire for the Initial packet, then for Handshake, >> which will result in PTO backoff accumulated for each packet: >> >> t1: <- Initial (lost) >> t2: <- Handshake (lost) >> t1': pto(t1) timeout >> <- Initial (pto_count=1) >> t2': pto(t2) timeout >> <- Handshake (pto_count=2) >> t1'': pto(t1') timeout >> <- Initial (pto_count=3) >> >> So, I would supplement the description with the phrase that that's >> fair typically with uncoalesced packets seen in interop tests, and >> that the same is true vice verse with packet loss at initial packet >> number space affecting PTO backoff in handshake packet number space. >> >> But see above about PTO backoff increase across all spaces. > > I tend to think that it is better to leave things as is. > maybe RFC needs some better wording in this case. > > I've checked ngtcp2 and msquic and it it looks like both > handle pto counter per-connection too; > (see pto_count in ngtcp2 and QUIC_LOSS_DETECTION.ProbeCount in msquic) > Ok, thanks for deep looking into this. >>> >> >> This part of the change to reset pto_count >> for duplicate ACK or ACK for non-ack-eliciting frame >> contradicts the OnAckReceived example in RFC 9002, >> although I didn't found a format text in the RFC itself: >> >> OnAckReceived(ack, pn_space): >> ... >> // DetectAndRemoveAckedPackets finds packets that are newly >> // acknowledged and removes them from sent_packets. >> newly_acked_packets = >> DetectAndRemoveAckedPackets(ack, pn_space) >> // Nothing to do if there are no newly acked packets. >> if (newly_acked_packets.empty()): >> return >> >> // Update the RTT if the largest acknowledged is newly acked >> // and at least one ack-eliciting was newly acked. >> ... >> >> // Reset pto_count ... >> >> From which it follows that pto_count is reset >> (and RTT updated) for newly ack'ed packets only. > > yes, agreed. > the main issue is tracking of in-flight PING frames. >> >> I think the better fix would be to properly track in-flight PING frames. >> Moreover, the current behaviour of not tracking PING frames in ctx->sent >> prevents from a properly calculated PTO timeout: each time it is calculated >> against the original packet (with increasingly receding time to the past) >> that triggered the first PTO timeout, which doesn't result in exponentially >> increased PTO period as expected, but rather some bogus value. > > Agree. I've created a patch that fixes this. > (Will reply separately.) -- Sergey Kandaurov From xeioex at nginx.com Thu Nov 16 00:24:53 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Thu, 16 Nov 2023 00:24:53 +0000 Subject: [njs] Fetch: fixed Headers.set(). Message-ID: details: https://hg.nginx.org/njs/rev/0213cb43bfce branches: changeset: 2233:0213cb43bfce user: Dmitry Volyntsev date: Wed Nov 15 15:07:20 2023 -0800 description: Fetch: fixed Headers.set(). This closes #680 issue on Github. diffstat: nginx/ngx_js_fetch.c | 10 ++++++++++ nginx/t/js_fetch_objects.t | 5 +++++ 2 files changed, 15 insertions(+), 0 deletions(-) diffs (38 lines): diff -r f936754f6f62 -r 0213cb43bfce nginx/ngx_js_fetch.c --- a/nginx/ngx_js_fetch.c Thu Nov 09 17:09:16 2023 -0800 +++ b/nginx/ngx_js_fetch.c Wed Nov 15 15:07:20 2023 -0800 @@ -3637,9 +3637,19 @@ ngx_headers_js_ext_set(njs_vm_t *vm, njs ph = &(*ph)->next; *pp = NULL; } + + goto done; } } + ret = ngx_js_headers_append(vm, headers, name.start, name.length, + value.start, value.length); + if (ret != NJS_OK) { + return NJS_ERROR; + } + +done: + njs_value_undefined_set(retval); return NJS_OK; diff -r f936754f6f62 -r 0213cb43bfce nginx/t/js_fetch_objects.t --- a/nginx/t/js_fetch_objects.t Thu Nov 09 17:09:16 2023 -0800 +++ b/nginx/t/js_fetch_objects.t Wed Nov 15 15:07:20 2023 -0800 @@ -206,6 +206,11 @@ my $p0 = port(8080); h.set('a', '#'); return h.get('a'); }, '#'], + ['set on empty', () => { + var h = new Headers([]); + h.set('x-test', '1234'); + return h.get('x-test'); + }, '1234'], ]; run(r, tests); From xeioex at nginx.com Thu Nov 16 00:24:55 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Thu, 16 Nov 2023 00:24:55 +0000 Subject: [njs] Tests: added console test for stream module. Message-ID: details: https://hg.nginx.org/njs/rev/e4105b65d105 branches: changeset: 2234:e4105b65d105 user: Dmitry Volyntsev date: Wed Nov 15 15:08:18 2023 -0800 description: Tests: added console test for stream module. diffstat: nginx/t/stream_js_console.t | 142 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 142 insertions(+), 0 deletions(-) diffs (146 lines): diff -r 0213cb43bfce -r e4105b65d105 nginx/t/stream_js_console.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/stream_js_console.t Wed Nov 15 15:08:18 2023 -0800 @@ -0,0 +1,142 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, console object. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + + server { + listen 127.0.0.1:8080; + + js_preread test.log; + + proxy_pass 127.0.0.1:8090; + } + + server { + listen 127.0.0.1:8081; + + js_preread test.timer; + + proxy_pass 127.0.0.1:8090; + } +} + +EOF + +$t->write_file('test.js', < 0) { + s.off('upload'); + data = Buffer.from(data, 'base64'); + const object = JSON.parse(data); + console.log(object); + s.allow(); + } + }); + } + + function timer(s) { + s.on('upload', function (data) { + if (data.length > 0) { + s.off('upload'); + console.time('foo'); + setTimeout(function() { + console.timeEnd('foo'); + s.allow(); + }, 7); + } + }); + } + + export default { log, timer }; +EOF + +$t->run_daemon(\&stream_daemon, port(8090)); +$t->try_run('no njs console')->plan(4); +$t->waitforsocket('127.0.0.1:' . port(8090)); + +############################################################################### + +is(stream('127.0.0.1:' . port(8080))->io('eyJhIjpbIkIiLCJDIl19'), + 'eyJhIjpbIkIiLCJDIl19', 'log test'); +is(stream('127.0.0.1:' . port(8081))->io('timer'), 'timer', 'timer test'); + +$t->stop(); + +like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/, + 'console.log with object'); +like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/, + 'console.time foo'); + +############################################################################### + +sub stream_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8090), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or next; + + log2i("$client $buffer"); + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + close $client; + } +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +############################################################################### From winshining at 163.com Fri Nov 17 18:24:44 2023 From: winshining at 163.com (XingY Wang) Date: Sat, 18 Nov 2023 02:24:44 +0800 (CST) Subject: QUIC: improved huffman decode debug tracing. In-Reply-To: References: Message-ID: <4d0903e5.21.18bde87337f.Coremail.winshining@163.com> Hi, Sergey Kandaurov, thanks for your review. >Date: Tue, 14 Nov 2023 15:57:24 +0400 >From: Sergey Kandaurov >To: nginx-devel at nginx.org >Subject: Re: QUIC: improved huffman decode debug tracing. >Message-ID: <20231114115724.ub2d6qp5cbmsfj7x at Y9MQ9X2QVV> >Content-Type: text/plain; charset=us-ascii > >On Tue, Oct 31, 2023 at 09:06:03PM +0800, winshining wrote: >> Previously, only HTTP2 used huffman encoding (gRPC is util now >> HTTP2 based), as HTTP3 becomes available, both of them uses huffman >> encoding. But existed debug log in huffman decode function is hard >> coded using "http2" prefixes, if a client transports an incorrect >> huffman encoded field value in an HTTP3 request, it will give an >> erroneous log. With the patch, it will properly log a bad field value. > >While indeed it makes sense to adjust hardcoded "http2" prefixes, >I don't think this is a reason to break ngx_http_huff_decode() API. >(It was once broken when moving Huffman coding out of HTTP/2, >but that was quite justified). > >> Alternatively, removing "http2" prefixes only is ok, but it can not >> differentiate whether it is caused by an HTTP2 or an HTTP3 request. > >Affected messages are logged at the debug level anyway, so this >should not be a problem, it can be easily seen from the context - >by looking at the adjacent messages belonging to the connection. Yes. I didn't find any error in log when I debugged a client which showed the request didn't work until I turned on the debug level. > >[ Note that your attachment is sent as application/octet-stream, >I had to adjust my mailcap in order to include it in the reply.] > >> # HG changeset patch >> # User XingY Wang >> # Date 1698714765 -28800 >> # Tue Oct 31 09:12:45 2023 +0800 >> # Node ID 760857905505f91c9627f3bfeb5b6e496f4992a8 >> # Parent 7ec761f0365f418511e30b82e9adf80bc56681df >> QUIC: improved huffman decode debug tracing. >> >> Previously, only HTTP2 used huffman encoding (gRPC is util now >> HTTP2 based), as HTTP3 becomes available, both of them uses huffman >> encoding. But existed debug log in huffman decode function is hard >> coded using "http2" prefixes, if a client transports an incorrect >> huffman encoded field value in an HTTP3 request, it will give an >> erroneous log. With the patch, it will properly log a bad field value. >> Alternatively, removing "http2" prefixes only is ok, but it can not >> differentiate whether it is caused by an HTTP2 or an HTTP3 request. >> >> diff -r 7ec761f0365f -r 760857905505 src/http/modules/ngx_http_grpc_module.c >> --- a/src/http/modules/ngx_http_grpc_module.c Thu Oct 26 23:35:09 2023 +0300 >> +++ b/src/http/modules/ngx_http_grpc_module.c Tue Oct 31 09:12:45 2023 +0800 >> @@ -3189,6 +3189,7 @@ >> if (ngx_http_huff_decode(&ctx->field_state, p, size, >> &ctx->field_end, >> ctx->field_rest == 0, >> + NGX_HTTP_VERSION_20, >> r->connection->log) >> != NGX_OK) >> { >> @@ -3298,6 +3299,7 @@ >> if (ngx_http_huff_decode(&ctx->field_state, p, size, >> &ctx->field_end, >> ctx->field_rest == 0, >> + NGX_HTTP_VERSION_20, >> r->connection->log) >> != NGX_OK) >> { >> diff -r 7ec761f0365f -r 760857905505 src/http/ngx_http.h >> --- a/src/http/ngx_http.h Thu Oct 26 23:35:09 2023 +0300 >> +++ b/src/http/ngx_http.h Tue Oct 31 09:12:45 2023 +0800 >> @@ -179,7 +179,7 @@ >> >> #if (NGX_HTTP_V2 || NGX_HTTP_V3) >> ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, >> - u_char **dst, ngx_uint_t last, ngx_log_t *log); >> + u_char **dst, ngx_uint_t last, ngx_uint_t http_version, ngx_log_t *log); >> size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, >> ngx_uint_t lower); >> #endif >> diff -r 7ec761f0365f -r 760857905505 src/http/ngx_http_huff_decode.c >> --- a/src/http/ngx_http_huff_decode.c Thu Oct 26 23:35:09 2023 +0300 >> +++ b/src/http/ngx_http_huff_decode.c Tue Oct 31 09:12:45 2023 +0800 >> @@ -2641,9 +2641,22 @@ >> >> ngx_int_t >> ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, >> - ngx_uint_t last, ngx_log_t *log) >> + ngx_uint_t last, ngx_uint_t http_version, ngx_log_t *log) >> { >> u_char *end, ch, ending; >> +#if (NGX_DEBUG) >> + char *from = NULL; >> + >> + switch (http_version) { >> + >> + case NGX_HTTP_VERSION_30: >> + from = "http3"; >> + break; >> + >> + default: >> + from = "http2"; >> + } >> +#endif >> >> ch = 0; >> ending = 1; >> @@ -2656,9 +2669,9 @@ >> if (ngx_http_huff_decode_bits(state, &ending, ch >> 4, dst) >> != NGX_OK) >> { >> - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, >> - "http2 huffman decoding error at state %d: " >> - "bad code 0x%Xd", *state, ch >> 4); >> + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0, >> + "%s huffman decoding error at state %d: " >> + "bad code 0x%Xd", from, *state, ch >> 4); >> >> return NGX_ERROR; >> } >> @@ -2666,9 +2679,9 @@ >> if (ngx_http_huff_decode_bits(state, &ending, ch & 0xf, dst) >> != NGX_OK) >> { >> - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, >> - "http2 huffman decoding error at state %d: " >> - "bad code 0x%Xd", *state, ch & 0xf); >> + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0, >> + "%s huffman decoding error at state %d: " >> + "bad code 0x%Xd", from, *state, ch & 0xf); >> >> return NGX_ERROR; >> } >> @@ -2676,9 +2689,9 @@ >> >> if (last) { >> if (!ending) { >> - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, >> - "http2 huffman decoding error: " >> - "incomplete code 0x%Xd", ch); >> + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, >> + "%s huffman decoding error: " >> + "incomplete code 0x%Xd", from, ch); >> >> return NGX_ERROR; >> } >> diff -r 7ec761f0365f -r 760857905505 src/http/v2/ngx_http_v2.c >> --- a/src/http/v2/ngx_http_v2.c Thu Oct 26 23:35:09 2023 +0300 >> +++ b/src/http/v2/ngx_http_v2.c Tue Oct 31 09:12:45 2023 +0800 >> @@ -1579,6 +1579,7 @@ >> if (ngx_http_huff_decode(&h2c->state.field_state, pos, size, >> &h2c->state.field_end, >> h2c->state.field_rest == 0, >> + NGX_HTTP_VERSION_20, >> h2c->connection->log) >> != NGX_OK) >> { >> diff -r 7ec761f0365f -r 760857905505 src/http/v3/ngx_http_v3_parse.c >> --- a/src/http/v3/ngx_http_v3_parse.c Thu Oct 26 23:35:09 2023 +0300 >> +++ b/src/http/v3/ngx_http_v3_parse.c Tue Oct 31 09:12:45 2023 +0800 >> @@ -647,7 +647,8 @@ >> >> if (st->huffman) { >> if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, >> - st->length == 1, c->log) >> + st->length == 1, NGX_HTTP_VERSION_30, >> + c->log) >> != NGX_OK) >> { >> return NGX_ERROR; > >This introduces complexity just for debugging purpose. >Removing any mention of HTTP protocol version should be enough. >Huffman coding routines do not depend on HTTP protocol versions, >both HPACK and QPACK refer to the same part of specification, >that is Appendix B of RFC 7541. OK. > >Also, while looking at ngx_http_huff_decode() use-cases, >I noticed that HTTP/3 doesn't log Huffman decoding errors, >which it probably should because this is a user-induced >error, and for consistency with other nginx core modules. > ># HG changeset patch ># User Sergey Kandaurov ># Date 1699959003 -14400 ># Tue Nov 14 14:50:03 2023 +0400 ># Node ID c458cd00bb0bca8804ed831474533a813bcfd134 ># Parent 7ec761f0365f418511e30b82e9adf80bc56681df >Adjusted Huffman coding debug logging, missed in 7977:336084ff943b. > >Spotted by XingY Wang. > >diff --git a/src/http/ngx_http_huff_decode.c b/src/http/ngx_http_huff_decode.c >--- a/src/http/ngx_http_huff_decode.c >+++ b/src/http/ngx_http_huff_decode.c >@@ -2657,7 +2657,7 @@ ngx_http_huff_decode(u_char *state, u_ch > != NGX_OK) > { > ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, >- "http2 huffman decoding error at state %d: " >+ "http huffman decoding error at state %d: " > "bad code 0x%Xd", *state, ch >> 4); > > return NGX_ERROR; >@@ -2667,7 +2667,7 @@ ngx_http_huff_decode(u_char *state, u_ch > != NGX_OK) > { > ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, >- "http2 huffman decoding error at state %d: " >+ "http huffman decoding error at state %d: " > "bad code 0x%Xd", *state, ch & 0xf); > > return NGX_ERROR; >@@ -2677,7 +2677,7 @@ ngx_http_huff_decode(u_char *state, u_ch > if (last) { > if (!ending) { > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, >- "http2 huffman decoding error: " >+ "http huffman decoding error: " > "incomplete code 0x%Xd", ch); > > return NGX_ERROR; ># HG changeset patch ># User Sergey Kandaurov ># Date 1699961162 -14400 ># Tue Nov 14 15:26:02 2023 +0400 ># Node ID f366007dd23a6ce8e8427c1b3042781b618a2ade ># Parent c458cd00bb0bca8804ed831474533a813bcfd134 >HTTP/3: added Huffman decoding error logging. > >diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c >--- a/src/http/v3/ngx_http_v3_parse.c >+++ b/src/http/v3/ngx_http_v3_parse.c >@@ -650,6 +650,8 @@ ngx_http_v3_parse_literal(ngx_connection > st->length == 1, c->log) > != NGX_OK) > { >+ ngx_log_error(NGX_LOG_INFO, c->log, 0, >+ "client sent invalid encoded field line"); > return NGX_ERROR; > } > -------------- next part -------------- An HTML attachment was scrubbed... URL: From yaroslavros at gmail.com Mon Nov 20 23:08:21 2023 From: yaroslavros at gmail.com (Yaroslav Rosomakho) Date: Mon, 20 Nov 2023 23:08:21 +0000 Subject: [PATCH] Support for Encrypted Client Hello with BoringSSL Message-ID: # HG changeset patch # User Yaroslav Rosomakho # Date 1700520956 0 # Mon Nov 20 22:55:56 2023 +0000 # Node ID c72fa7e2f3fe4d3635164b61ca9b1aa90dedd3ca # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Added support for Encrypted Client Hello with BoringSSL. This patch introduces support for ECH TLS extension with the BoringSSL library. ECH is already supported and enabled by default in modern Chrome and Firefox. ECH support is implemented through ssl_ech configuration directive, $ssl_ech and $ssl_ech_config variables. To enable ECH for a given server configure ssl_ech as follows: ssl_ech public_name config_id [key=file] [noretry] public_name is mandatory. It needs to be set to FQDN to be populated in clear-text SNI of Outer ClientHello. It's highly recommended to have a server block matching that public_name and providing a valid certificate for it, otherwise the ECH retry mechanism will not work. config_id is mandatory. It is a number between 0 and 255 identifying ECH configuration. Running multiple configurations with the same id is possible but will reduce performance as the server will need to try multiple encryption keys. key=file is optional. It specifies a file with PEM encoded X25519 private key. If it is not specified, the key will be generated dynamically on each restart/configuration reload. It is highly recommended to generate and use a static key unless you have DNS automation to update HTTPS DNS records each time a new key is generated. noretry is an optional flag to remove given configuration from retry list or generated ECHConfigList for DNS record. It should be used for historic rotated out keys that may still be used by clients due to caching. Valid configuration requires at least one ssl_ech entry without a noretry flag. It is possible to have multiple ssl_ech configurations in a given server block. ssl_ech configurations from multiple server blocks under the same listener will be automatically aggregated. Note that TLS 1.3 must be enabled for ssl_ech to be accepted. The only KEM supported for ECH in BoringSSL is X25519, HKDF-SHA256, so X25519 key is required. To generate one with OpenSSL run openssl genpkey -out ech.key -algorithm X25519 After parsing configurationECHConfigList will be dumped into error_log similarly to server ech.example.com ECH config for HTTPS DNS record ech="AEX+DQBB8QAgACBl2nj6LhmbUqJJseiydASRUkdmEQGq/u/e5fXDLsFJSAAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=" For ECH to work this encoded configuration needs to be added to the HTTPS record. Typical HTTPS record looks like this: kdig +short crypto.cloudflare.com https 1 . alpn=http/1.1,h2 ipv4hint=162.159.137.85,162.159.138.85 ech=AEX+DQBB8QAgACBl2nj6LhmbUqJJseiydASRUkdmEQGq/u/e5fXDLsFJSAAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:7::a29f:8955,2606:4700:7::a29f:8a55 If everything is configured correctly, Chrome 117+ and Firefox 118+ will be using public_name in the clear-text SNI of Outer ClientHello - confirm this with Wireshark. Note that for ECH to work Firefox requires DoH. Chrome supports ECH with any DNS as long as it resolves HTTPS records. There are two variables set for ECH: $ssl_ech and $ssl_ech_config $ssl_ech is set to "1" if ECH was successfully negotiated $ssl_ech_config is set to server-specific ECHConfigList mentioned above. It can be used to generate json according to draft-ietf-tls-wkech-04 location /.well-known/origin-svcb { add_header Content-Type application/json; return 200 '{"endpoints":[{"ech":"$ssl_ech_config"}]}'; } This patch implements ECH support only for the HTTP module, but can be easily extended to cover other SSL servers. diff -r 7ec761f0365f -r c72fa7e2f3fe contrib/vim/syntax/nginx.vim --- a/contrib/vim/syntax/nginx.vim Thu Oct 26 23:35:09 2023 +0300 +++ b/contrib/vim/syntax/nginx.vim Mon Nov 20 22:55:56 2023 +0000 @@ -587,6 +587,7 @@ syn keyword ngxDirective contained ssl_dhparam syn keyword ngxDirective contained ssl_early_data syn keyword ngxDirective contained ssl_ecdh_curve +syn keyword ngxDirective contained ssl_ech syn keyword ngxDirective contained ssl_engine syn keyword ngxDirective contained ssl_handshake_timeout syn keyword ngxDirective contained ssl_ocsp diff -r 7ec761f0365f -r c72fa7e2f3fe src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/event/ngx_event_openssl.c Mon Nov 20 22:55:56 2023 +0000 @@ -135,6 +135,9 @@ int ngx_ssl_next_certificate_index; int ngx_ssl_certificate_name_index; int ngx_ssl_stapling_index; +#ifdef SSL_R_ECH_REJECTED +int ngx_ssl_ech_index; +#endif ngx_int_t @@ -288,6 +291,15 @@ return NGX_ERROR; } +#ifdef SSL_R_ECH_REJECTED + ngx_ssl_ech_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + + if (ngx_ssl_ech_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); + return NGX_ERROR; + } +#endif + return NGX_OK; } @@ -453,6 +465,280 @@ } +#ifdef SSL_R_ECH_REJECTED +ngx_int_t +ngx_ssl_ech(ngx_conf_t *cf, ngx_array_t **ech_configs, ngx_str_t *value) +{ + ngx_int_t config_id; + ngx_ssl_ech_conf_t *ech_config; + ngx_uint_t i; + + if (ngx_strchr(value[1].data, '/')) { + ngx_ssl_error(NGX_LOG_WARN, cf->log, 0, + "public name \"%V\" has suspicious symbols", + &value[1]); + } + + config_id = ngx_atoi(value[2].data, value[2].len); + if (config_id < 0 || config_id > 255) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "ECH config_id \"%V\" is invalid. Valid values are between 0 and 255", + &value[2]); + return NGX_ERROR; + } + + if (*ech_configs == NGX_CONF_UNSET_PTR) { + *ech_configs = ngx_array_create(cf->pool, 4, sizeof(ngx_ssl_ech_conf_t)); + if (ech_configs == NULL) { + return NGX_ERROR; + } + } + + ech_config = ngx_array_push(*ech_configs); + if (ech_config == NULL) { + return NGX_ERROR; + } + + ngx_memzero(ech_config, sizeof(ngx_ssl_ech_conf_t)); + + ech_config->public_name = value[1]; + ech_config->config_id = config_id; + + for (i = 3; i < cf->args->nelts; i++) { + if (ngx_strncmp(value[i].data, "key=", 4) == 0) { + ech_config->ech_key.data = value[i].data + 4; + ech_config->ech_key.len = value[i].len - 4; + } else if (ngx_strcmp(value[i].data, "noretry") == 0) { + ech_config->noretry = 1; + } else { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "unexpected configuration parameter \"%V\"", + &value[i]); + return NGX_ERROR; + } + + } + + return NGX_OK; +} + +ngx_int_t +ngx_ssl_prepare_ech(ngx_conf_t *cf, ngx_array_t *ech_configs, ngx_array_t *passwords) { + ngx_uint_t i; + ngx_ssl_ech_conf_t *ech_config; + EVP_PKEY *pkey; + char *err; + + ech_config = ech_configs->elts; + for (i = 0; i < ech_configs->nelts; i++) { + if (ech_config[i].ech_key.len > 0) { + pkey = ngx_ssl_load_certificate_key(cf->pool, &err, &ech_config[i].ech_key, passwords); + if (pkey == NULL) { + if (err != NULL) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot load ECH key from \"%V\": %s", + &ech_config[i].ech_key, err); + } + return NGX_ERROR; + } + if (EVP_PKEY_id(pkey) != EVP_PKEY_X25519) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "only X25519 keys supported for ECH"); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + if (!EVP_PKEY_get_raw_private_key(pkey, NULL, &ech_config[i].ech_key.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot not read private key from \"%V\"", + &ech_config[i].ech_key); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + ech_config[i].ech_key.data = ngx_pnalloc(cf->temp_pool, ech_config[i].ech_key.len); + if (ech_config[i].ech_key.data == NULL) { + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + if (!EVP_PKEY_get_raw_private_key(pkey, ech_config[i].ech_key.data, &ech_config[i].ech_key.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot not extract private key from \"%V\"", + &ech_config[i].ech_key); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + EVP_PKEY_free(pkey); + } + } + + return NGX_OK; +} + +ngx_int_t +ngx_ssl_attach_ech(ngx_conf_t *cf, ngx_array_t *port_ech_configs, ngx_array_t *server_names, + ngx_array_t *ssls, ngx_ssl_t *default_ssl) +{ + ngx_array_t **ech_configs; + ngx_uint_t c, c2, s, s2, found, retry_found, has_keys; + EVP_HPKE_KEY *hpke_key; + ngx_str_t ech_config, dns_config, *base64_dns_config, **server_name; + SSL_ECH_KEYS *ech_keys; + ngx_ssl_ech_conf_t *configs, *configs2; + ngx_ssl_t **ssl; + + default_ssl->ech_keys = SSL_ECH_KEYS_new(); + ech_configs = port_ech_configs->elts; + server_name = server_names->elts; + ssl = ssls->elts; + has_keys = 0; + + for (s = 0; s < port_ech_configs->nelts; s++) { + if (ech_configs[s] && ech_configs[s]->nelts > 0) { + has_keys = 1; + retry_found = 0; + ech_keys = SSL_ECH_KEYS_new(); + configs = ech_configs[s]->elts; + for (c = 0; c < ech_configs[s]->nelts; c++) { + if (!retry_found && !configs[c].noretry) { + retry_found = 1; + } + hpke_key = EVP_HPKE_KEY_new(); + if (hpke_key == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot allocate HPKE key"); + return NGX_ERROR; + } + if(!configs[c].ech_key.len) { + for (s2 = 0; s2 < port_ech_configs->nelts; s2++) { + if (s2 == s || !ech_configs[s2]) { + continue; + } + configs2 = ech_configs[s2]->elts; + for (c2 = 0; c2 < ech_configs[s2]->nelts; c2++) { + if (configs[c].config_id == configs2[c2].config_id && + ngx_strcmp(configs[c].public_name.data, + configs2[c2].public_name.data) == 0 && + configs2[c2].ech_key.len > 0) { + configs[c].ech_key.len = configs2[c2].ech_key.len; + configs[c].ech_key.data = configs2[c2].ech_key.data; + break; + } + } + } + } + if(!configs[c].ech_key.len) { + if (!EVP_HPKE_KEY_generate(hpke_key, EVP_hpke_x25519_hkdf_sha256())) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot generate ECH private key"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + configs[c].ech_key.data = ngx_pnalloc(cf->temp_pool, + EVP_HPKE_MAX_PRIVATE_KEY_LENGTH); + if (configs[c].ech_key.data == NULL) { + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + if (!EVP_HPKE_KEY_private_key(hpke_key, configs[c].ech_key.data, + &configs[c].ech_key.len, EVP_HPKE_MAX_PRIVATE_KEY_LENGTH)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot export generated ECH private key"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + } else { + if (!EVP_HPKE_KEY_init(hpke_key, EVP_hpke_x25519_hkdf_sha256(), + configs[c].ech_key.data, configs[c].ech_key.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot import ECH private key"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + } + + if (!SSL_marshal_ech_config(&ech_config.data, &ech_config.len, configs[c].config_id, + hpke_key, (const char*) configs[c].public_name.data, configs[c].public_name.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot marshall ECH config"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + + if (!SSL_ECH_KEYS_add(ech_keys, !configs[c].noretry, ech_config.data, ech_config.len, + hpke_key)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot add key to server ECH config"); + OPENSSL_free(ech_config.data); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + + found = 0; + for (s2 = 0; s2 < s; s2++) { + configs2 = ech_configs[s2]->elts; + for (c2 = 0; c2 < ech_configs[s2]->nelts; c2++) { + if (configs[c].config_id == configs2[c2].config_id && + ngx_strcmp(configs[c].public_name.data, configs2[c2].public_name.data) == 0 && + ngx_strcmp(configs[c].ech_key.data, configs2[c2].ech_key.data) == 0) { + found = 1; + } + } + } + if (!found) { + if (!SSL_ECH_KEYS_add(default_ssl->ech_keys, !configs[c].noretry, ech_config.data, + ech_config.len, hpke_key)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot add key to ECH config"); + OPENSSL_free(ech_config.data); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + } + OPENSSL_free(ech_config.data); + EVP_HPKE_KEY_free(hpke_key); + } + if (!retry_found) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "at least one ssl_ech entry without noretry required for server %V", server_name[s]); + return NGX_ERROR; + } + SSL_ECH_KEYS_marshal_retry_configs(ech_keys, &dns_config.data, &dns_config.len); + base64_dns_config = ngx_palloc(cf->pool, sizeof(ngx_str_t)); + if (base64_dns_config == NULL) { + return NGX_ERROR; + } + base64_dns_config->len = ngx_base64_encoded_length(dns_config.len); + base64_dns_config->data = ngx_palloc(cf->pool, base64_dns_config->len); + if (base64_dns_config->data == NULL) { + return NGX_ERROR; + } + ngx_encode_base64(base64_dns_config, &dns_config); + ngx_ssl_error(NGX_LOG_WARN, cf->log, 0, "server %V ECH config for HTTPS DNS record ech=\"%V\"", + server_name[s], base64_dns_config); + if (SSL_CTX_set_ex_data(ssl[s]->ctx, ngx_ssl_ech_index, base64_dns_config) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + OPENSSL_free(dns_config.data); + } + } + + if (default_ssl->ctx && has_keys) { + if (!SSL_CTX_set1_ech_keys(default_ssl->ctx, default_ssl->ech_keys)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "could not attach ECH keys to context"); + return NGX_ERROR; + } + } + + return NGX_OK; +} + +#endif + + ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords) @@ -801,7 +1087,6 @@ } } else { - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key) != NGX_OK) { @@ -4861,6 +5146,11 @@ } SSL_CTX_free(ssl->ctx); +#ifdef SSL_R_ECH_REJECTED + if (ssl->ech_keys) { + SSL_ECH_KEYS_free(ssl->ech_keys); + } +#endif } @@ -5943,6 +6233,42 @@ } +ngx_int_t +ngx_ssl_ech_negotiated(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ + + s->len = 0; + +#ifdef SSL_R_ECH_REJECTED + if (SSL_ech_accepted(c->ssl->connection)) { + ngx_str_set(s, "1"); + } +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ech_config(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ + s->len = 0; +#ifdef SSL_R_ECH_REJECTED + ngx_str_t *ech_config; + ech_config = SSL_CTX_get_ex_data(c->ssl->session_ctx, ngx_ssl_ech_index); + if (ech_config) { + s->len = ech_config->len; + s->data = ngx_pnalloc(pool, s->len); + if (s->data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(s->data, ech_config->data, s->len); + } +#endif + return NGX_OK; +} + + static time_t ngx_ssl_parse_time( #if OPENSSL_VERSION_NUMBER > 0x10100000L diff -r 7ec761f0365f -r c72fa7e2f3fe src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h Thu Oct 26 23:35:09 2023 +0300 +++ b/src/event/ngx_event_openssl.h Mon Nov 20 22:55:56 2023 +0000 @@ -39,6 +39,9 @@ #include #include #include +#ifdef SSL_R_ECH_REJECTED +#include +#endif #define NGX_SSL_NAME "OpenSSL" @@ -88,6 +91,9 @@ struct ngx_ssl_s { SSL_CTX *ctx; +#ifdef SSL_R_ECH_REJECTED + SSL_ECH_KEYS *ech_keys; +#endif ngx_log_t *log; size_t buffer_size; }; @@ -174,6 +180,16 @@ } ngx_ssl_session_cache_t; +#ifdef SSL_R_ECH_REJECTED +typedef struct { + ngx_str_t public_name; + ngx_uint_t config_id; + ngx_str_t ech_key; + ngx_flag_t noretry; +} ngx_ssl_ech_conf_t; +#endif + + #define NGX_SSL_SSLv2 0x0002 #define NGX_SSL_SSLv3 0x0004 #define NGX_SSL_TLSv1 0x0008 @@ -191,6 +207,14 @@ ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); +#ifdef SSL_R_ECH_REJECTED +ngx_int_t ngx_ssl_ech(ngx_conf_t *cf, ngx_array_t **ech_configs, + ngx_str_t *value); +ngx_int_t ngx_ssl_attach_ech(ngx_conf_t *cf, ngx_array_t *port_ech_configs, + ngx_array_t *server_names, ngx_array_t *ssls, ngx_ssl_t *default_ssl); +ngx_int_t ngx_ssl_prepare_ech(ngx_conf_t *cf, ngx_array_t *ech_configs, + ngx_array_t *passwords); +#endif ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords); ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, @@ -307,6 +331,10 @@ ngx_str_t *s); ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_ech_negotiated(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); +ngx_int_t ngx_ssl_ech_config(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); @@ -334,6 +362,9 @@ extern int ngx_ssl_next_certificate_index; extern int ngx_ssl_certificate_name_index; extern int ngx_ssl_stapling_index; +#ifdef SSL_R_ECH_REJECTED +extern int ngx_ssl_ech_index; +#endif #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff -r 7ec761f0365f -r c72fa7e2f3fe src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/modules/ngx_http_ssl_module.c Mon Nov 20 22:55:56 2023 +0000 @@ -49,6 +49,8 @@ void *conf); static char *ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_ssl_ech(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); @@ -290,6 +292,14 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_ech"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2|NGX_CONF_TAKE3| + NGX_CONF_TAKE4, + ngx_http_ssl_ech, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + ngx_null_command }; @@ -399,6 +409,12 @@ { ngx_string("ssl_client_v_remain"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_client_v_remain, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_ech"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_ech_negotiated, NGX_HTTP_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_ech_config"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_ech_config, NGX_HTTP_VAR_CHANGEABLE, 0 }, + ngx_http_null_variable }; @@ -629,6 +645,9 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; +#ifdef SSL_R_ECH_REJECTED + sscf->ech_configs = NGX_CONF_UNSET_PTR; +#endif return sscf; } @@ -693,6 +712,9 @@ ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); +#ifdef SSL_R_ECH_REJECTED + ngx_conf_merge_ptr_value(conf->ech_configs, prev->ech_configs, NULL); +#endif conf->ssl.log = cf->log; @@ -886,6 +908,12 @@ return NGX_CONF_ERROR; } +#ifdef SSL_R_ECH_REJECTED + if (conf->ech_configs && ngx_ssl_prepare_ech(cf, conf->ech_configs, conf->passwords) != NGX_OK) { + return NGX_CONF_ERROR; + } +#endif + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1207,6 +1235,23 @@ } +static char * +ngx_http_ssl_ech(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ +#ifndef SSL_R_ECH_REJECTED + return "is not supported on this platform"; +#else + ngx_http_ssl_srv_conf_t *sscf = conf; + + if (ngx_ssl_ech(cf, &sscf->ech_configs, cf->args->elts) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { @@ -1218,6 +1263,13 @@ ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t **cscfp, *cscf; ngx_http_core_main_conf_t *cmcf; +#ifdef SSL_R_ECH_REJECTED + ngx_array_t **ech_config, *ech_configs, *server_names, *ssls; + ngx_str_t **server_name; + ngx_http_core_srv_conf_t *cscf2; + ngx_http_ssl_srv_conf_t *sscf2; + ngx_ssl_t **ssl; +#endif cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); cscfp = cmcf->servers.elts; @@ -1280,6 +1332,43 @@ cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + cscfp = addr[a].servers.elts; + +#ifdef SSL_R_ECH_REJECTED + ech_configs = ngx_array_create(cf->temp_pool, addr[a].servers.nelts, sizeof(ngx_array_t *)); + server_names = ngx_array_create(cf->temp_pool, addr[a].servers.nelts, sizeof(ngx_str_t *)); + ssls = ngx_array_create(cf->temp_pool, addr[a].servers.nelts, sizeof(ngx_ssl_t *)); + for (s = 0; s < addr[a].servers.nelts; s++) { + cscf2 = cscfp[s]; + sscf2 = cscf2->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + if (!(sscf2->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "ECH requires \"ssl_protocols\" to include TLSv1.3 for " + "server %V", &cscf2->server_name); + return NGX_ERROR; + } + ech_config = ngx_array_push(ech_configs); + if (ech_config == NULL) { + return NGX_ERROR; + } + *ech_config = sscf2->ech_configs; + server_name = ngx_array_push(server_names); + if (server_name == NULL) { + return NGX_ERROR; + } + *server_name = &cscf2->server_name; + ssl = ngx_array_push(ssls); + if (ssl == NULL) { + return NGX_ERROR; + } + *ssl = &sscf2->ssl; + } + if (ngx_ssl_attach_ech(cf, ech_configs, server_names, ssls, &sscf->ssl) != NGX_OK) { + return NGX_ERROR; + } + ngx_array_destroy(ech_configs); + ngx_array_destroy(server_names); +#endif if (sscf->certificates) { @@ -1307,7 +1396,6 @@ * check all non-default server blocks */ - cscfp = addr[a].servers.elts; for (s = 0; s < addr[a].servers.nelts; s++) { cscf = cscfp[s]; diff -r 7ec761f0365f -r c72fa7e2f3fe src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/modules/ngx_http_ssl_module.h Mon Nov 20 22:55:56 2023 +0000 @@ -62,6 +62,10 @@ ngx_flag_t stapling_verify; ngx_str_t stapling_file; ngx_str_t stapling_responder; + +#ifdef SSL_R_ECH_REJECTED + ngx_array_t *ech_configs; +#endif } ngx_http_ssl_srv_conf_t; From yaroslavros at gmail.com Mon Nov 20 23:35:34 2023 From: yaroslavros at gmail.com (Yaroslav Rosomakho) Date: Mon, 20 Nov 2023 23:35:34 +0000 Subject: [PATCH] Support for Encrypted Client Hello with BoringSSL (resubmitting with corrected lines) Message-ID: # HG changeset patch # User Yaroslav Rosomakho # Date 1700523131 0 # Mon Nov 20 23:32:11 2023 +0000 # Node ID 8e9f043550f062594800ba9ae97bc4a250df123e # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Added support for Encrypted Client Hello with BoringSSL. This patch introduces support for ECH TLS extension with the BoringSSL library. ECH is already supported and enabled by default in modern Chrome and Firefox. ECH support is implemented through ssl_ech configuration directive, $ssl_ech and $ssl_ech_config variables. To enable ECH for a given server configure ssl_ech as follows: ssl_ech public_name config_id [key=file] [noretry] public_name is mandatory. It needs to be set to FQDN to be populated in clear-text SNI of Outer ClientHello. It's highly recommended to have a server block matching that public_name and providing a valid certificate for it, otherwise the ECH retry mechanism will not work. config_id is mandatory. It is a number between 0 and 255 identifying ECH configuration. Running multiple configurations with the same id is possible but will reduce performance as the server will need to try multiple encryption keys. key=file is optional. It specifies a file with PEM encoded X25519 private key. If it is not specified, the key will be generated dynamically on each restart/configuration reload. It is highly recommended to generate and use a static key unless you have DNS automation to update HTTPS DNS records each time a new key is generated. noretry is an optional flag to remove given configuration from retry list or generated ECHConfigList for DNS record. It should be used for historic rotated out keys that may still be used by clients due to caching. Valid configuration requires at least one ssl_ech entry without a noretry flag. It is possible to have multiple ssl_ech configurations in a given server block. ssl_ech configurations from multiple server blocks under the same listener will be automatically aggregated. Note that TLS 1.3 must be enabled for ssl_ech to be accepted. The only KEM supported for ECH in BoringSSL is X25519, HKDF-SHA256, so X25519 key is required. To generate one with OpenSSL run openssl genpkey -out ech.key -algorithm X25519 After parsing configurationECHConfigList will be dumped into error_log similarly to server ech.example.com ECH config for HTTPS DNS record ech="AEX+DQBB8QAgACBl2nj6LhmbUqJJseiydASRUkdmEQGq/u/e5fXDLsFJSAAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=" For ECH to work this encoded configuration needs to be added to the HTTPS record. Typical HTTPS record looks like this: kdig +short crypto.cloudflare.com https 1 . alpn=http/1.1,h2 ipv4hint=162.159.137.85,162.159.138.85 ech=AEX+DQBB8QAgACBl2nj6LhmbUqJJseiydASRUkdmEQGq/u/e5fXDLsFJSAAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:7::a29f:8955,2606:4700:7::a29f:8a55 If everything is configured correctly, Chrome 117+ and Firefox 118+ will be using public_name in the clear-text SNI of Outer ClientHello - confirm this with Wireshark. Note that for ECH to work Firefox requires DoH. Chrome supports ECH with any DNS as long as it resolves HTTPS records. There are two variables set for ECH: $ssl_ech and $ssl_ech_config $ssl_ech is set to "1" if ECH was successfully negotiated $ssl_ech_config is set to server-specific ECHConfigList mentioned above. It can be used to generate json according to draft-ietf-tls-wkech-04 location /.well-known/origin-svcb { add_header Content-Type application/json; return 200 '{"endpoints":[{"ech":"$ssl_ech_config"}]}'; } This patch implements ECH support only for the HTTP module, but can be easily extended to cover other SSL servers. diff -r 7ec761f0365f -r 8e9f043550f0 contrib/vim/syntax/nginx.vim --- a/contrib/vim/syntax/nginx.vim Thu Oct 26 23:35:09 2023 +0300 +++ b/contrib/vim/syntax/nginx.vim Mon Nov 20 23:32:11 2023 +0000 @@ -587,6 +587,7 @@ syn keyword ngxDirective contained ssl_dhparam syn keyword ngxDirective contained ssl_early_data syn keyword ngxDirective contained ssl_ecdh_curve +syn keyword ngxDirective contained ssl_ech syn keyword ngxDirective contained ssl_engine syn keyword ngxDirective contained ssl_handshake_timeout syn keyword ngxDirective contained ssl_ocsp diff -r 7ec761f0365f -r 8e9f043550f0 src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/event/ngx_event_openssl.c Mon Nov 20 23:32:11 2023 +0000 @@ -135,6 +135,9 @@ int ngx_ssl_next_certificate_index; int ngx_ssl_certificate_name_index; int ngx_ssl_stapling_index; +#ifdef SSL_R_ECH_REJECTED +int ngx_ssl_ech_index; +#endif ngx_int_t @@ -288,6 +291,15 @@ return NGX_ERROR; } +#ifdef SSL_R_ECH_REJECTED + ngx_ssl_ech_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + + if (ngx_ssl_ech_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); + return NGX_ERROR; + } +#endif + return NGX_OK; } @@ -453,6 +465,305 @@ } +#ifdef SSL_R_ECH_REJECTED +ngx_int_t +ngx_ssl_ech(ngx_conf_t *cf, ngx_array_t **ech_configs, ngx_str_t *value) +{ + ngx_int_t config_id; + ngx_ssl_ech_conf_t *ech_config; + ngx_uint_t i; + + if (ngx_strchr(value[1].data, '/')) { + ngx_ssl_error(NGX_LOG_WARN, cf->log, 0, + "public name \"%V\" has suspicious symbols", + &value[1]); + } + + config_id = ngx_atoi(value[2].data, value[2].len); + if (config_id < 0 || config_id > 255) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "ECH config_id \"%V\" is invalid. Valid values" + "are between 0 and 255", + &value[2]); + return NGX_ERROR; + } + + if (*ech_configs == NGX_CONF_UNSET_PTR) { + *ech_configs = ngx_array_create(cf->pool, 4, + sizeof(ngx_ssl_ech_conf_t)); + if (ech_configs == NULL) { + return NGX_ERROR; + } + } + + ech_config = ngx_array_push(*ech_configs); + if (ech_config == NULL) { + return NGX_ERROR; + } + + ngx_memzero(ech_config, sizeof(ngx_ssl_ech_conf_t)); + + ech_config->public_name = value[1]; + ech_config->config_id = config_id; + + for (i = 3; i < cf->args->nelts; i++) { + if (ngx_strncmp(value[i].data, "key=", 4) == 0) { + ech_config->ech_key.data = value[i].data + 4; + ech_config->ech_key.len = value[i].len - 4; + } else if (ngx_strcmp(value[i].data, "noretry") == 0) { + ech_config->noretry = 1; + } else { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "unexpected configuration parameter \"%V\"", + &value[i]); + return NGX_ERROR; + } + + } + + return NGX_OK; +} + +ngx_int_t +ngx_ssl_prepare_ech(ngx_conf_t *cf, ngx_array_t *ech_configs, + ngx_array_t *passwords) { + ngx_uint_t i; + ngx_ssl_ech_conf_t *ech_config; + EVP_PKEY *pkey; + char *err; + + ech_config = ech_configs->elts; + for (i = 0; i < ech_configs->nelts; i++) { + if (ech_config[i].ech_key.len > 0) { + pkey = ngx_ssl_load_certificate_key(cf->pool, &err, + &ech_config[i].ech_key, passwords); + if (pkey == NULL) { + if (err != NULL) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot load ECH key from \"%V\": %s", + &ech_config[i].ech_key, err); + } + return NGX_ERROR; + } + if (EVP_PKEY_id(pkey) != EVP_PKEY_X25519) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "only X25519 keys supported for ECH"); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + if (!EVP_PKEY_get_raw_private_key(pkey, NULL, + &ech_config[i].ech_key.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot not read private key from \"%V\"", + &ech_config[i].ech_key); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + ech_config[i].ech_key.data = ngx_pnalloc(cf->temp_pool, + ech_config[i].ech_key.len); + if (ech_config[i].ech_key.data == NULL) { + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + if (!EVP_PKEY_get_raw_private_key(pkey, + ech_config[i].ech_key.data, &ech_config[i].ech_key.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot not extract private key from \"%V\"", + &ech_config[i].ech_key); + EVP_PKEY_free(pkey); + return NGX_ERROR; + } + EVP_PKEY_free(pkey); + } + } + + return NGX_OK; +} + +ngx_int_t +ngx_ssl_attach_ech(ngx_conf_t *cf, ngx_array_t *port_ech_configs, + ngx_array_t *server_names, ngx_array_t *ssls, ngx_ssl_t *default_ssl) +{ + ngx_array_t **ech_configs; + ngx_uint_t c, c2, s, s2, found, retry_found, has_keys; + EVP_HPKE_KEY *hpke_key; + ngx_str_t ech_config, dns_config, + *base64_dns_config, **server_name; + SSL_ECH_KEYS *ech_keys; + ngx_ssl_ech_conf_t *configs, *configs2; + ngx_ssl_t **ssl; + + default_ssl->ech_keys = SSL_ECH_KEYS_new(); + ech_configs = port_ech_configs->elts; + server_name = server_names->elts; + ssl = ssls->elts; + has_keys = 0; + + for (s = 0; s < port_ech_configs->nelts; s++) { + if (ech_configs[s] && ech_configs[s]->nelts > 0) { + has_keys = 1; + retry_found = 0; + ech_keys = SSL_ECH_KEYS_new(); + configs = ech_configs[s]->elts; + for (c = 0; c < ech_configs[s]->nelts; c++) { + if (!retry_found && !configs[c].noretry) { + retry_found = 1; + } + hpke_key = EVP_HPKE_KEY_new(); + if (hpke_key == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot allocate HPKE key"); + return NGX_ERROR; + } + if(!configs[c].ech_key.len) { + for (s2 = 0; s2 < port_ech_configs->nelts; s2++) { + if (s2 == s || !ech_configs[s2]) { + continue; + } + configs2 = ech_configs[s2]->elts; + for (c2 = 0; c2 < ech_configs[s2]->nelts; c2++) { + if (configs[c].config_id == + configs2[c2].config_id && + ngx_strcmp(configs[c].public_name.data, + configs2[c2].public_name.data) == 0 && + configs2[c2].ech_key.len > 0) { + configs[c].ech_key.len = + configs2[c2].ech_key.len; + configs[c].ech_key.data = + configs2[c2].ech_key.data; + break; + } + } + } + } + if(!configs[c].ech_key.len) { + if (!EVP_HPKE_KEY_generate(hpke_key, + EVP_hpke_x25519_hkdf_sha256())) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot generate ECH private key"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + configs[c].ech_key.data = ngx_pnalloc(cf->temp_pool, + EVP_HPKE_MAX_PRIVATE_KEY_LENGTH); + if (configs[c].ech_key.data == NULL) { + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + if (!EVP_HPKE_KEY_private_key(hpke_key, + configs[c].ech_key.data, + &configs[c].ech_key.len, + EVP_HPKE_MAX_PRIVATE_KEY_LENGTH)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot export generated ECH private key"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + } else { + if (!EVP_HPKE_KEY_init(hpke_key, + EVP_hpke_x25519_hkdf_sha256(), + configs[c].ech_key.data, configs[c].ech_key.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot import ECH private key"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + } + + if (!SSL_marshal_ech_config(&ech_config.data, + &ech_config.len, configs[c].config_id, + hpke_key, (const char*) configs[c].public_name.data, + configs[c].public_name.len)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot marshall ECH config"); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + + if (!SSL_ECH_KEYS_add(ech_keys, !configs[c].noretry, + ech_config.data, ech_config.len, hpke_key)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot add key to server ECH config"); + OPENSSL_free(ech_config.data); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + + found = 0; + for (s2 = 0; s2 < s; s2++) { + configs2 = ech_configs[s2]->elts; + for (c2 = 0; c2 < ech_configs[s2]->nelts; c2++) { + if (configs[c].config_id == configs2[c2].config_id && + ngx_strcmp(configs[c].public_name.data, + configs2[c2].public_name.data) == 0 && + ngx_strcmp(configs[c].ech_key.data, + configs2[c2].ech_key.data) == 0) { + found = 1; + } + } + } + if (!found) { + if (!SSL_ECH_KEYS_add(default_ssl->ech_keys, + !configs[c].noretry, ech_config.data, + ech_config.len, hpke_key)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "cannot add key to ECH config"); + OPENSSL_free(ech_config.data); + EVP_HPKE_KEY_free(hpke_key); + return NGX_ERROR; + } + } + OPENSSL_free(ech_config.data); + EVP_HPKE_KEY_free(hpke_key); + } + if (!retry_found) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "at least one ssl_ech entry without" + " noretry required for server %V", server_name[s]); + return NGX_ERROR; + } + SSL_ECH_KEYS_marshal_retry_configs(ech_keys, + &dns_config.data, &dns_config.len); + base64_dns_config = ngx_palloc(cf->pool, sizeof(ngx_str_t)); + if (base64_dns_config == NULL) { + return NGX_ERROR; + } + base64_dns_config->len = ngx_base64_encoded_length(dns_config.len); + base64_dns_config->data = ngx_palloc(cf->pool, + base64_dns_config->len); + if (base64_dns_config->data == NULL) { + return NGX_ERROR; + } + ngx_encode_base64(base64_dns_config, &dns_config); + ngx_ssl_error(NGX_LOG_WARN, cf->log, 0, + "server %V ECH config for HTTPS DNS record ech=\"%V\"", + server_name[s], base64_dns_config); + if (SSL_CTX_set_ex_data(ssl[s]->ctx, ngx_ssl_ech_index, + base64_dns_config) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + OPENSSL_free(dns_config.data); + } + } + + if (default_ssl->ctx && has_keys) { + if (!SSL_CTX_set1_ech_keys(default_ssl->ctx, default_ssl->ech_keys)) { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "could not attach ECH keys to context"); + return NGX_ERROR; + } + } + + return NGX_OK; +} + +#endif + + ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords) @@ -801,7 +1112,6 @@ } } else { - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key) != NGX_OK) { @@ -4861,6 +5171,11 @@ } SSL_CTX_free(ssl->ctx); +#ifdef SSL_R_ECH_REJECTED + if (ssl->ech_keys) { + SSL_ECH_KEYS_free(ssl->ech_keys); + } +#endif } @@ -5943,6 +6258,42 @@ } +ngx_int_t +ngx_ssl_ech_negotiated(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ + + s->len = 0; + +#ifdef SSL_R_ECH_REJECTED + if (SSL_ech_accepted(c->ssl->connection)) { + ngx_str_set(s, "1"); + } +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ech_config(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ + s->len = 0; +#ifdef SSL_R_ECH_REJECTED + ngx_str_t *ech_config; + ech_config = SSL_CTX_get_ex_data(c->ssl->session_ctx, ngx_ssl_ech_index); + if (ech_config) { + s->len = ech_config->len; + s->data = ngx_pnalloc(pool, s->len); + if (s->data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(s->data, ech_config->data, s->len); + } +#endif + return NGX_OK; +} + + static time_t ngx_ssl_parse_time( #if OPENSSL_VERSION_NUMBER > 0x10100000L diff -r 7ec761f0365f -r 8e9f043550f0 src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h Thu Oct 26 23:35:09 2023 +0300 +++ b/src/event/ngx_event_openssl.h Mon Nov 20 23:32:11 2023 +0000 @@ -39,6 +39,9 @@ #include #include #include +#ifdef SSL_R_ECH_REJECTED +#include +#endif #define NGX_SSL_NAME "OpenSSL" @@ -88,6 +91,9 @@ struct ngx_ssl_s { SSL_CTX *ctx; +#ifdef SSL_R_ECH_REJECTED + SSL_ECH_KEYS *ech_keys; +#endif ngx_log_t *log; size_t buffer_size; }; @@ -174,6 +180,16 @@ } ngx_ssl_session_cache_t; +#ifdef SSL_R_ECH_REJECTED +typedef struct { + ngx_str_t public_name; + ngx_uint_t config_id; + ngx_str_t ech_key; + ngx_flag_t noretry; +} ngx_ssl_ech_conf_t; +#endif + + #define NGX_SSL_SSLv2 0x0002 #define NGX_SSL_SSLv3 0x0004 #define NGX_SSL_TLSv1 0x0008 @@ -191,6 +207,14 @@ ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); +#ifdef SSL_R_ECH_REJECTED +ngx_int_t ngx_ssl_ech(ngx_conf_t *cf, ngx_array_t **ech_configs, + ngx_str_t *value); +ngx_int_t ngx_ssl_attach_ech(ngx_conf_t *cf, ngx_array_t *port_ech_configs, + ngx_array_t *server_names, ngx_array_t *ssls, ngx_ssl_t *default_ssl); +ngx_int_t ngx_ssl_prepare_ech(ngx_conf_t *cf, ngx_array_t *ech_configs, + ngx_array_t *passwords); +#endif ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords); ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, @@ -307,6 +331,10 @@ ngx_str_t *s); ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_ech_negotiated(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); +ngx_int_t ngx_ssl_ech_config(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); @@ -334,6 +362,9 @@ extern int ngx_ssl_next_certificate_index; extern int ngx_ssl_certificate_name_index; extern int ngx_ssl_stapling_index; +#ifdef SSL_R_ECH_REJECTED +extern int ngx_ssl_ech_index; +#endif #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff -r 7ec761f0365f -r 8e9f043550f0 src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/modules/ngx_http_ssl_module.c Mon Nov 20 23:32:11 2023 +0000 @@ -49,6 +49,8 @@ void *conf); static char *ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_ssl_ech(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); @@ -290,6 +292,14 @@ offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), NULL }, + { ngx_string("ssl_ech"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2|NGX_CONF_TAKE3| + NGX_CONF_TAKE4, + ngx_http_ssl_ech, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + ngx_null_command }; @@ -399,6 +409,12 @@ { ngx_string("ssl_client_v_remain"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_client_v_remain, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_ech"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_ech_negotiated, NGX_HTTP_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_ech_config"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_ech_config, NGX_HTTP_VAR_CHANGEABLE, 0 }, + ngx_http_null_variable }; @@ -629,6 +645,9 @@ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; +#ifdef SSL_R_ECH_REJECTED + sscf->ech_configs = NGX_CONF_UNSET_PTR; +#endif return sscf; } @@ -693,6 +712,9 @@ ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); +#ifdef SSL_R_ECH_REJECTED + ngx_conf_merge_ptr_value(conf->ech_configs, prev->ech_configs, NULL); +#endif conf->ssl.log = cf->log; @@ -886,6 +908,14 @@ return NGX_CONF_ERROR; } +#ifdef SSL_R_ECH_REJECTED + if (conf->ech_configs && + ngx_ssl_prepare_ech(cf, conf->ech_configs, + conf->passwords) != NGX_OK) { + return NGX_CONF_ERROR; + } +#endif + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1207,6 +1237,23 @@ } +static char * +ngx_http_ssl_ech(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ +#ifndef SSL_R_ECH_REJECTED + return "is not supported on this platform"; +#else + ngx_http_ssl_srv_conf_t *sscf = conf; + + if (ngx_ssl_ech(cf, &sscf->ech_configs, cf->args->elts) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { @@ -1218,6 +1265,14 @@ ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t **cscfp, *cscf; ngx_http_core_main_conf_t *cmcf; +#ifdef SSL_R_ECH_REJECTED + ngx_array_t **ech_config, *ech_configs, + *server_names, *ssls; + ngx_str_t **server_name; + ngx_http_core_srv_conf_t *cscf2; + ngx_http_ssl_srv_conf_t *sscf2; + ngx_ssl_t **ssl; +#endif cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); cscfp = cmcf->servers.elts; @@ -1280,6 +1335,47 @@ cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + cscfp = addr[a].servers.elts; + +#ifdef SSL_R_ECH_REJECTED + ech_configs = ngx_array_create(cf->temp_pool, + addr[a].servers.nelts, sizeof(ngx_array_t *)); + server_names = ngx_array_create(cf->temp_pool, + addr[a].servers.nelts, sizeof(ngx_str_t *)); + ssls = ngx_array_create(cf->temp_pool, + addr[a].servers.nelts, sizeof(ngx_ssl_t *)); + for (s = 0; s < addr[a].servers.nelts; s++) { + cscf2 = cscfp[s]; + sscf2 = cscf2->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + if (!(sscf2->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "ECH requires \"ssl_protocols\" to include TLSv1.3" + " for server %V", &cscf2->server_name); + return NGX_ERROR; + } + ech_config = ngx_array_push(ech_configs); + if (ech_config == NULL) { + return NGX_ERROR; + } + *ech_config = sscf2->ech_configs; + server_name = ngx_array_push(server_names); + if (server_name == NULL) { + return NGX_ERROR; + } + *server_name = &cscf2->server_name; + ssl = ngx_array_push(ssls); + if (ssl == NULL) { + return NGX_ERROR; + } + *ssl = &sscf2->ssl; + } + if (ngx_ssl_attach_ech(cf, ech_configs, + server_names, ssls, &sscf->ssl) != NGX_OK) { + return NGX_ERROR; + } + ngx_array_destroy(ech_configs); + ngx_array_destroy(server_names); +#endif if (sscf->certificates) { @@ -1307,7 +1403,6 @@ * check all non-default server blocks */ - cscfp = addr[a].servers.elts; for (s = 0; s < addr[a].servers.nelts; s++) { cscf = cscfp[s]; diff -r 7ec761f0365f -r 8e9f043550f0 src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/modules/ngx_http_ssl_module.h Mon Nov 20 23:32:11 2023 +0000 @@ -62,6 +62,10 @@ ngx_flag_t stapling_verify; ngx_str_t stapling_file; ngx_str_t stapling_responder; + +#ifdef SSL_R_ECH_REJECTED + ngx_array_t *ech_configs; +#endif } ngx_http_ssl_srv_conf_t; From v.zhestikov at f5.com Tue Nov 21 14:55:03 2023 From: v.zhestikov at f5.com (=?utf-8?q?Vadim_Zhestikov?=) Date: Tue, 21 Nov 2023 14:55:03 +0000 Subject: [njs] Fixed memory overlapping. Message-ID: details: https://hg.nginx.org/njs/rev/8e024f36e38e branches: changeset: 2235:8e024f36e38e user: Vadim Zhestikov date: Tue Nov 21 06:49:30 2023 -0800 description: Fixed memory overlapping. diffstat: src/njs_iterator.c | 22 ++-------------------- src/test/njs_unit_test.c | 3 +++ 2 files changed, 5 insertions(+), 20 deletions(-) diffs (66 lines): diff -r e4105b65d105 -r 8e024f36e38e src/njs_iterator.c --- a/src/njs_iterator.c Wed Nov 15 15:08:18 2023 -0800 +++ b/src/njs_iterator.c Tue Nov 21 06:49:30 2023 -0800 @@ -301,7 +301,6 @@ njs_object_iterate(njs_vm_t *vm, njs_ite njs_value_t *value, *entry, prop, character; const u_char *p, *end, *pos; njs_string_prop_t string_prop; - njs_object_value_t *object; value = njs_value_arg(&args->value); from = args->from; @@ -348,15 +347,7 @@ njs_object_iterate(njs_vm_t *vm, njs_ite if (njs_is_string(value) || njs_is_object_string(value)) { - if (njs_is_string(value)) { - object = njs_object_value_alloc(vm, NJS_OBJ_TYPE_STRING, 0, value); - if (njs_slow_path(object == NULL)) { - return NJS_ERROR; - } - - njs_set_object_value(njs_value_arg(&args->value), object); - } - else { + if (!njs_is_string(value)) { value = njs_object_value(value); } @@ -461,7 +452,6 @@ njs_object_iterate_reverse(njs_vm_t *vm, njs_value_t *entry, *value, prop, character; const u_char *p, *end, *pos; njs_string_prop_t string_prop; - njs_object_value_t *object; value = njs_value_arg(&args->value); from = args->from; @@ -510,15 +500,7 @@ njs_object_iterate_reverse(njs_vm_t *vm, if (njs_is_string(value) || njs_is_object_string(value)) { - if (njs_is_string(value)) { - object = njs_object_value_alloc(vm, NJS_OBJ_TYPE_STRING, 0, value); - if (njs_slow_path(object == NULL)) { - return NJS_ERROR; - } - - njs_set_object_value(njs_value_arg(&args->value), object); - } - else { + if (!njs_is_string(value)) { value = njs_object_value(value); } diff -r e4105b65d105 -r 8e024f36e38e src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Wed Nov 15 15:08:18 2023 -0800 +++ b/src/test/njs_unit_test.c Tue Nov 21 06:49:30 2023 -0800 @@ -12136,6 +12136,9 @@ static njs_unit_test_t njs_test[] = { njs_str("let e = AggregateError('abc'); e.errors"), njs_str("a,b,c") }, + { njs_str("let e = AggregateError('1234567'); e.errors"), + njs_str("1,2,3,4,5,6,7") }, + { njs_str("let e = AggregateError([1, 2, 3], 'm'); e"), njs_str("AggregateError: m") }, From xeioex at nginx.com Tue Nov 21 16:58:01 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 21 Nov 2023 16:58:01 +0000 Subject: [njs] Modules: fixed js_set with Buffer values. Message-ID: details: https://hg.nginx.org/njs/rev/1d13f6e877ad branches: changeset: 2236:1d13f6e877ad user: Dmitry Volyntsev date: Tue Nov 21 08:57:03 2023 -0800 description: Modules: fixed js_set with Buffer values. Previously, a Buffer value which contains invalid UTF-8 when returned as a value for js_set handler was mangled because the bytes value was converted to a string value. The fix is to use bytes value of Buffer, TypedArray and ArrayBuffer as is, and not convert it to a string first. diffstat: nginx/ngx_http_js_module.c | 8 +++--- nginx/ngx_stream_js_module.c | 8 +++--- nginx/t/js.t | 28 ++++++++++++++++++++++++-- nginx/t/stream_js.t | 45 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 75 insertions(+), 14 deletions(-) diffs (243 lines): diff -r 8e024f36e38e -r 1d13f6e877ad nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Tue Nov 21 06:49:30 2023 -0800 +++ b/nginx/ngx_http_js_module.c Tue Nov 21 08:57:03 2023 -0800 @@ -1250,7 +1250,7 @@ ngx_http_js_variable_set(ngx_http_reques ngx_int_t rc; njs_int_t pending; - ngx_str_t value; + njs_str_t value; ngx_http_js_ctx_t *ctx; rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); @@ -1285,15 +1285,15 @@ ngx_http_js_variable_set(ngx_http_reques return NGX_ERROR; } - if (ngx_js_retval(ctx->vm, &ctx->retval, &value) != NGX_OK) { + if (ngx_js_string(ctx->vm, njs_value_arg(&ctx->retval), &value) != NGX_OK) { return NGX_ERROR; } - v->len = value.len; + v->len = value.length; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; - v->data = value.data; + v->data = value.start; return NGX_OK; } diff -r 8e024f36e38e -r 1d13f6e877ad nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Tue Nov 21 06:49:30 2023 -0800 +++ b/nginx/ngx_stream_js_module.c Tue Nov 21 08:57:03 2023 -0800 @@ -914,7 +914,7 @@ ngx_stream_js_variable_set(ngx_stream_se ngx_int_t rc; njs_int_t pending; - ngx_str_t value; + njs_str_t value; ngx_stream_js_ctx_t *ctx; rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); @@ -949,15 +949,15 @@ ngx_stream_js_variable_set(ngx_stream_se return NGX_ERROR; } - if (ngx_js_retval(ctx->vm, &ctx->retval, &value) != NGX_OK) { + if (ngx_js_string(ctx->vm, njs_value_arg(&ctx->retval), &value) != NGX_OK) { return NGX_ERROR; } - v->len = value.len; + v->len = value.length; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; - v->data = value.data; + v->data = value.start; return NGX_OK; } diff -r 8e024f36e38e -r 1d13f6e877ad nginx/t/js.t --- a/nginx/t/js.t Tue Nov 21 06:49:30 2023 -0800 +++ b/nginx/t/js.t Tue Nov 21 08:57:03 2023 -0800 @@ -46,6 +46,7 @@ http { js_set $test_global test.global_obj; js_set $test_log test.log; js_set $test_internal test.sub_internal; + js_set $buffer test.buffer; js_set $test_except test.except; js_import test.js; @@ -95,6 +96,10 @@ http { js_content test.status; } + location /buffer_variable { + js_content test.buffer_variable; + } + location /request_body { js_content test.request_body; } @@ -171,6 +176,10 @@ EOF return 'variable=' + r.variables.remote_addr; } + function buffer(r) { + return Buffer.from([0xaa, 0xbb, 0xcc, 0xdd]); + } + function global_obj(r) { return 'global=' + global; } @@ -225,6 +234,10 @@ EOF r.log('SEE-LOG'); } + function buffer_variable(r) { + r.return(200, r.rawVariables.buffer.toString('hex')); + } + async function internal(r) { let reply = await r.subrequest('/sub_internal'); @@ -248,14 +261,15 @@ EOF function content_empty(r) { } - export default {njs:test_njs, method, version, addr, uri, + export default {njs:test_njs, method, version, addr, uri, buffer, variable, global_obj, status, request_body, internal, request_body_cache, send, return_method, sub_internal, - type, log, except, content_except, content_empty}; + type, log, buffer_variable, except, content_except, + content_empty}; EOF -$t->try_run('no njs available')->plan(27); +$t->try_run('no njs available')->plan(28); ############################################################################### @@ -307,6 +321,14 @@ like(http_get('/internal'), qr/parent: f } + +TODO: { +local $TODO = 'not yet' unless has_version('0.8.3'); + +like(http_get('/buffer_variable'), qr/aabbccdd/, 'buffer variable'); + +} + http_get('/except'); http_get('/content_except'); diff -r 8e024f36e38e -r 1d13f6e877ad nginx/t/stream_js.t --- a/nginx/t/stream_js.t Tue Nov 21 06:49:30 2023 -0800 +++ b/nginx/t/stream_js.t Tue Nov 21 08:57:03 2023 -0800 @@ -68,6 +68,7 @@ stream { js_set $js_req_line test.req_line; js_set $js_sess_unk test.sess_unk; js_set $js_async test.asyncf; + js_set $js_buffer test.buffer; js_import test.js; @@ -190,6 +191,11 @@ stream { listen 127.0.0.1:8100; return $js_async; } + + server { + listen 127.0.0.1:8101; + return $js_buffer; + } } EOF @@ -211,8 +217,13 @@ EOF return 'sess_unk=' + s.unk; } + function buffer(s) { + return Buffer.from([0xaa, 0xbb, 0xcc, 0xdd]); + } + function log(s) { s.log("SEE-THIS"); + return 'log'; } var res = ''; @@ -377,12 +388,13 @@ EOF preread_step, filter_step, access_undecided, access_allow, access_deny, preread_async, preread_data, preread_req_line, req_line, filter_empty, filter_header_inject, filter_search, - access_except, preread_except, filter_except, asyncf}; + access_except, preread_except, filter_except, asyncf, + buffer}; EOF $t->run_daemon(\&stream_daemon, port(8090)); -$t->try_run('no stream njs available')->plan(23); +$t->try_run('no stream njs available')->plan(24); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### @@ -391,7 +403,7 @@ is(stream('127.0.0.1:' . port(8080))->re 's.remoteAddress'); is(dgram('127.0.0.1:' . port(8985))->io('.'), 'addr=127.0.0.1', 's.remoteAddress udp'); -is(stream('127.0.0.1:' . port(8081))->read(), 'undefined', 's.log'); +is(stream('127.0.0.1:' . port(8081))->read(), 'log', 's.log'); is(stream('127.0.0.1:' . port(8082))->read(), 'variable=127.0.0.1', 's.variables'); is(stream('127.0.0.1:' . port(8083))->read(), '', 'stream js unknown function'); @@ -417,6 +429,14 @@ stream('127.0.0.1:' . port(8099))->io('x is(stream('127.0.0.1:' . port(8100))->read(), 'retval: 30', 'asyncf'); +TODO: { +local $TODO = 'not yet' unless has_version('0.8.3'); + +like(stream('127.0.0.1:' . port(8101))->read(), qr/\xaa\xbb\xcc\xdd/, + 'buffer variable'); + +} + $t->stop(); ok(index($t->read_file('error.log'), 'SEE-THIS') > 0, 'stream js log'); @@ -432,6 +452,25 @@ like($t->read_file('status.log'), qr/$p[ ############################################################################### +sub has_version { + my $need = shift; + + get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + +############################################################################### + sub stream_daemon { my $server = IO::Socket::INET->new( Proto => 'tcp', From xeioex at nginx.com Tue Nov 21 16:58:03 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 21 Nov 2023 16:58:03 +0000 Subject: [njs] Renaming ngx_js_retval() to ngx_js_exception(). Message-ID: details: https://hg.nginx.org/njs/rev/896e7e271382 branches: changeset: 2237:896e7e271382 user: Dmitry Volyntsev date: Tue Nov 21 08:57:09 2023 -0800 description: Renaming ngx_js_retval() to ngx_js_exception(). After previous commit ngx_js_retval() is only used for getting the exception value and should be renamed to better reflect the purpose. diffstat: nginx/ngx_http_js_module.c | 4 ++-- nginx/ngx_js.c | 14 ++++---------- nginx/ngx_js.h | 3 +-- nginx/ngx_stream_js_module.c | 8 ++++---- 4 files changed, 11 insertions(+), 18 deletions(-) diffs (115 lines): diff -r 1d13f6e877ad -r 896e7e271382 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Tue Nov 21 08:57:03 2023 -0800 +++ b/nginx/ngx_http_js_module.c Tue Nov 21 08:57:09 2023 -0800 @@ -1401,7 +1401,7 @@ ngx_http_js_init_vm(ngx_http_request_t * } if (njs_vm_start(ctx->vm, njs_value_arg(&retval)) == NJS_ERROR) { - ngx_js_retval(ctx->vm, NULL, &exception); + ngx_js_exception(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %V", &exception); @@ -4526,7 +4526,7 @@ ngx_http_js_handle_vm_event(ngx_http_req (ngx_int_t) rc, vm_event); if (rc == NJS_ERROR) { - ngx_js_retval(ctx->vm, NULL, &exception); + ngx_js_exception(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %V", &exception); diff -r 1d13f6e877ad -r 896e7e271382 nginx/ngx_js.c --- a/nginx/ngx_js.c Tue Nov 21 08:57:03 2023 -0800 +++ b/nginx/ngx_js.c Tue Nov 21 08:57:09 2023 -0800 @@ -359,7 +359,7 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs, njs_value_arg(retval)); if (ret == NJS_ERROR) { - ngx_js_retval(vm, NULL, &exception); + ngx_js_exception(vm, &exception); ngx_log_error(NGX_LOG_ERR, log, 0, "js exception: %V", &exception); @@ -369,7 +369,7 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f ret = njs_vm_run(vm); if (ret == NJS_ERROR) { - ngx_js_retval(vm, NULL, &exception); + ngx_js_exception(vm, &exception); ngx_log_error(NGX_LOG_ERR, log, 0, "js exception: %V", &exception); @@ -382,18 +382,12 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f ngx_int_t -ngx_js_retval(njs_vm_t *vm, njs_opaque_value_t *retval, ngx_str_t *s) +ngx_js_exception(njs_vm_t *vm, ngx_str_t *s) { njs_int_t ret; njs_str_t str; - if (retval != NULL && njs_value_is_valid(njs_value_arg(retval))) { - ret = njs_vm_value_string(vm, &str, njs_value_arg(retval)); - - } else { - ret = njs_vm_exception_string(vm, &str); - } - + ret = njs_vm_exception_string(vm, &str); if (ret != NJS_OK) { return NGX_ERROR; } diff -r 1d13f6e877ad -r 896e7e271382 nginx/ngx_js.h --- a/nginx/ngx_js.h Tue Nov 21 08:57:03 2023 -0800 +++ b/nginx/ngx_js.h Tue Nov 21 08:57:09 2023 -0800 @@ -133,8 +133,7 @@ ngx_int_t ngx_js_call(njs_vm_t *vm, ngx_ njs_opaque_value_t *args, njs_uint_t nargs); ngx_int_t ngx_js_invoke(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, njs_opaque_value_t *args, njs_uint_t nargs, njs_opaque_value_t *retval); -ngx_int_t ngx_js_retval(njs_vm_t *vm, njs_opaque_value_t *retval, - ngx_str_t *s); +ngx_int_t ngx_js_exception(njs_vm_t *vm, ngx_str_t *s); njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t level, njs_value_t *retval); diff -r 1d13f6e877ad -r 896e7e271382 nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Tue Nov 21 08:57:03 2023 -0800 +++ b/nginx/ngx_stream_js_module.c Tue Nov 21 08:57:09 2023 -0800 @@ -754,7 +754,7 @@ ngx_stream_js_phase_handler(ngx_stream_s ret = ngx_stream_js_run_event(s, ctx, &ctx->events[NGX_JS_EVENT_UPLOAD], 0); if (ret != NJS_OK) { - ngx_js_retval(ctx->vm, NULL, &exception); + ngx_js_exception(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", &exception); @@ -841,7 +841,7 @@ ngx_stream_js_body_filter(ngx_stream_ses if (event->ev != NULL) { ret = ngx_stream_js_run_event(s, ctx, event, from_upstream); if (ret != NJS_OK) { - ngx_js_retval(ctx->vm, NULL, &exception); + ngx_js_exception(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", &exception); @@ -1063,7 +1063,7 @@ ngx_stream_js_init_vm(ngx_stream_session } if (njs_vm_start(ctx->vm, njs_value_arg(&retval)) == NJS_ERROR) { - ngx_js_retval(ctx->vm, NULL, &exception); + ngx_js_exception(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "js exception: %V", &exception); @@ -1812,7 +1812,7 @@ ngx_stream_js_handle_event(ngx_stream_se (ngx_int_t) rc, vm_event); if (rc == NJS_ERROR) { - ngx_js_retval(ctx->vm, NULL, &exception); + ngx_js_exception(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "js exception: %V", &exception); From xeioex at nginx.com Tue Nov 21 17:04:30 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 21 Nov 2023 17:04:30 +0000 Subject: [njs] Moving out setTimeout() and clearTimeout() from njs core. Message-ID: details: https://hg.nginx.org/njs/rev/dffdf7c50dfc branches: changeset: 2238:dffdf7c50dfc user: Dmitry Volyntsev date: Tue Nov 21 09:00:52 2023 -0800 description: Moving out setTimeout() and clearTimeout() from njs core. This functions are not part of the ECMAScript and should be implemented by host environment. diffstat: auto/sources | 1 - external/njs_shell.c | 314 ++++++++++++++++++++++++++---------------- nginx/ngx_http_js_module.c | 123 +++++----------- nginx/ngx_js.c | 273 +++++++++++++++++++++++++++++++++++++- nginx/ngx_js.h | 46 ++++++- nginx/ngx_stream_js_module.c | 120 ++++----------- src/njs.h | 2 - src/njs_builtin.c | 6 - src/njs_main.h | 1 - src/test/njs_unit_test.c | 24 --- test/shell_test.exp | 2 +- 11 files changed, 580 insertions(+), 332 deletions(-) diffs (truncated from 1505 to 1000 lines): diff -r 896e7e271382 -r dffdf7c50dfc auto/sources --- a/auto/sources Tue Nov 21 08:57:09 2023 -0800 +++ b/auto/sources Tue Nov 21 09:00:52 2023 -0800 @@ -33,7 +33,6 @@ NJS_LIB_SRCS=" \ src/njs_scope.c \ src/njs_generator.c \ src/njs_disassembler.c \ - src/njs_timer.c \ src/njs_module.c \ src/njs_event.c \ src/njs_extern.c \ diff -r 896e7e271382 -r dffdf7c50dfc external/njs_shell.c --- a/external/njs_shell.c Tue Nov 21 08:57:09 2023 -0800 +++ b/external/njs_shell.c Tue Nov 21 09:00:52 2023 -0800 @@ -11,8 +11,6 @@ #include #include #include -#include -#include #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) @@ -70,7 +68,12 @@ typedef struct { typedef struct { - njs_vm_event_t vm_event; + NJS_RBTREE_NODE (node); + njs_function_t *function; + njs_value_t *args; + njs_uint_t nargs; + uint32_t id; + njs_queue_link_t link; } njs_ev_t; @@ -85,7 +88,8 @@ typedef struct { typedef struct { njs_vm_t *vm; - njs_lvlhsh_t events; /* njs_ev_t * */ + uint32_t event_id; + njs_rbtree_t events; /* njs_ev_t * */ njs_queue_t posted_events; njs_queue_t labels; @@ -120,6 +124,12 @@ static char *njs_completion_generator(co #endif +static njs_int_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_ext_console_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t magic, njs_value_t *retval); static njs_int_t njs_ext_console_time(njs_vm_t *vm, njs_value_t *args, @@ -127,17 +137,11 @@ static njs_int_t njs_ext_console_time(nj static njs_int_t njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -static njs_host_event_t njs_console_set_timer(njs_external_ptr_t external, - uint64_t delay, njs_vm_event_t vm_event); - -static void njs_console_clear_timer(njs_external_ptr_t external, - njs_host_event_t event); static void njs_console_log(njs_vm_t *vm, njs_external_ptr_t external, njs_log_level_t level, const u_char *start, size_t length); -static njs_int_t lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data); -static void *lvlhsh_pool_alloc(void *pool, size_t size); -static void lvlhsh_pool_free(void *pool, void *p, size_t size); +static intptr_t njs_event_rbtree_compare(njs_rbtree_node_t *node1, + njs_rbtree_node_t *node2); njs_int_t njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); @@ -264,17 +268,7 @@ static njs_external_t njs_ext_262[] = { }; -static const njs_lvlhsh_proto_t lvlhsh_proto njs_aligned(64) = { - NJS_LVLHSH_LARGE_SLAB, - lvlhsh_key_test, - lvlhsh_pool_alloc, - lvlhsh_pool_free, -}; - - static njs_vm_ops_t njs_console_ops = { - njs_console_set_timer, - njs_console_clear_timer, NULL, njs_console_log, }; @@ -635,7 +629,8 @@ njs_console_init(njs_vm_t *vm, njs_conso { console->vm = vm; - njs_lvlhsh_init(&console->events); + console->event_id = 0; + njs_rbtree_init(&console->events, njs_event_rbtree_compare); njs_queue_init(&console->posted_events); njs_queue_init(&console->labels); @@ -649,6 +644,24 @@ njs_console_init(njs_vm_t *vm, njs_conso static njs_int_t +njs_function_bind(njs_vm_t *vm, const njs_str_t *name, + njs_function_native_t native, njs_bool_t ctor) +{ + njs_function_t *f; + njs_opaque_value_t value; + + f = njs_vm_function_alloc(vm, native, 1, ctor); + if (f == NULL) { + return NJS_ERROR; + } + + njs_value_function_set(njs_value_arg(&value), f); + + return njs_vm_bind(vm, name, njs_value_arg(&value), 1); +} + + +static njs_int_t njs_externals_init(njs_vm_t *vm) { njs_int_t ret, proto_id; @@ -659,6 +672,9 @@ njs_externals_init(njs_vm_t *vm) static const njs_str_t dollar_262 = njs_str("$262"); static const njs_str_t print_name = njs_str("print"); static const njs_str_t console_log = njs_str("console.log"); + static const njs_str_t set_timeout = njs_str("setTimeout"); + static const njs_str_t set_immediate = njs_str("setImmediate"); + static const njs_str_t clear_timeout = njs_str("clearTimeout"); console = njs_vm_options(vm)->external; @@ -690,8 +706,18 @@ njs_externals_init(njs_vm_t *vm) return NJS_ERROR; } - ret = njs_console_init(vm, console); - if (njs_slow_path(ret != NJS_OK)) { + ret = njs_function_bind(vm, &set_timeout, njs_set_timeout, 0); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_function_bind(vm, &set_immediate, njs_set_immediate, 0); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_function_bind(vm, &clear_timeout, njs_clear_timeout, 0); + if (ret != NJS_OK) { return NJS_ERROR; } @@ -712,6 +738,11 @@ njs_externals_init(njs_vm_t *vm) return NJS_ERROR; } + ret = njs_console_init(vm, console); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + return NJS_OK; } @@ -829,10 +860,13 @@ njs_console_output(njs_vm_t *vm, njs_val static njs_int_t njs_process_events(void *runtime) { - njs_ev_t *ev; - njs_queue_t *events; - njs_console_t *console; - njs_queue_link_t *link; + njs_ev_t *ev; + njs_vm_t *vm; + njs_int_t ret; + njs_queue_t *events; + njs_console_t *console; + njs_queue_link_t *link; + njs_opaque_value_t retval; if (runtime == NULL) { njs_stderror("njs_process_events(): no runtime\n"); @@ -840,6 +874,7 @@ njs_process_events(void *runtime) } console = runtime; + vm = console->vm; events = &console->posted_events; @@ -856,10 +891,24 @@ njs_process_events(void *runtime) ev->link.prev = NULL; ev->link.next = NULL; - njs_vm_post_event(console->vm, ev->vm_event, NULL, 0); + njs_rbtree_delete(&console->events, &ev->node); + + ret = njs_vm_invoke(vm, ev->function, ev->args, ev->nargs, + njs_value_arg(&retval)); + if (ret == NJS_ERROR) { + njs_process_output(vm, njs_value_arg(&retval), ret); + + if (!njs_vm_options(vm)->interactive) { + return NJS_ERROR; + } + } } - return NJS_OK; + if (!njs_rbtree_is_empty(&console->events)) { + return NJS_AGAIN; + } + + return njs_vm_pending(vm) ? NJS_AGAIN: NJS_OK; } @@ -1047,28 +1096,7 @@ njs_process_script(njs_vm_t *vm, void *r } for ( ;; ) { - if (!njs_vm_pending(vm) && !njs_vm_unhandled_rejection(vm)) { - ret = NJS_OK; - break; - } - - ret = njs_process_events(runtime); - if (njs_slow_path(ret != NJS_OK)) { - njs_stderror("njs_process_events() failed\n"); - ret = NJS_ERROR; - break; - } - - if (njs_vm_waiting(vm) && !njs_vm_posted(vm)) { - /*TODO: async events. */ - - njs_stderror("njs_process_script(): async events unsupported\n"); - ret = NJS_ERROR; - break; - } - ret = njs_vm_run(vm); - if (ret == NJS_ERROR) { njs_process_output(vm, njs_value_arg(&retval), ret); @@ -1076,6 +1104,15 @@ njs_process_script(njs_vm_t *vm, void *r return NJS_ERROR; } } + + ret = njs_process_events(runtime); + if (njs_slow_path(ret == NJS_ERROR)) { + break; + } + + if (ret == NJS_OK) { + break; + } } return ret; @@ -1534,81 +1571,119 @@ njs_ext_console_time_end(njs_vm_t *vm, n } -static njs_host_event_t -njs_console_set_timer(njs_external_ptr_t external, uint64_t delay, - njs_vm_event_t vm_event) +static njs_int_t +njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_bool_t immediate, njs_value_t *retval) { - njs_ev_t *ev; - njs_vm_t *vm; - njs_int_t ret; - njs_console_t *console; - njs_lvlhsh_query_t lhq; + njs_ev_t *ev; + uint64_t delay; + njs_uint_t n; + njs_console_t *console; + + console = njs_vm_external_ptr(vm); - console = external; - vm = console->vm; + if (njs_slow_path(nargs < 2)) { + njs_vm_type_error(vm, "too few arguments"); + return NJS_ERROR; + } - if (delay != 0) { - njs_vm_err(vm, "njs_console_set_timer(): async timers unsupported\n"); - return NULL; + if (njs_slow_path(!njs_value_is_function(njs_argument(args, 1)))) { + njs_vm_type_error(vm, "first arg must be a function"); + return NJS_ERROR; } - ev = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_ev_t)); - if (njs_slow_path(ev == NULL)) { - return NULL; + delay = 0; + + if (!immediate && nargs >= 3 + && njs_value_is_number(njs_argument(args, 2))) + { + delay = njs_value_number(njs_argument(args, 2)); + } + + if (delay != 0) { + njs_vm_internal_error(vm, "njs_set_timer(): async timers unsupported"); + return NJS_ERROR; } - ev->vm_event = vm_event; + n = immediate ? 2 : 3; + nargs = (nargs >= n) ? nargs - n : 0; - lhq.key.start = (u_char *) &ev->vm_event; - lhq.key.length = sizeof(njs_vm_event_t); - lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length); + ev = njs_mp_alloc(njs_vm_memory_pool(vm), + sizeof(njs_ev_t) + sizeof(njs_opaque_value_t) * nargs); + if (njs_slow_path(ev == NULL)) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } - lhq.replace = 0; - lhq.value = ev; - lhq.proto = &lvlhsh_proto; - lhq.pool = njs_vm_memory_pool(vm); + ev->function = njs_value_function(njs_argument(args, 1)); + ev->nargs = nargs; + ev->args = (njs_value_t *) ((u_char *) ev + sizeof(njs_ev_t)); + ev->id = console->event_id++; - ret = njs_lvlhsh_insert(&console->events, &lhq); - if (njs_slow_path(ret != NJS_OK)) { - return NULL; + if (ev->nargs != 0) { + memcpy(ev->args, njs_argument(args, n), + sizeof(njs_opaque_value_t) * ev->nargs); } + njs_rbtree_insert(&console->events, &ev->node); + njs_queue_insert_tail(&console->posted_events, &ev->link); - return (njs_host_event_t) ev; + njs_value_number_set(retval, ev->id); + + return NJS_OK; +} + + +static njs_int_t +njs_set_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + return njs_set_timer(vm, args, nargs, unused, 0, retval); +} + + +static njs_int_t +njs_set_immediate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + return njs_set_timer(vm, args, nargs, unused, 1, retval); } -static void -njs_console_clear_timer(njs_external_ptr_t external, njs_host_event_t event) +static njs_int_t +njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) { - njs_vm_t *vm; - njs_ev_t *ev; - njs_int_t ret; - njs_console_t *console; - njs_lvlhsh_query_t lhq; + njs_ev_t ev_lookup, *ev; + njs_console_t *console; + njs_rbtree_node_t *rb; - ev = event; - console = external; - vm = console->vm; - - lhq.key.start = (u_char *) &ev->vm_event; - lhq.key.length = sizeof(njs_vm_event_t); - lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length); - - lhq.proto = &lvlhsh_proto; - lhq.pool = njs_vm_memory_pool(vm); - - if (ev->link.prev != NULL) { - njs_queue_remove(&ev->link); + if (nargs < 2 || !njs_value_is_number(njs_argument(args, 1))) { + njs_value_undefined_set(retval); + return NJS_OK; } - ret = njs_lvlhsh_delete(&console->events, &lhq); - if (ret != NJS_OK) { - njs_vm_err(vm, "njs_lvlhsh_delete() failed\n"); + console = njs_vm_external_ptr(vm); + + ev_lookup.id = njs_value_number(njs_argument(args, 1)); + + rb = njs_rbtree_find(&console->events, &ev_lookup.node); + if (njs_slow_path(rb == NULL)) { + njs_vm_internal_error(vm, "failed to find timer"); + return NJS_ERROR; } - njs_mp_free(njs_vm_memory_pool(vm), ev); + njs_rbtree_delete(&console->events, (njs_rbtree_part_t *) rb); + + ev = (njs_ev_t *) rb; + njs_queue_remove(&ev->link); + ev->link.prev = NULL; + ev->link.next = NULL; + + njs_value_undefined_set(retval); + + return NJS_OK; } @@ -1630,30 +1705,21 @@ njs_console_log(njs_vm_t *vm, njs_extern } -static njs_int_t -lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data) +static intptr_t +njs_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2) { - njs_ev_t *ev; + njs_ev_t *ev1, *ev2; - ev = data; + ev1 = (njs_ev_t *) node1; + ev2 = (njs_ev_t *) node2; - if (memcmp(&ev->vm_event, lhq->key.start, sizeof(njs_vm_event_t)) == 0) { - return NJS_OK; + if (ev1->id < ev2->id) { + return -1; } - return NJS_DECLINED; -} - + if (ev1->id > ev2->id) { + return 1; + } -static void * -lvlhsh_pool_alloc(void *pool, size_t size) -{ - return njs_mp_align(pool, NJS_MAX_ALIGNMENT, size); + return 0; } - - -static void -lvlhsh_pool_free(void *pool, void *p, size_t size) -{ - njs_mp_free(pool, p); -} diff -r 896e7e271382 -r dffdf7c50dfc nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Tue Nov 21 08:57:09 2023 -0800 +++ b/nginx/ngx_http_js_module.c Tue Nov 21 09:00:52 2023 -0800 @@ -49,7 +49,7 @@ typedef struct { typedef struct { - njs_vm_t *vm; + NGX_JS_COMMON_CTX; ngx_log_t *log; ngx_uint_t done; ngx_int_t status; @@ -78,14 +78,6 @@ typedef struct { typedef struct { - ngx_http_request_t *request; - njs_vm_event_t vm_event; - void *unused; - ngx_int_t ident; -} ngx_http_js_event_t; - - -typedef struct { njs_str_t name; #if defined(nginx_version) && (nginx_version >= 1023000) unsigned flags; @@ -264,11 +256,6 @@ static njs_int_t ngx_http_js_location(nj unsigned flags, njs_str_t *name, njs_value_t *setval, njs_value_t *retval); -static njs_host_event_t ngx_http_js_set_timer(njs_external_ptr_t external, - uint64_t delay, njs_vm_event_t vm_event); -static void ngx_http_js_clear_timer(njs_external_ptr_t external, - njs_host_event_t event); -static void ngx_http_js_timer_handler(ngx_event_t *ev); static ngx_pool_t *ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r); static ngx_resolver_t *ngx_http_js_resolver(njs_vm_t *vm, ngx_http_request_t *r); @@ -281,6 +268,8 @@ static size_t ngx_http_js_max_response_b ngx_http_request_t *r); static void ngx_http_js_handle_vm_event(ngx_http_request_t *r, njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs); +static void ngx_http_js_event_finalize(ngx_http_request_t *r, njs_int_t rc); +static ngx_js_ctx_t *ngx_http_js_ctx(njs_vm_t *vm, ngx_http_request_t *r); static void ngx_http_js_periodic_handler(ngx_event_t *ev); static void ngx_http_js_periodic_write_event_handler(ngx_http_request_t *r); @@ -853,8 +842,6 @@ static njs_external_t ngx_http_js_ext_p static njs_vm_ops_t ngx_http_js_ops = { - ngx_http_js_set_timer, - ngx_http_js_clear_timer, NULL, ngx_js_logger, }; @@ -872,6 +859,8 @@ static uintptr_t ngx_http_js_uptr[] = { (uintptr_t) ngx_http_js_buffer_size, (uintptr_t) ngx_http_js_max_response_buffer_size, (uintptr_t) 0 /* main_conf ptr */, + (uintptr_t) ngx_http_js_event_finalize, + (uintptr_t) ngx_http_js_ctx, }; @@ -991,7 +980,7 @@ ngx_http_js_content_write_event_handler( ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (!njs_vm_pending(ctx->vm)) { + if (!ngx_vm_pending(ctx)) { ngx_http_js_content_finalize(r, ctx); return; } @@ -1087,7 +1076,7 @@ ngx_http_js_header_filter(ngx_http_reque ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); ctx->filter = 1; - pending = njs_vm_pending(ctx->vm); + pending = ngx_vm_pending(ctx); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http js header call \"%V\"", &jlcf->header_filter); @@ -1190,7 +1179,7 @@ ngx_http_js_body_filter(ngx_http_request return ret; } - pending = njs_vm_pending(ctx->vm); + pending = ngx_vm_pending(ctx); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http js body call \"%V\"", &jlcf->body_filter); @@ -1269,7 +1258,7 @@ ngx_http_js_variable_set(ngx_http_reques ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - pending = njs_vm_pending(ctx->vm); + pending = ngx_vm_pending(ctx); rc = ngx_js_invoke(ctx->vm, fname, r->connection->log, &ctx->request, 1, &ctx->retval); @@ -1352,6 +1341,8 @@ ngx_http_js_init_vm(ngx_http_request_t * return NGX_ERROR; } + ngx_js_ctx_init((ngx_js_ctx_t *) ctx); + njs_value_invalid_set(njs_value_arg(&ctx->retval)); ngx_http_set_ctx(r, ctx, ngx_http_js_module); @@ -1424,14 +1415,14 @@ ngx_http_js_cleanup_ctx(void *data) { ngx_http_js_ctx_t *ctx = data; - if (njs_vm_pending(ctx->vm)) { + if (ngx_vm_pending(ctx)) { ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http js vm destroy: %p", ctx->vm); - njs_vm_destroy(ctx->vm); + ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx); } @@ -4280,7 +4271,7 @@ ngx_http_js_periodic_write_event_handler ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (!njs_vm_pending(ctx->vm)) { + if (!ngx_vm_pending(ctx)) { ngx_http_js_periodic_finalize(r, NGX_OK); return; } @@ -4317,9 +4308,9 @@ ngx_http_js_periodic_finalize(ngx_http_r ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http js periodic finalize: \"%V\" rc: %i c: %i pending: %i", &ctx->periodic->method, rc, r->count, - njs_vm_pending(ctx->vm)); - - if (r->count > 1 || (rc == NGX_OK && njs_vm_pending(ctx->vm))) { + ngx_vm_pending(ctx)); + + if (r->count > 1 || (rc == NGX_OK && ngx_vm_pending(ctx))) { return; } @@ -4386,65 +4377,6 @@ ngx_http_js_periodic_init(ngx_js_periodi } -static njs_host_event_t -ngx_http_js_set_timer(njs_external_ptr_t external, uint64_t delay, - njs_vm_event_t vm_event) -{ - ngx_event_t *ev; - ngx_http_request_t *r; - ngx_http_js_event_t *js_event; - - r = (ngx_http_request_t *) external; - - ev = ngx_pcalloc(r->pool, sizeof(ngx_event_t)); - if (ev == NULL) { - return NULL; - } - - js_event = ngx_palloc(r->pool, sizeof(ngx_http_js_event_t)); - if (js_event == NULL) { - return NULL; - } - - js_event->request = r; - js_event->vm_event = vm_event; - js_event->ident = r->connection->fd; - - ev->data = js_event; - ev->log = r->connection->log; - ev->handler = ngx_http_js_timer_handler; - - ngx_add_timer(ev, delay); - - return ev; -} - - -static void -ngx_http_js_clear_timer(njs_external_ptr_t external, njs_host_event_t event) -{ - ngx_event_t *ev = event; - - if (ev->timer_set) { - ngx_del_timer(ev); - } -} - - -static void -ngx_http_js_timer_handler(ngx_event_t *ev) -{ - ngx_http_request_t *r; - ngx_http_js_event_t *js_event; - - js_event = (ngx_http_js_event_t *) ev->data; - - r = js_event->request; - - ngx_http_js_handle_vm_event(r, js_event->vm_event, NULL, 0); -} - - static ngx_pool_t * ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r) { @@ -4530,7 +4462,19 @@ ngx_http_js_handle_vm_event(ngx_http_req ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %V", &exception); - + } + + ngx_http_js_event_finalize(r, rc); +} + + +static void +ngx_http_js_event_finalize(ngx_http_request_t *r, njs_int_t rc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js event finalize rc: %i", (ngx_int_t) rc); + + if (rc == NJS_ERROR) { if (r->health_check) { ngx_http_js_periodic_finalize(r, NGX_ERROR); return; @@ -4548,6 +4492,13 @@ ngx_http_js_handle_vm_event(ngx_http_req } +static ngx_js_ctx_t * +ngx_http_js_ctx(njs_vm_t *vm, ngx_http_request_t *r) +{ + return ngx_http_get_module_ctx(r, ngx_http_js_module); +} + + static njs_int_t ngx_js_http_init(njs_vm_t *vm) { diff -r 896e7e271382 -r dffdf7c50dfc nginx/ngx_js.c --- a/nginx/ngx_js.c Tue Nov 21 08:57:09 2023 -0800 +++ b/nginx/ngx_js.c Tue Nov 21 09:00:52 2023 -0800 @@ -43,6 +43,12 @@ static njs_int_t ngx_js_ext_console_time njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t ngx_js_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static void ngx_js_cleanup_vm(void *data); static njs_int_t ngx_js_core_init(njs_vm_t *vm); @@ -344,6 +350,7 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f njs_int_t ret; njs_str_t name; ngx_str_t exception; + ngx_js_ctx_t *ctx; njs_function_t *func; name.start = fname->data; @@ -377,7 +384,13 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f return NGX_ERROR; } - return (ret == NJS_AGAIN) ? NGX_AGAIN : NGX_OK; + if (ret == NJS_AGAIN) { + return NGX_AGAIN; + } + + ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); + + return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN; } @@ -431,12 +444,88 @@ ngx_js_string(njs_vm_t *vm, njs_value_t static njs_int_t +njs_function_bind(njs_vm_t *vm, const njs_str_t *name, + njs_function_native_t native, njs_bool_t ctor) +{ + njs_function_t *f; + njs_opaque_value_t value; + + f = njs_vm_function_alloc(vm, native, 1, ctor); + if (f == NULL) { + return NJS_ERROR; + } + + njs_value_function_set(njs_value_arg(&value), f); + + return njs_vm_bind(vm, name, njs_value_arg(&value), 1); +} + + + +static intptr_t +ngx_js_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2) +{ + ngx_js_event_t *ev1, *ev2; + + ev1 = (ngx_js_event_t *) ((u_char *) node1 + - offsetof(ngx_js_event_t, node)); + ev2 = (ngx_js_event_t *) ((u_char *) node2 + - offsetof(ngx_js_event_t, node)); + + if (ev1->fd < ev2->fd) { + return -1; + } + + if (ev1->fd > ev2->fd) { + return 1; + } + + return 0; +} + + +void +ngx_js_ctx_init(ngx_js_ctx_t *ctx) +{ + ctx->event_id = 0; + njs_rbtree_init(&ctx->waiting_events, ngx_js_event_rbtree_compare); +} + + +void +ngx_js_ctx_destroy(ngx_js_ctx_t *ctx) +{ + ngx_js_event_t *event; + njs_rbtree_node_t *node; + + node = njs_rbtree_min(&ctx->waiting_events); + + while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { + event = (ngx_js_event_t *) ((u_char *) node + - offsetof(ngx_js_event_t, node)); + + if (event->destructor != NULL) { + event->destructor(njs_vm_external_ptr(event->vm), event); + } + + node = njs_rbtree_node_successor(&ctx->waiting_events, node); + } + + njs_vm_destroy(ctx->vm); +} + + +static njs_int_t ngx_js_core_init(njs_vm_t *vm) { njs_int_t ret, proto_id; njs_str_t name; njs_opaque_value_t value; + static const njs_str_t set_timeout = njs_str("setTimeout"); + static const njs_str_t set_immediate = njs_str("setImmediate"); + static const njs_str_t clear_timeout = njs_str("clearTimeout"); + proto_id = njs_vm_external_prototype(vm, ngx_js_ext_core, njs_nitems(ngx_js_ext_core)); if (proto_id < 0) { @@ -476,6 +565,21 @@ ngx_js_core_init(njs_vm_t *vm) return NJS_ERROR; } + ret = njs_function_bind(vm, &set_timeout, njs_set_timeout, 1); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_function_bind(vm, &set_immediate, njs_set_immediate, 1); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_function_bind(vm, &clear_timeout, njs_clear_timeout, 1); + if (ret != NJS_OK) { + return NJS_ERROR; + } + return NJS_OK; } @@ -859,6 +963,173 @@ not_found: } +static void +ngx_js_timer_handler(ngx_event_t *ev) +{ + njs_vm_t *vm; + njs_int_t ret; + ngx_str_t exception; + ngx_js_ctx_t *ctx; + ngx_js_event_t *event; + ngx_connection_t *c; + njs_external_ptr_t external; + njs_opaque_value_t retval; + + event = (ngx_js_event_t *) ((u_char *) ev - offsetof(ngx_js_event_t, ev)); + + vm = event->vm; + + ret = njs_vm_invoke(vm, event->function, event->args, event->nargs, + njs_value_arg(&retval)); + + external = njs_vm_external_ptr(vm); + ctx = ngx_external_ctx(vm, external); + njs_rbtree_delete(&ctx->waiting_events, &event->node); + + if (ret == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + c = ngx_external_connection(vm, njs_vm_external_ptr(vm)); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js exception: %V", &exception); + } + + ngx_external_event_finalize(vm)(external, ret); +} + + +static void +ngx_js_clear_timer(njs_external_ptr_t external, ngx_js_event_t *event) +{ + if (event->ev.timer_set) { + ngx_del_timer(&event->ev); + } +} + + +static njs_int_t +njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_bool_t immediate, njs_value_t *retval) +{ + uint64_t delay; + njs_uint_t n; + ngx_js_ctx_t *ctx; + ngx_js_event_t *event; + ngx_connection_t *c; + + if (njs_slow_path(nargs < 2)) { + njs_vm_type_error(vm, "too few arguments"); + return NJS_ERROR; + } + + if (njs_slow_path(!njs_value_is_function(njs_argument(args, 1)))) { + njs_vm_type_error(vm, "first arg must be a function"); + return NJS_ERROR; + } + + delay = 0; + + if (!immediate && nargs >= 3 + && njs_value_is_number(njs_argument(args, 2))) + { + delay = njs_value_number(njs_argument(args, 2)); + } + + n = immediate ? 2 : 3; + nargs = (nargs >= n) ? nargs - n : 0; + + event = njs_mp_zalloc(njs_vm_memory_pool(vm), + sizeof(ngx_js_event_t) + + sizeof(njs_opaque_value_t) * nargs); + if (njs_slow_path(event == NULL)) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + event->vm = vm; + event->function = njs_value_function(njs_argument(args, 1)); + event->nargs = nargs; + event->args = (njs_value_t *) ((u_char *) event + sizeof(ngx_js_event_t)); + event->destructor = ngx_js_clear_timer; + + ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); + event->fd = ctx->event_id++; + + c = ngx_external_connection(vm, njs_vm_external_ptr(vm)); + + event->ev.log = c->log; + event->ev.data = event; + event->ev.handler = ngx_js_timer_handler; + + if (event->nargs != 0) { + memcpy(event->args, njs_argument(args, n), + sizeof(njs_opaque_value_t) * event->nargs); + } + + njs_rbtree_insert(&ctx->waiting_events, &event->node); + + ngx_add_timer(&event->ev, delay); From arut at nginx.com Wed Nov 22 12:41:27 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 22 Nov 2023 16:41:27 +0400 Subject: QUIC: improved huffman decode debug tracing. In-Reply-To: <20231114115724.ub2d6qp5cbmsfj7x@Y9MQ9X2QVV> References: <4d14bfec.6a40.18b85d7527d.Coremail.winshining@163.com> <20231114115724.ub2d6qp5cbmsfj7x@Y9MQ9X2QVV> Message-ID: <20231122124127.zgtsjyfazfdnv6s4@N00W24XTQX> Hi, On Tue, Nov 14, 2023 at 03:57:24PM +0400, Sergey Kandaurov wrote: > On Tue, Oct 31, 2023 at 09:06:03PM +0800, winshining wrote: > > Previously, only HTTP2 used huffman encoding (gRPC is util now > > HTTP2 based), as HTTP3 becomes available, both of them uses huffman > > encoding. But existed debug log in huffman decode function is hard > > coded using "http2" prefixes, if a client transports an incorrect > > huffman encoded field value in an HTTP3 request, it will give an > > erroneous log. With the patch, it will properly log a bad field value. > > While indeed it makes sense to adjust hardcoded "http2" prefixes, > I don't think this is a reason to break ngx_http_huff_decode() API. > (It was once broken when moving Huffman coding out of HTTP/2, > but that was quite justified). > > > Alternatively, removing "http2" prefixes only is ok, but it can not > > differentiate whether it is caused by an HTTP2 or an HTTP3 request. > > Affected messages are logged at the debug level anyway, so this > should not be a problem, it can be easily seen from the context - > by looking at the adjacent messages belonging to the connection. > > [ Note that your attachment is sent as application/octet-stream, > I had to adjust my mailcap in order to include it in the reply.] > > > # HG changeset patch > > # User XingY Wang > > # Date 1698714765 -28800 > > # Tue Oct 31 09:12:45 2023 +0800 > > # Node ID 760857905505f91c9627f3bfeb5b6e496f4992a8 > > # Parent 7ec761f0365f418511e30b82e9adf80bc56681df > > QUIC: improved huffman decode debug tracing. > > > > Previously, only HTTP2 used huffman encoding (gRPC is util now > > HTTP2 based), as HTTP3 becomes available, both of them uses huffman > > encoding. But existed debug log in huffman decode function is hard > > coded using "http2" prefixes, if a client transports an incorrect > > huffman encoded field value in an HTTP3 request, it will give an > > erroneous log. With the patch, it will properly log a bad field value. > > Alternatively, removing "http2" prefixes only is ok, but it can not > > differentiate whether it is caused by an HTTP2 or an HTTP3 request. > > > > diff -r 7ec761f0365f -r 760857905505 src/http/modules/ngx_http_grpc_module.c > > --- a/src/http/modules/ngx_http_grpc_module.c Thu Oct 26 23:35:09 2023 +0300 > > +++ b/src/http/modules/ngx_http_grpc_module.c Tue Oct 31 09:12:45 2023 +0800 > > @@ -3189,6 +3189,7 @@ > > if (ngx_http_huff_decode(&ctx->field_state, p, size, > > &ctx->field_end, > > ctx->field_rest == 0, > > + NGX_HTTP_VERSION_20, > > r->connection->log) > > != NGX_OK) > > { > > @@ -3298,6 +3299,7 @@ > > if (ngx_http_huff_decode(&ctx->field_state, p, size, > > &ctx->field_end, > > ctx->field_rest == 0, > > + NGX_HTTP_VERSION_20, > > r->connection->log) > > != NGX_OK) > > { > > diff -r 7ec761f0365f -r 760857905505 src/http/ngx_http.h > > --- a/src/http/ngx_http.h Thu Oct 26 23:35:09 2023 +0300 > > +++ b/src/http/ngx_http.h Tue Oct 31 09:12:45 2023 +0800 > > @@ -179,7 +179,7 @@ > > > > #if (NGX_HTTP_V2 || NGX_HTTP_V3) > > ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, > > - u_char **dst, ngx_uint_t last, ngx_log_t *log); > > + u_char **dst, ngx_uint_t last, ngx_uint_t http_version, ngx_log_t *log); > > size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, > > ngx_uint_t lower); > > #endif > > diff -r 7ec761f0365f -r 760857905505 src/http/ngx_http_huff_decode.c > > --- a/src/http/ngx_http_huff_decode.c Thu Oct 26 23:35:09 2023 +0300 > > +++ b/src/http/ngx_http_huff_decode.c Tue Oct 31 09:12:45 2023 +0800 > > @@ -2641,9 +2641,22 @@ > > > > ngx_int_t > > ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, > > - ngx_uint_t last, ngx_log_t *log) > > + ngx_uint_t last, ngx_uint_t http_version, ngx_log_t *log) > > { > > u_char *end, ch, ending; > > +#if (NGX_DEBUG) > > + char *from = NULL; > > + > > + switch (http_version) { > > + > > + case NGX_HTTP_VERSION_30: > > + from = "http3"; > > + break; > > + > > + default: > > + from = "http2"; > > + } > > +#endif > > > > ch = 0; > > ending = 1; > > @@ -2656,9 +2669,9 @@ > > if (ngx_http_huff_decode_bits(state, &ending, ch >> 4, dst) > > != NGX_OK) > > { > > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > > - "http2 huffman decoding error at state %d: " > > - "bad code 0x%Xd", *state, ch >> 4); > > + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0, > > + "%s huffman decoding error at state %d: " > > + "bad code 0x%Xd", from, *state, ch >> 4); > > > > return NGX_ERROR; > > } > > @@ -2666,9 +2679,9 @@ > > if (ngx_http_huff_decode_bits(state, &ending, ch & 0xf, dst) > > != NGX_OK) > > { > > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > > - "http2 huffman decoding error at state %d: " > > - "bad code 0x%Xd", *state, ch & 0xf); > > + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, log, 0, > > + "%s huffman decoding error at state %d: " > > + "bad code 0x%Xd", from, *state, ch & 0xf); > > > > return NGX_ERROR; > > } > > @@ -2676,9 +2689,9 @@ > > > > if (last) { > > if (!ending) { > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, > > - "http2 huffman decoding error: " > > - "incomplete code 0x%Xd", ch); > > + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > > + "%s huffman decoding error: " > > + "incomplete code 0x%Xd", from, ch); > > > > return NGX_ERROR; > > } > > diff -r 7ec761f0365f -r 760857905505 src/http/v2/ngx_http_v2.c > > --- a/src/http/v2/ngx_http_v2.c Thu Oct 26 23:35:09 2023 +0300 > > +++ b/src/http/v2/ngx_http_v2.c Tue Oct 31 09:12:45 2023 +0800 > > @@ -1579,6 +1579,7 @@ > > if (ngx_http_huff_decode(&h2c->state.field_state, pos, size, > > &h2c->state.field_end, > > h2c->state.field_rest == 0, > > + NGX_HTTP_VERSION_20, > > h2c->connection->log) > > != NGX_OK) > > { > > diff -r 7ec761f0365f -r 760857905505 src/http/v3/ngx_http_v3_parse.c > > --- a/src/http/v3/ngx_http_v3_parse.c Thu Oct 26 23:35:09 2023 +0300 > > +++ b/src/http/v3/ngx_http_v3_parse.c Tue Oct 31 09:12:45 2023 +0800 > > @@ -647,7 +647,8 @@ > > > > if (st->huffman) { > > if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, > > - st->length == 1, c->log) > > + st->length == 1, NGX_HTTP_VERSION_30, > > + c->log) > > != NGX_OK) > > { > > return NGX_ERROR; > > This introduces complexity just for debugging purpose. > Removing any mention of HTTP protocol version should be enough. > Huffman coding routines do not depend on HTTP protocol versions, > both HPACK and QPACK refer to the same part of specification, > that is Appendix B of RFC 7541. > > Also, while looking at ngx_http_huff_decode() use-cases, > I noticed that HTTP/3 doesn't log Huffman decoding errors, > which it probably should because this is a user-induced > error, and for consistency with other nginx core modules. > > # HG changeset patch > # User Sergey Kandaurov > # Date 1699959003 -14400 > # Tue Nov 14 14:50:03 2023 +0400 > # Node ID c458cd00bb0bca8804ed831474533a813bcfd134 > # Parent 7ec761f0365f418511e30b82e9adf80bc56681df > Adjusted Huffman coding debug logging, missed in 7977:336084ff943b. > > Spotted by XingY Wang. > > diff --git a/src/http/ngx_http_huff_decode.c b/src/http/ngx_http_huff_decode.c > --- a/src/http/ngx_http_huff_decode.c > +++ b/src/http/ngx_http_huff_decode.c > @@ -2657,7 +2657,7 @@ ngx_http_huff_decode(u_char *state, u_ch > != NGX_OK) > { > ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > - "http2 huffman decoding error at state %d: " > + "http huffman decoding error at state %d: " > "bad code 0x%Xd", *state, ch >> 4); > > return NGX_ERROR; > @@ -2667,7 +2667,7 @@ ngx_http_huff_decode(u_char *state, u_ch > != NGX_OK) > { > ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, > - "http2 huffman decoding error at state %d: " > + "http huffman decoding error at state %d: " > "bad code 0x%Xd", *state, ch & 0xf); > > return NGX_ERROR; > @@ -2677,7 +2677,7 @@ ngx_http_huff_decode(u_char *state, u_ch > if (last) { > if (!ending) { > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, > - "http2 huffman decoding error: " > + "http huffman decoding error: " > "incomplete code 0x%Xd", ch); > > return NGX_ERROR; > # HG changeset patch > # User Sergey Kandaurov > # Date 1699961162 -14400 > # Tue Nov 14 15:26:02 2023 +0400 > # Node ID f366007dd23a6ce8e8427c1b3042781b618a2ade > # Parent c458cd00bb0bca8804ed831474533a813bcfd134 > HTTP/3: added Huffman decoding error logging. > > diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c > --- a/src/http/v3/ngx_http_v3_parse.c > +++ b/src/http/v3/ngx_http_v3_parse.c > @@ -650,6 +650,8 @@ ngx_http_v3_parse_literal(ngx_connection > st->length == 1, c->log) > != NGX_OK) > { > + ngx_log_error(NGX_LOG_INFO, c->log, 0, > + "client sent invalid encoded field line"); > return NGX_ERROR; > } Both patches look good to me. -- Roman Arutyunyan From jordanc.carter at outlook.com Thu Nov 23 14:52:55 2023 From: jordanc.carter at outlook.com (J Carter) Date: Thu, 23 Nov 2023 14:52:55 +0000 Subject: [PATCH] Added merge inheritance to proxy_set_header Message-ID: # HG changeset patch # User J Carter # Date 1700751016 0 # Thu Nov 23 14:50:16 2023 +0000 # Node ID cc79903ca02eff8aa87238a0f801f8070425d9ab # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Added merge inheritance to proxy_set_header This patch introduces 'inherit' argument to proxy_set_header directive. When marked as such it will be merge inherited into child contexts regardless of the presence of other proxy_set_header directives within those contexts. This patch also introduces the 'proxy_set_header_inherit' directive which blocks the merge inheritance in receiving contexts when set to off. The purpose of the added mechanics is to reduce repetition within the nginx configuration for universally set (or boilerplate) request headers, while maintaining flexibility to set additional headers for specific paths. There is no change in behavior for existing configurations. Example below: server { ... proxy_set_header Host $host inherit; proxy_set_header Connection "" inherit; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for inherit; location /1/ { #sets this header in addition to server context ones. proxy_set_header customHeader1 "customvalue1"; proxy_pass http://backend1; } location /2/ { #sets this header in addition to server context ones. proxy_set_header customheader2 "customValue2"; proxy_pass http://backend1; } location /3/ { #no location specific headers, only server context ones set. proxy_pass http://backend1; } location /special/ { #Blocks merge inheritance from server context. proxy_set_header_inherit off; proxy_set_header Host "notserverthost.co"; proxy_set_header Connection ""; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://backend2; } } TODO: - Repeat this implementation for grpc_set_header diff -r 7ec761f0365f -r cc79903ca02e src/http/modules/ngx_http_proxy_module.c --- a/src/http/modules/ngx_http_proxy_module.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/modules/ngx_http_proxy_module.c Thu Nov 23 14:50:16 2023 +0000 @@ -69,6 +69,11 @@ ngx_str_t uri; } ngx_http_proxy_vars_t; +typedef struct { + ngx_str_t key; + ngx_str_t value; + ngx_uint_t inherit; /* unsigned inherit:1 */ +} ngx_http_proxy_header_src_t; typedef struct { ngx_array_t *flushes; @@ -117,6 +122,8 @@ ngx_uint_t headers_hash_max_size; ngx_uint_t headers_hash_bucket_size; + ngx_flag_t headers_inherit; + #if (NGX_HTTP_SSL) ngx_uint_t ssl; ngx_uint_t ssl_protocols; @@ -215,6 +222,8 @@ void *conf); static char *ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_proxy_set_header(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); #if (NGX_HTTP_CACHE) static char *ngx_http_proxy_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -409,12 +418,19 @@ NULL }, { ngx_string("proxy_set_header"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, - ngx_conf_set_keyval_slot, + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23, + ngx_http_proxy_set_header, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, headers_source), NULL }, + { ngx_string("proxy_set_header_inherit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, headers_inherit), + NULL }, + { ngx_string("proxy_headers_hash_max_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -3400,6 +3416,8 @@ conf->upstream.intercept_errors = NGX_CONF_UNSET; + conf->headers_inherit = NGX_CONF_UNSET; + #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; @@ -3444,13 +3462,15 @@ ngx_http_proxy_loc_conf_t *prev = parent; ngx_http_proxy_loc_conf_t *conf = child; - u_char *p; - size_t size; - ngx_int_t rc; - ngx_hash_init_t hash; - ngx_http_core_loc_conf_t *clcf; - ngx_http_proxy_rewrite_t *pr; - ngx_http_script_compile_t sc; + u_char *p; + size_t size; + ngx_int_t rc; + ngx_uint_t i; + ngx_hash_init_t hash; + ngx_http_core_loc_conf_t *clcf; + ngx_http_proxy_rewrite_t *pr; + ngx_http_script_compile_t sc; + ngx_http_proxy_header_src_t *phs, *pphs; #if (NGX_HTTP_CACHE) @@ -3724,6 +3744,9 @@ ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); + ngx_conf_merge_value(conf->headers_inherit, + prev->headers_inherit, 1); + #if (NGX_HTTP_SSL) if (ngx_http_proxy_merge_ssl(cf, conf, prev) != NGX_OK) { @@ -3905,8 +3928,25 @@ #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif - } - + } else if (conf->headers_source + && prev->headers_source + && prev->headers_source != NGX_CONF_UNSET_PTR) + { + pphs = prev->headers_source->elts; + + for (i = 0; i < prev->headers_source->nelts; i++) { + if (!conf->headers_inherit || !pphs[i].inherit) { + continue; + } + + phs = ngx_array_push(conf->headers_source); + if (phs == NULL) { + return NGX_CONF_ERROR; + } + + *phs = pphs[i]; + } + } rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, ngx_http_proxy_headers); if (rc != NGX_OK) { @@ -3957,6 +3997,7 @@ ngx_hash_init_t hash; ngx_http_script_compile_t sc; ngx_http_script_copy_code_t *copy; + ngx_http_proxy_header_src_t *phs; if (headers->hash.buckets) { return NGX_OK; @@ -3986,7 +4027,7 @@ if (conf->headers_source) { - src = conf->headers_source->elts; + phs = conf->headers_source->elts; for (i = 0; i < conf->headers_source->nelts; i++) { s = ngx_array_push(&headers_merged); @@ -3994,7 +4035,8 @@ return NGX_ERROR; } - *s = src[i]; + s->key = phs[i].key; + s->value = phs[i].value; } } @@ -4769,6 +4811,52 @@ } +static char * +ngx_http_proxy_set_header(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_proxy_loc_conf_t *plcf = conf; + + ngx_str_t *value; + ngx_array_t **headers; + ngx_http_proxy_header_src_t *phs; + + value = cf->args->elts; + + headers = (ngx_array_t **) ((char *) plcf + cmd->offset); + + if (*headers == NGX_CONF_UNSET_PTR) { + *headers = ngx_array_create(cf->pool, 1, + sizeof(ngx_http_proxy_header_src_t)); + if (*headers == NULL) { + return NGX_CONF_ERROR; + } + } + + phs = ngx_array_push(*headers); + if (phs == NULL) { + return NGX_CONF_ERROR; + } + + phs->key = value[1]; + phs->value = value[2]; + phs->inherit = 0; + + if (cf->args->nelts == 3) { + return NGX_CONF_OK; + } + + if (ngx_strcmp(value[3].data, "inherit") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[3]); + return NGX_CONF_ERROR; + } + + phs->inherit = 1; + + return NGX_CONF_OK; +} + + #if (NGX_HTTP_CACHE) static char * From chipitsine at gmail.com Thu Nov 23 20:29:41 2023 From: chipitsine at gmail.com (=?iso-8859-1?q?Ilya_Shipitsin?=) Date: Thu, 23 Nov 2023 21:29:41 +0100 Subject: [PATCH] ssl: SSL_get0_verified_chain is available for LibreSSL >= 3.3.6 Message-ID: <2001e73ce136d5bfc9bd.1700771381@fedora> # HG changeset patch # User Ilya Shipitsin # Date 1700769135 -3600 # Thu Nov 23 20:52:15 2023 +0100 # Node ID 2001e73ce136d5bfc9bde27d338865b14b8ad436 # Parent 7ec761f0365f418511e30b82e9adf80bc56681df ssl: SSL_get0_verified_chain is available for LibreSSL >= 3.3.6 diff -r 7ec761f0365f -r 2001e73ce136 src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/event/ngx_event_openssl_stapling.c Thu Nov 23 20:52:15 2023 +0100 @@ -893,7 +893,8 @@ ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD; ocsp->conf = ocf; -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) +/* minimum OpenSSL 1.1.1 & LibreSSL 3.3.6 */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) || (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3030600L)) ocsp->certs = SSL_get0_verified_chain(c->ssl->connection); From pluknet at nginx.com Fri Nov 24 10:53:19 2023 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 24 Nov 2023 10:53:19 +0000 Subject: [nginx] Adjusted Huffman coding debug logging, missed in 7977:336084ff943b. Message-ID: details: https://hg.nginx.org/nginx/rev/c458cd00bb0b branches: changeset: 9185:c458cd00bb0b user: Sergey Kandaurov date: Tue Nov 14 14:50:03 2023 +0400 description: Adjusted Huffman coding debug logging, missed in 7977:336084ff943b. Spotted by XingY Wang. diffstat: src/http/ngx_http_huff_decode.c | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diffs (30 lines): diff -r 7ec761f0365f -r c458cd00bb0b src/http/ngx_http_huff_decode.c --- a/src/http/ngx_http_huff_decode.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/ngx_http_huff_decode.c Tue Nov 14 14:50:03 2023 +0400 @@ -2657,7 +2657,7 @@ ngx_http_huff_decode(u_char *state, u_ch != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch >> 4); return NGX_ERROR; @@ -2667,7 +2667,7 @@ ngx_http_huff_decode(u_char *state, u_ch != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch & 0xf); return NGX_ERROR; @@ -2677,7 +2677,7 @@ ngx_http_huff_decode(u_char *state, u_ch if (last) { if (!ending) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error: " + "http huffman decoding error: " "incomplete code 0x%Xd", ch); return NGX_ERROR; From pluknet at nginx.com Fri Nov 24 10:53:22 2023 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 24 Nov 2023 10:53:22 +0000 Subject: [nginx] HTTP/3: added Huffman decoding error logging. Message-ID: details: https://hg.nginx.org/nginx/rev/f366007dd23a branches: changeset: 9186:f366007dd23a user: Sergey Kandaurov date: Tue Nov 14 15:26:02 2023 +0400 description: HTTP/3: added Huffman decoding error logging. diffstat: src/http/v3/ngx_http_v3_parse.c | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diffs (12 lines): diff -r c458cd00bb0b -r f366007dd23a src/http/v3/ngx_http_v3_parse.c --- a/src/http/v3/ngx_http_v3_parse.c Tue Nov 14 14:50:03 2023 +0400 +++ b/src/http/v3/ngx_http_v3_parse.c Tue Nov 14 15:26:02 2023 +0400 @@ -650,6 +650,8 @@ ngx_http_v3_parse_literal(ngx_connection st->length == 1, c->log) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid encoded field line"); return NGX_ERROR; } From jordanc.carter at outlook.com Sat Nov 25 23:37:50 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sat, 25 Nov 2023 23:37:50 +0000 Subject: [PATCH] Proxy: altered limit_rate to support variables Message-ID: # HG changeset patch # User J Carter # Date 1700949429 0 # Sat Nov 25 21:57:09 2023 +0000 # Node ID 98306e705015758eab0a05103d90e6bdb1da2819 # Parent f366007dd23a6ce8e8427c1b3042781b618a2ade Proxy: altered limit_rate to support variables diff -r f366007dd23a -r 98306e705015 src/http/modules/ngx_http_fastcgi_module.c --- a/src/http/modules/ngx_http_fastcgi_module.c Tue Nov 14 15:26:02 2023 +0400 +++ b/src/http/modules/ngx_http_fastcgi_module.c Sat Nov 25 21:57:09 2023 +0000 @@ -375,7 +375,7 @@ { ngx_string("fastcgi_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, + ngx_http_set_complex_value_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, upstream.limit_rate), NULL }, @@ -2898,7 +2898,7 @@ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; - conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; + conf->upstream.limit_rate = NGX_CONF_UNSET_PTR; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; @@ -3015,8 +3015,8 @@ prev->upstream.buffer_size, (size_t) ngx_pagesize); - ngx_conf_merge_size_value(conf->upstream.limit_rate, - prev->upstream.limit_rate, 0); + ngx_conf_merge_ptr_value(conf->upstream.limit_rate, + prev->upstream.limit_rate, NULL); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, diff -r f366007dd23a -r 98306e705015 src/http/modules/ngx_http_proxy_module.c --- a/src/http/modules/ngx_http_proxy_module.c Tue Nov 14 15:26:02 2023 +0400 +++ b/src/http/modules/ngx_http_proxy_module.c Sat Nov 25 21:57:09 2023 +0000 @@ -494,7 +494,7 @@ { ngx_string("proxy_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, + ngx_http_set_complex_value_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.limit_rate), NULL }, @@ -3371,7 +3371,7 @@ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; - conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; + conf->upstream.limit_rate = NGX_CONF_UNSET_PTR; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; @@ -3515,8 +3515,8 @@ prev->upstream.buffer_size, (size_t) ngx_pagesize); - ngx_conf_merge_size_value(conf->upstream.limit_rate, - prev->upstream.limit_rate, 0); + ngx_conf_merge_ptr_value(conf->upstream.limit_rate, + prev->upstream.limit_rate, NULL); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, 8, ngx_pagesize); diff -r f366007dd23a -r 98306e705015 src/http/modules/ngx_http_scgi_module.c --- a/src/http/modules/ngx_http_scgi_module.c Tue Nov 14 15:26:02 2023 +0400 +++ b/src/http/modules/ngx_http_scgi_module.c Sat Nov 25 21:57:09 2023 +0000 @@ -223,7 +223,7 @@ { ngx_string("scgi_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, + ngx_http_set_complex_value_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_scgi_loc_conf_t, upstream.limit_rate), NULL }, @@ -1301,7 +1301,7 @@ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; - conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; + conf->upstream.limit_rate = NGX_CONF_UNSET_PTR; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; @@ -1413,8 +1413,8 @@ prev->upstream.buffer_size, (size_t) ngx_pagesize); - ngx_conf_merge_size_value(conf->upstream.limit_rate, - prev->upstream.limit_rate, 0); + ngx_conf_merge_ptr_value(conf->upstream.limit_rate, + prev->upstream.limit_rate, NULL); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, diff -r f366007dd23a -r 98306e705015 src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c Tue Nov 14 15:26:02 2023 +0400 +++ b/src/http/modules/ngx_http_uwsgi_module.c Sat Nov 25 21:57:09 2023 +0000 @@ -289,7 +289,7 @@ { ngx_string("uwsgi_limit_rate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, + ngx_http_set_complex_value_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_uwsgi_loc_conf_t, upstream.limit_rate), NULL }, @@ -1532,7 +1532,7 @@ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; - conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE; + conf->upstream.limit_rate = NGX_CONF_UNSET_PTR; conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE; conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE; @@ -1656,8 +1656,8 @@ prev->upstream.buffer_size, (size_t) ngx_pagesize); - ngx_conf_merge_size_value(conf->upstream.limit_rate, - prev->upstream.limit_rate, 0); + ngx_conf_merge_ptr_value(conf->upstream.limit_rate, + prev->upstream.limit_rate, NULL); ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs, diff -r f366007dd23a -r 98306e705015 src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c Tue Nov 14 15:26:02 2023 +0400 +++ b/src/http/ngx_http_upstream.c Sat Nov 25 21:57:09 2023 +0000 @@ -3236,7 +3236,7 @@ p->downstream = c; p->pool = r->pool; p->log = c->log; - p->limit_rate = u->conf->limit_rate; + p->limit_rate = ngx_http_complex_value_size(r, u->conf->limit_rate, 0); p->start_sec = ngx_time(); p->cacheable = u->cacheable || u->store; diff -r f366007dd23a -r 98306e705015 src/http/ngx_http_upstream.h --- a/src/http/ngx_http_upstream.h Tue Nov 14 15:26:02 2023 +0400 +++ b/src/http/ngx_http_upstream.h Sat Nov 25 21:57:09 2023 +0000 @@ -156,7 +156,7 @@ size_t send_lowat; size_t buffer_size; - size_t limit_rate; + ngx_http_complex_value_t *limit_rate; size_t busy_buffers_size; size_t max_temp_file_size; From mdounin at mdounin.ru Mon Nov 27 02:50:23 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 27 Nov 2023 05:50:23 +0300 Subject: [PATCH 0 of 4] aio fixes Message-ID: Hello! Here is a patch series which fixes several issues with AIO (threaded IO) and some related issues, initially inspired by ticket #2555. Most notably, it fixes various issues with incorrect request termination with subrequests when an AIO operation is running in another subrequest, as observed in ticket #2555 (first patch). Additionally, it fixes an issue with request termination due to filter finalization with ngx_event_pipe() on stack (second patch), fixes incorrect alerts about open sockets left when SIGQUIT is followed by SIGTERM (third patch), and introduces guard timers for AIO operations to prevent premature shutdown of worker processes (forth patch, ticket #2162). In tests this fixes two alerts as observed with aio_write, in image_filter_finalize.t and in proxy_cache_use_stale.t: image_filter_finalize.t (Wstat: 0 Tests: 5 Failed: 0) TODO passed: 4 proxy_cache_use_stale.t (Wstat: 0 Tests: 36 Failed: 0) TODO passed: 35 Review and testing appreciated. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon Nov 27 02:50:26 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 27 Nov 2023 05:50:26 +0300 Subject: [PATCH 3 of 4] Silenced complaints about socket leaks on forced termination In-Reply-To: References: Message-ID: <61d08e4cf97cc073200e.1701053426@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1701049787 -10800 # Mon Nov 27 04:49:47 2023 +0300 # Node ID 61d08e4cf97cc073200ec32fc6ada9a2d48ffe51 # Parent faf0b9defc76b8683af466f8a950c2c241382970 Silenced complaints about socket leaks on forced termination. When graceful shutdown was requested, and then nginx was forced to do fast shutdown, it used to (incorrectly) complain about open sockets left in connections which weren't yet closed when fast shutdown was requested. Fix is to avoid complaining about open sockets when fast shutdown was requested after graceful one. Abnormal termination, if requested with the WINCH signal, can still happen though. diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c --- a/src/os/unix/ngx_process_cycle.c +++ b/src/os/unix/ngx_process_cycle.c @@ -948,7 +948,7 @@ ngx_worker_process_exit(ngx_cycle_t *cyc } } - if (ngx_exiting) { + if (ngx_exiting && !ngx_terminate) { c = cycle->connections; for (i = 0; i < cycle->connection_n; i++) { if (c[i].fd != -1 @@ -963,11 +963,11 @@ ngx_worker_process_exit(ngx_cycle_t *cyc ngx_debug_quit = 1; } } + } - if (ngx_debug_quit) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting"); - ngx_debug_point(); - } + if (ngx_debug_quit) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting"); + ngx_debug_point(); } /* diff --git a/src/os/win32/ngx_process_cycle.c b/src/os/win32/ngx_process_cycle.c --- a/src/os/win32/ngx_process_cycle.c +++ b/src/os/win32/ngx_process_cycle.c @@ -834,7 +834,7 @@ ngx_worker_process_exit(ngx_cycle_t *cyc } } - if (ngx_exiting) { + if (ngx_exiting && !ngx_terminate) { c = cycle->connections; for (i = 0; i < cycle->connection_n; i++) { if (c[i].fd != (ngx_socket_t) -1 From mdounin at mdounin.ru Mon Nov 27 02:50:25 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 27 Nov 2023 05:50:25 +0300 Subject: [PATCH 2 of 4] Upstream: fixed usage of closed sockets with filter finalization In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1701049758 -10800 # Mon Nov 27 04:49:18 2023 +0300 # Node ID faf0b9defc76b8683af466f8a950c2c241382970 # Parent a5e39e9d1f4c84dcbe6a2f9e079372a3d63aef0b Upstream: fixed usage of closed sockets with filter finalization. When filter finalization is triggered when working with an upstream server, and error_page redirects request processing to some simple handler, ngx_http_request_finalize() triggers request termination when the response is sent. In particular, via the upstream cleanup handler, nginx will close the upstream connection and the corresponding socket. Still, this can happen to be with ngx_event_pipe() on stack. While the code will set p->downstream_error due to NGX_ERROR returned from the output filter chain by filter finalization, otherwise the error will be ignored till control returns to ngx_http_upstream_process_request(). And event pipe might try reading from the (already closed) socket, resulting in "readv() failed (9: Bad file descriptor) while reading upstream" errors (or even segfaults with SSL). Such errors were seen with the following configuration: location /t2 { proxy_pass http://127.0.0.1:8080/big; image_filter_buffer 10m; image_filter resize 150 100; error_page 415 = /empty; } location /empty { return 204; } location /big { # big enough static file } Fix is to set p->upstream_error in ngx_http_upstream_finalize_request(), so the existing checks in ngx_event_pipe_read_upstream() will prevent further reading from the closed upstream connection. Similarly, p->upstream_error is now checked when handling events at ngx_event_pipe() exit, as checking p->upstream->fd is not enough if keepalive upstream connections are being used and the connection was saved to cache during request termination. diff --git a/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c --- a/src/event/ngx_event_pipe.c +++ b/src/event/ngx_event_pipe.c @@ -57,7 +57,9 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_ do_write = 1; } - if (p->upstream->fd != (ngx_socket_t) -1) { + if (p->upstream->fd != (ngx_socket_t) -1 + && !p->upstream_error) + { rev = p->upstream->read; flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -4561,6 +4561,10 @@ ngx_http_upstream_finalize_request(ngx_h u->peer.connection = NULL; + if (u->pipe) { + u->pipe->upstream_error = 1; + } + if (u->pipe && u->pipe->temp_file) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream temp fd: %d", From mdounin at mdounin.ru Mon Nov 27 02:50:27 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 27 Nov 2023 05:50:27 +0300 Subject: [PATCH 4 of 4] AIO operations now add timers (ticket #2162) In-Reply-To: References: Message-ID: <00c3e7333145ddb5ea0e.1701053427@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1701050170 -10800 # Mon Nov 27 04:56:10 2023 +0300 # Node ID 00c3e7333145ddb5ea0eeaaa66b3d9c26973c9c2 # Parent 61d08e4cf97cc073200ec32fc6ada9a2d48ffe51 AIO operations now add timers (ticket #2162). Each AIO (thread IO) operation being run is now accompanied with 1-minute timer. This timer prevents unexpected shutdown of the worker process while an AIO operation is running, and logs an alert if the operation is running for too long. This fixes "open socket left" alerts during worker processes shutdown due to pending AIO (or thread IO) operations while corresponding requests have no timers. In particular, such errors were observed while reading cache headers (ticket #2162), and with worker_shutdown_timeout. diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c --- a/src/http/ngx_http_copy_filter_module.c +++ b/src/http/ngx_http_copy_filter_module.c @@ -170,6 +170,8 @@ ngx_http_copy_aio_handler(ngx_output_cha file->aio->data = r; file->aio->handler = ngx_http_copy_aio_event_handler; + ngx_add_timer(&file->aio->event, 60000); + r->main->blocked++; r->aio = 1; ctx->aio = 1; @@ -192,6 +194,17 @@ ngx_http_copy_aio_event_handler(ngx_even ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http aio: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "aio operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; @@ -273,6 +286,8 @@ ngx_http_copy_thread_handler(ngx_thread_ return NGX_ERROR; } + ngx_add_timer(&task->event, 60000); + r->main->blocked++; r->aio = 1; @@ -297,6 +312,17 @@ ngx_http_copy_thread_event_handler(ngx_e ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -705,6 +705,8 @@ ngx_http_file_cache_aio_read(ngx_http_re c->file.aio->data = r; c->file.aio->handler = ngx_http_cache_aio_event_handler; + ngx_add_timer(&c->file.aio->event, 60000); + r->main->blocked++; r->aio = 1; @@ -752,6 +754,17 @@ ngx_http_cache_aio_event_handler(ngx_eve ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache aio: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "aio operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; @@ -810,6 +823,8 @@ ngx_http_cache_thread_handler(ngx_thread return NGX_ERROR; } + ngx_add_timer(&task->event, 60000); + r->main->blocked++; r->aio = 1; @@ -831,6 +846,17 @@ ngx_http_cache_thread_event_handler(ngx_ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -3949,6 +3949,8 @@ ngx_http_upstream_thread_handler(ngx_thr r->aio = 1; p->aio = 1; + ngx_add_timer(&task->event, 60000); + return NGX_OK; } @@ -3967,6 +3969,17 @@ ngx_http_upstream_thread_event_handler(n ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; diff --git a/src/os/unix/ngx_files.c b/src/os/unix/ngx_files.c --- a/src/os/unix/ngx_files.c +++ b/src/os/unix/ngx_files.c @@ -110,6 +110,8 @@ ngx_thread_read(ngx_file_t *file, u_char return NGX_ERROR; } + task->event.log = file->log; + file->thread_task = task; } @@ -493,6 +495,8 @@ ngx_thread_write_chain_to_file(ngx_file_ return NGX_ERROR; } + task->event.log = file->log; + file->thread_task = task; } diff --git a/src/os/unix/ngx_linux_sendfile_chain.c b/src/os/unix/ngx_linux_sendfile_chain.c --- a/src/os/unix/ngx_linux_sendfile_chain.c +++ b/src/os/unix/ngx_linux_sendfile_chain.c @@ -332,6 +332,7 @@ ngx_linux_sendfile_thread(ngx_connection return NGX_ERROR; } + task->event.log = c->log; task->handler = ngx_linux_sendfile_thread_handler; c->sendfile_task = task; From mdounin at mdounin.ru Mon Nov 27 02:50:24 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 27 Nov 2023 05:50:24 +0300 Subject: [PATCH 1 of 4] Fixed request termination with AIO and subrequests (ticket #2555) In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1701049682 -10800 # Mon Nov 27 04:48:02 2023 +0300 # Node ID a5e39e9d1f4c84dcbe6a2f9e079372a3d63aef0b # Parent f366007dd23a6ce8e8427c1b3042781b618a2ade Fixed request termination with AIO and subrequests (ticket #2555). When a request was terminated due to an error via ngx_http_terminate_request() while an AIO operation was running in a subrequest, various issues were observed. This happened because ngx_http_request_finalizer() was only set in the subrequest where ngx_http_terminate_request() was called, but not in the subrequest where the AIO operation was running. After completion of the AIO operation resumed normal processing of the subrequest, leading to issues. In particular, in case of the upstream module, termination of the request called upstream cleanup, which closed the upstream connection. Attempts to further work with the upstream connection after AIO operation completion resulted in segfaults in ngx_ssl_recv(), "readv() failed (9: Bad file descriptor) while reading upstream" errors, or socket leaks. In ticket #2555, issues were observed with the following configuration with cache background update (with thread writing instrumented to introduce a delay, when a client closes the connection during an update): location = /background-and-aio-write { proxy_pass ... proxy_cache one; proxy_cache_valid 200 1s; proxy_cache_background_update on; proxy_cache_use_stale updating; aio threads; aio_write on; limit_rate 1000; } Similarly, the same issue can be seen with SSI, and can be caused by errors in subrequests, such as in the following configuration (were "/proxy" uses AIO, and "/sleep" returns 444 after some delay, causing request termination): location = /ssi-active-boom { ssi on; ssi_types *; return 200 ' '; limit_rate 1000; } Or the same with both AIO operation and the error in non-active subrequests (which needs slightly different handling, see below): location = /ssi-non-active-boom { ssi on; ssi_types *; return 200 ' '; limit_rate 1000; } Similarly, issues can be observed with just static files. However, with static files potential impact is limited due to timeout safeguards in ngx_http_writer(), and the fact that c->error is set during request termination. In a simple configuration with an AIO operation in the active subrequest, such as in the following configuration, the connection is closed right after completion of the AIO operation anyway, since ngx_http_writer() tries to write to the connection and fails due to c->error set: location = /ssi-active-static-boom { ssi on; ssi_types *; return 200 ' '; limit_rate 1000; } In the following configuration, with an AIO operation in a non-active subrequest, the connection is closed only after send_timeout expires: location = /ssi-non-active-static-boom { ssi on; ssi_types *; return 200 ' '; limit_rate 1000; } Fix is to introduce r->main->terminated flag, which is to be checked by AIO event handlers when the r->main->blocked counter is decremented. When the flag is set, handlers are expected to wake up the connection instead of the subrequest (which might be already cleaned up). Additionally, now ngx_http_request_finalizer() is always set in the active subrequest, so waking up the connection properly finalizes the request even if termination happened in a non-active subrequest. diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c --- a/src/http/ngx_http_copy_filter_module.c +++ b/src/http/ngx_http_copy_filter_module.c @@ -195,9 +195,18 @@ ngx_http_copy_aio_event_handler(ngx_even r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ - ngx_http_run_posted_requests(c); + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -305,11 +314,11 @@ ngx_http_copy_thread_event_handler(ngx_e #endif - if (r->done) { + if (r->done || r->main->terminated) { /* * trigger connection event handler if the subrequest was - * already finalized; this can happen if the handler is used - * for sendfile() in threads + * already finalized (this can happen if the handler is used + * for sendfile() in threads), or if the request was terminated */ c->write->handler(c->write); diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -14,7 +14,7 @@ static ngx_int_t ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c); static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev); -static void ngx_http_file_cache_lock_wait(ngx_http_request_t *r, +static ngx_int_t ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c); static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c); @@ -463,6 +463,7 @@ ngx_http_file_cache_lock(ngx_http_reques static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev) { + ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; @@ -474,13 +475,31 @@ ngx_http_file_cache_lock_wait_handler(ng ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache wait: \"%V?%V\"", &r->uri, &r->args); - ngx_http_file_cache_lock_wait(r, r->cache); - - ngx_http_run_posted_requests(c); + rc = ngx_http_file_cache_lock_wait(r, r->cache); + + if (rc == NGX_AGAIN) { + return; + } + + r->cache->waiting = 0; + r->main->blocked--; + + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } -static void +static ngx_int_t ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) { ngx_uint_t wait; @@ -495,7 +514,7 @@ ngx_http_file_cache_lock_wait(ngx_http_r ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "cache lock timeout"); c->lock_timeout = 0; - goto wakeup; + return NGX_OK; } cache = c->file_cache; @@ -513,14 +532,10 @@ ngx_http_file_cache_lock_wait(ngx_http_r if (wait) { ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer); - return; + return NGX_AGAIN; } -wakeup: - - c->waiting = 0; - r->main->blocked--; - r->write_event_handler(r); + return NGX_OK; } @@ -740,9 +755,18 @@ ngx_http_cache_aio_event_handler(ngx_eve r->main->blocked--; r->aio = 0; - r->write_event_handler(r); - - ngx_http_run_posted_requests(c); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -810,9 +834,18 @@ ngx_http_cache_thread_event_handler(ngx_ r->main->blocked--; r->aio = 0; - r->write_event_handler(r); - - ngx_http_run_posted_requests(c); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2681,6 +2681,8 @@ ngx_http_terminate_request(ngx_http_requ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http terminate request count:%d", mr->count); + mr->terminated = 1; + if (rc > 0 && (mr->headers_out.status == 0 || mr->connection->sent == 0)) { mr->headers_out.status = rc; } @@ -2703,8 +2705,13 @@ ngx_http_terminate_request(ngx_http_requ if (mr->write_event_handler) { if (mr->blocked) { + if (r != r->connection->data) { + r = r->connection->data; + } + r->connection->error = 1; r->write_event_handler = ngx_http_request_finalizer; + return; } diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -550,6 +550,7 @@ struct ngx_http_request_s { unsigned root_tested:1; unsigned done:1; unsigned logged:1; + unsigned terminated:1; unsigned buffered:4; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -3984,11 +3984,11 @@ ngx_http_upstream_thread_event_handler(n #endif - if (r->done) { + if (r->done || r->main->terminated) { /* * trigger connection event handler if the subrequest was - * already finalized; this can happen if the handler is used - * for sendfile() in threads + * already finalized (this can happen if the handler is used + * for sendfile() in threads), or if the request was terminated */ c->write->handler(c->write); From arut at nginx.com Mon Nov 27 12:18:21 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 27 Nov 2023 16:18:21 +0400 Subject: [PATCH] Added merge inheritance to proxy_set_header In-Reply-To: References: Message-ID: <20231127121821.f3pags4kp2gupw5e@N00W24XTQX> Hi Jordan, On Thu, Nov 23, 2023 at 02:52:55PM +0000, J Carter wrote: > # HG changeset patch > # User J Carter > # Date 1700751016 0 > # Thu Nov 23 14:50:16 2023 +0000 > # Node ID cc79903ca02eff8aa87238a0f801f8070425d9ab > # Parent 7ec761f0365f418511e30b82e9adf80bc56681df > Added merge inheritance to proxy_set_header > > This patch introduces 'inherit' argument to proxy_set_header > directive. When marked as such it will be merge inherited into child > contexts regardless of the presence of other proxy_set_header > directives within those contexts. > > This patch also introduces the 'proxy_set_header_inherit' directive > which blocks the merge inheritance in receiving contexts when set to off. > > The purpose of the added mechanics is to reduce repetition within the > nginx configuration for universally set (or boilerplate) request > headers, while maintaining flexibility to set additional headers for > specific paths. > > There is no change in behavior for existing configurations. > > Example below: > > server { > > ... > > proxy_set_header Host $host inherit; > proxy_set_header Connection "" inherit; > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for inherit; > > location /1/ { > #sets this header in addition to server context ones. > proxy_set_header customHeader1 "customvalue1"; > proxy_pass http://backend1; > } > > location /2/ { > #sets this header in addition to server context ones. > proxy_set_header customheader2 "customValue2"; > proxy_pass http://backend1; > } > > location /3/ { > #no location specific headers, only server context ones set. > proxy_pass http://backend1; > } > > location /special/ { > #Blocks merge inheritance from server context. > proxy_set_header_inherit off; > proxy_set_header Host "notserverthost.co"; > proxy_set_header Connection ""; > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; > proxy_pass http://backend2; > } > > } Even though the inheritance rules we have now are not convenient for some people, they do have one benefit - they are simple. Looking into a single location is enough to have a full list of headers. You introduce inheritable headers which break this simplicity. I also though about this issue and I think it'd be better to add a separate directive/parameter which would explicitly inherit all headers from the outer scope. server { proxy_set_header Host "notserverthost.co"; location / { ... proxy_set_header inherit; # explicitly inherit from above proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } [..] -- Roman Arutyunyan From jordanc.carter at outlook.com Mon Nov 27 18:02:26 2023 From: jordanc.carter at outlook.com (J Carter) Date: Mon, 27 Nov 2023 18:02:26 +0000 Subject: [PATCH] Added merge inheritance to proxy_set_header In-Reply-To: <20231127121821.f3pags4kp2gupw5e@N00W24XTQX> References: <20231127121821.f3pags4kp2gupw5e@N00W24XTQX> Message-ID: Hello Roman, Thank you for the review and feedback. On Mon, 27 Nov 2023 16:18:21 +0400 Roman Arutyunyan wrote: > Hi Jordan, > > On Thu, Nov 23, 2023 at 02:52:55PM +0000, J Carter wrote: > > # HG changeset patch > > # User J Carter > > # Date 1700751016 0 > > # Thu Nov 23 14:50:16 2023 +0000 > > # Node ID cc79903ca02eff8aa87238a0f801f8070425d9ab > > # Parent 7ec761f0365f418511e30b82e9adf80bc56681df > > Added merge inheritance to proxy_set_header > > > > This patch introduces 'inherit' argument to proxy_set_header > > directive. When marked as such it will be merge inherited into child > > contexts regardless of the presence of other proxy_set_header > > directives within those contexts. > > > > This patch also introduces the 'proxy_set_header_inherit' directive > > which blocks the merge inheritance in receiving contexts when set to off. > > > > The purpose of the added mechanics is to reduce repetition within the > > nginx configuration for universally set (or boilerplate) request > > headers, while maintaining flexibility to set additional headers for > > specific paths. > > > > There is no change in behavior for existing configurations. > > > > Example below: > > > > server { > > > > ... > > > > proxy_set_header Host $host inherit; > > proxy_set_header Connection "" inherit; > > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for inherit; > > > > location /1/ { > > #sets this header in addition to server context ones. > > proxy_set_header customHeader1 "customvalue1"; > > proxy_pass http://backend1; > > } > > > > location /2/ { > > #sets this header in addition to server context ones. > > proxy_set_header customheader2 "customValue2"; > > proxy_pass http://backend1; > > } > > > > location /3/ { > > #no location specific headers, only server context ones set. > > proxy_pass http://backend1; > > } > > > > location /special/ { > > #Blocks merge inheritance from server context. > > proxy_set_header_inherit off; > > proxy_set_header Host "notserverthost.co"; > > proxy_set_header Connection ""; > > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; > > proxy_pass http://backend2; > > } > > > > } > > Even though the inheritance rules we have now are not convenient for some > people, they do have one benefit - they are simple. Looking into a single > location is enough to have a full list of headers. You introduce inheritable > headers which break this simplicity. > > I also though about this issue and I think it'd be better to add a separate > directive/parameter which would explicitly inherit all headers from the outer > scope. I agree, your suggested approach significantly reduces code and configuration complexity, and still has good flexibility. > > server { > proxy_set_header Host "notserverthost.co"; > > location / { > ... > proxy_set_header inherit; # explicitly inherit from above > > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; > } > } > > [..] > I've kept it as a separate directive for a few reasons: - I think it makes the config syntax a little less ambiguous (ie. harder to misread as setting a header called 'inherit' to empty value). - It avoids a custom handler for proxy_set_header directive parsing. - It allows for this directive itself to be inherited (in normal fashion) for further reduction in configuration, while still allowing for disabling in lower contexts. I know this last point is against your explicitness point, however the user would be free to choose from both approaches. Personally I prefer the one with less config lines. Please find the revised patch below: # HG changeset patch # User J Carter # Date 1701107326 0 # Mon Nov 27 17:48:46 2023 +0000 # Node ID 33d34dd487501932f141cb6c2124959580ce38e5 # Parent 7ec761f0365f418511e30b82e9adf80bc56681df Proxy: proxy_set_header_inherit directive This patch introduces proxy_set_header_inherit which enables merge inheritance of proxy_set_header directives from higher context when set to 'on' in the receiving context. The purpose of the added mechanics is to reduce repetition within the nginx configuration for universally set (or boilerplate) request headers, while maintaining flexibility to set additional headers for specific paths. There is no change in behavior for existing configurations. Example below: server { ... proxy_set_header Host $host; proxy_set_header Connection ""; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header_inherit on; location /1/ { # proxy_set_header_inherit is on here, therefore # do merge inheritance with server headers. proxy_set_header customHeader1 "customvalue1"; proxy_pass http://backend1; } ... location /special/ { # Blocks merge inheritance from server context. # Standard nginx inheritence rules apply. proxy_set_header_inherit off; proxy_set_header Host "notserverhost.co"; proxy_set_header Connection ""; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://backend2; } } TODO: - repeat this for grpc module. diff -r 7ec761f0365f -r 33d34dd48750 src/http/modules/ngx_http_proxy_module.c --- a/src/http/modules/ngx_http_proxy_module.c Thu Oct 26 23:35:09 2023 +0300 +++ b/src/http/modules/ngx_http_proxy_module.c Mon Nov 27 17:48:46 2023 +0000 @@ -69,7 +69,6 @@ ngx_str_t uri; } ngx_http_proxy_vars_t; - typedef struct { ngx_array_t *flushes; ngx_array_t *lengths; @@ -117,6 +116,8 @@ ngx_uint_t headers_hash_max_size; ngx_uint_t headers_hash_bucket_size; + ngx_flag_t headers_inherit; + #if (NGX_HTTP_SSL) ngx_uint_t ssl; ngx_uint_t ssl_protocols; @@ -415,6 +416,13 @@ offsetof(ngx_http_proxy_loc_conf_t, headers_source), NULL }, + { ngx_string("proxy_set_header_inherit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, headers_inherit), + NULL }, + { ngx_string("proxy_headers_hash_max_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -3400,6 +3408,8 @@ conf->upstream.intercept_errors = NGX_CONF_UNSET; + conf->headers_inherit = NGX_CONF_UNSET; + #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; @@ -3447,6 +3457,8 @@ u_char *p; size_t size; ngx_int_t rc; + ngx_uint_t i; + ngx_keyval_t *src, *h; ngx_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; ngx_http_proxy_rewrite_t *pr; @@ -3724,6 +3736,9 @@ ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); + ngx_conf_merge_value(conf->headers_inherit, + prev->headers_inherit, 0); + #if (NGX_HTTP_SSL) if (ngx_http_proxy_merge_ssl(cf, conf, prev) != NGX_OK) { @@ -3905,6 +3920,24 @@ #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif + } else if (conf->headers_source + && prev->headers_source + && prev->headers_source != NGX_CONF_UNSET_PTR) + { + src = prev->headers_source->elts; + + for (i = 0; i < prev->headers_source->nelts; i++) { + if (!conf->headers_inherit) { + continue; + } + + h = ngx_array_push(conf->headers_source); + if (h == NULL) { + return NGX_CONF_ERROR; + } + + *h = src[i]; + } } rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, From mdounin at mdounin.ru Tue Nov 28 02:57:39 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 28 Nov 2023 05:57:39 +0300 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start In-Reply-To: <6f957e137407d8f3f7e3.1699607515@vlws> References: <6f957e137407d8f3f7e3.1699607515@vlws> Message-ID: Hello! On Fri, Nov 10, 2023 at 12:11:55PM +0300, Vladimir Homutov via nginx-devel wrote: > It is no longer used since the refactoring in 8e5bf1bc87e2 (2008). > > > src/http/ngx_http_request.c | 3 +-- > src/http/ngx_http_request.h | 1 - > 2 files changed, 1 insertions(+), 3 deletions(-) > > > # HG changeset patch > # User Vladimir Khomutov > # Date 1699603821 -10800 > # Fri Nov 10 11:10:21 2023 +0300 > # Node ID 6f957e137407d8f3f7e34f413c92103004b44594 > # Parent 505e927eb7a75f0fdce4caddb4ab9d9c71c9b9c8 > HTTP: removed unused r->port_start. > > It is no longer used since the refactoring in 8e5bf1bc87e2 (2008). Neither r->port_start nor r->port_end were ever used. The r->port_end is set by the parser, though it was never used by the following code (and was never usable, since not copied by the ngx_http_alloc_large_header_buffer() without r->port_start set). The 8e5bf1bc87e2 commit is completely unrelated, it is about refactoring of the ngx_parse_inet_url() function, which had a local variable named "port_start". > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > --- a/src/http/ngx_http_request.c > +++ b/src/http/ngx_http_request.c > @@ -1744,8 +1744,7 @@ ngx_http_alloc_large_header_buffer(ngx_h > } > } > > - if (r->port_start) { > - r->port_start = new + (r->port_start - old); > + if (r->port_end) { > r->port_end = new + (r->port_end - old); > } > > diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h > --- a/src/http/ngx_http_request.h > +++ b/src/http/ngx_http_request.h > @@ -597,7 +597,6 @@ struct ngx_http_request_s { > u_char *schema_end; > u_char *host_start; > u_char *host_end; > - u_char *port_start; > u_char *port_end; > > unsigned http_minor:16; I don't think it's a good change. Rather, we should either remove both, or (eventually) fix these and provide some valid usage of the port as parsed either from the request line or from the Host header, similarly to the $host variable. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue Nov 28 02:58:23 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 28 Nov 2023 05:58:23 +0300 Subject: [PATCH 1 of 2] HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer In-Reply-To: <505e927eb7a75f0fdce4.1699607514@vlws> References: <505e927eb7a75f0fdce4.1699607514@vlws> Message-ID: Hello! On Fri, Nov 10, 2023 at 12:11:54PM +0300, Vladimir Homutov via nginx-devel wrote: > If URI is not fully parsed yet, some pointers are not set. > As a result, the calculation of "new + (ptr - old)" expression > may overflow. In such a case, just avoid calculating it, as value > will be set correctly later by the parser in any case. > > The issue was found by GCC undefined behaviour sanitizer. > > > src/http/ngx_http_request.c | 34 ++++++++++++++++++++++++++-------- > 1 files changed, 26 insertions(+), 8 deletions(-) > > > # HG changeset patch > # User Vladimir Khomutov > # Date 1699604478 -10800 > # Fri Nov 10 11:21:18 2023 +0300 > # Node ID 505e927eb7a75f0fdce4caddb4ab9d9c71c9b9c8 > # Parent dadd13fdcf5228c8e8380e120d4621002e3b0919 > HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer. > > If URI is not fully parsed yet, some pointers are not set. > As a result, the calculation of "new + (ptr - old)" expression > may overflow. In such a case, just avoid calculating it, as value > will be set correctly later by the parser in any case. > > The issue was found by GCC undefined behaviour sanitizer. I would rather refrain from saying this is an issue, it is not (unless a compiler actually starts to do silly things as long as it sees something formally defined as "undefined behavior" in C standard, and this would be indeed an issue - in the compiler). As already noted in the initial review, the code as written is completely safe in practice. For such mostly style commits we usually write something like "Prodded by...". Also note that the issue is not an overflow, but rather subtraction of pointers which do not belong to the same array object (C11, 6.5.6 Additive operators, p.9): : When two pointers are subtracted, both shall point to elements : of the same array object, or one past the last element of the : array object The undefined behaviour is present as long as "ptr" and "old" are not in the same buffer (i.e., array object), which is the case when "ptr" is not set. And another one follows when trying to add the (already undefined) subtraction result to "new" (since the result is not going to belong to the same array object): : If both the pointer operand and the result point to elements of : the same array object, or one past the last element of the array : object, the evaluation shall not produce an overflow; otherwise, : the behavior is undefined. Overflow here might be an indicator that certainly there is an undefined behaviour, but it's just an indicator. You may want to rewrite commit log accordingly. > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > --- a/src/http/ngx_http_request.c > +++ b/src/http/ngx_http_request.c > @@ -1718,14 +1718,23 @@ ngx_http_alloc_large_header_buffer(ngx_h > r->request_end = new + (r->request_end - old); > } > > - r->method_end = new + (r->method_end - old); > - > - r->uri_start = new + (r->uri_start - old); > - r->uri_end = new + (r->uri_end - old); > + if (r->method_end) { > + r->method_end = new + (r->method_end - old); > + } > + > + if (r->uri_start) { > + r->uri_start = new + (r->uri_start - old); > + } > + > + if (r->uri_end) { > + r->uri_end = new + (r->uri_end - old); > + } > > if (r->schema_start) { > r->schema_start = new + (r->schema_start - old); > - r->schema_end = new + (r->schema_end - old); > + if (r->schema_end) { > + r->schema_end = new + (r->schema_end - old); > + } > } > > if (r->host_start) { See review of the second patch about r->port_start / r->port_end. I would rather change it similarly for now. > @@ -1754,9 +1763,18 @@ ngx_http_alloc_large_header_buffer(ngx_h > > } else { > r->header_name_start = new; > - r->header_name_end = new + (r->header_name_end - old); > - r->header_start = new + (r->header_start - old); > - r->header_end = new + (r->header_end - old); > + > + if (r->header_name_end) { > + r->header_name_end = new + (r->header_name_end - old); > + } > + > + if (r->header_start) { > + r->header_start = new + (r->header_start - old); > + } > + > + if (r->header_end) { > + r->header_end = new + (r->header_end - old); > + } > } > > r->header_in = b; Otherwise looks good. -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Tue Nov 28 04:41:49 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 28 Nov 2023 04:41:49 +0000 Subject: [njs] Remove njs_timer.c forgotten in the previous commit. Message-ID: details: https://hg.nginx.org/njs/rev/d82a667c50af branches: changeset: 2239:d82a667c50af user: Dmitry Volyntsev date: Mon Nov 27 18:43:04 2023 -0800 description: Remove njs_timer.c forgotten in the previous commit. diffstat: src/njs_timer.c | 133 -------------------------------------------------------- src/njs_timer.h | 19 -------- 2 files changed, 0 insertions(+), 152 deletions(-) diffs (160 lines): diff -r dffdf7c50dfc -r d82a667c50af src/njs_timer.c --- a/src/njs_timer.c Tue Nov 21 09:00:52 2023 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ - -/* - * Copyright (C) Dmitry Volyntsev - * Copyright (C) NGINX, Inc. - */ - - -#include - - -static njs_int_t -njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_bool_t immediate, njs_value_t *retval) -{ - njs_uint_t n; - uint64_t delay; - njs_event_t *event; - njs_vm_ops_t *ops; - - if (njs_slow_path(nargs < 2)) { - njs_type_error(vm, "too few arguments"); - return NJS_ERROR; - } - - if (njs_slow_path(!njs_is_function(&args[1]))) { - njs_type_error(vm, "first arg must be a function"); - return NJS_ERROR; - } - - ops = vm->options.ops; - if (njs_slow_path(ops == NULL)) { - njs_internal_error(vm, "not supported by host environment"); - return NJS_ERROR; - } - - delay = 0; - - if (!immediate && nargs >= 3 && njs_is_number(&args[2])) { - delay = njs_number(&args[2]); - } - - event = njs_mp_alloc(vm->mem_pool, sizeof(njs_event_t)); - if (njs_slow_path(event == NULL)) { - goto memory_error; - } - - n = immediate ? 2 : 3; - - event->destructor = ops->clear_timer; - event->function = njs_function(&args[1]); - event->nargs = (nargs >= n) ? nargs - n : 0; - event->once = 1; - event->posted = 0; - - if (event->nargs != 0) { - event->args = njs_mp_alloc(vm->mem_pool, - sizeof(njs_value_t) * event->nargs); - if (njs_slow_path(event->args == NULL)) { - goto memory_error; - } - - memcpy(event->args, &args[n], sizeof(njs_value_t) * event->nargs); - } - - event->host_event = ops->set_timer(vm->external, delay, event); - if (njs_slow_path(event->host_event == NULL)) { - njs_internal_error(vm, "set_timer() failed"); - return NJS_ERROR; - } - - if (njs_add_event(vm, event) == NJS_OK) { - njs_set_number(retval, vm->event_id - 1); - } - - return NJS_OK; - -memory_error: - - njs_memory_error(vm); - - return NJS_ERROR; -} - - -njs_int_t -njs_set_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - return njs_set_timer(vm, args, nargs, unused, 0, retval); -} - - -njs_int_t -njs_set_immediate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - return njs_set_timer(vm, args, nargs, unused, 1, retval); -} - - -njs_int_t -njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - u_char buf[16], *p; - njs_int_t ret; - njs_event_t *event; - njs_lvlhsh_query_t lhq; - - if (njs_fast_path(nargs < 2) || !njs_is_number(&args[1])) { - njs_set_undefined(retval); - return NJS_OK; - } - - p = njs_sprintf(buf, buf + njs_length(buf), "%uD", - (unsigned) njs_number(&args[1])); - - lhq.key.start = buf; - lhq.key.length = p - buf; - lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length); - lhq.proto = &njs_event_hash_proto; - lhq.pool = vm->mem_pool; - - ret = njs_lvlhsh_find(&vm->events_hash, &lhq); - if (ret == NJS_OK) { - event = lhq.value; - njs_del_event(vm, event, NJS_EVENT_RELEASE | NJS_EVENT_DELETE); - } - - njs_set_undefined(retval); - - return NJS_OK; -} diff -r dffdf7c50dfc -r d82a667c50af src/njs_timer.h --- a/src/njs_timer.h Tue Nov 21 09:00:52 2023 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ - -/* - * Copyright (C) Dmitry Volyntsev - * Copyright (C) NGINX, Inc. - */ - -#ifndef _NJS_TIMER_H_INCLUDED_ -#define _NJS_TIMER_H_INCLUDED_ - - -njs_int_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); - - -#endif /* _NJS_TIMER_H_INCLUDED_ */ From xeioex at nginx.com Tue Nov 28 04:41:51 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Tue, 28 Nov 2023 04:41:51 +0000 Subject: [njs] XML: fixed building with libxml2 2.12 and later. Message-ID: details: https://hg.nginx.org/njs/rev/6a72cee054f6 branches: changeset: 2240:6a72cee054f6 user: Dmitry Volyntsev date: Mon Nov 27 18:43:36 2023 -0800 description: XML: fixed building with libxml2 2.12 and later. This fixes #684 issue on Github. diffstat: external/njs_xml_module.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-) diffs (18 lines): diff -r d82a667c50af -r 6a72cee054f6 external/njs_xml_module.c --- a/external/njs_xml_module.c Mon Nov 27 18:43:04 2023 -0800 +++ b/external/njs_xml_module.c Mon Nov 27 18:43:36 2023 -0800 @@ -1986,10 +1986,10 @@ njs_xml_nset_cleanup(void *data) static void njs_xml_error(njs_vm_t *vm, njs_xml_doc_t *current, const char *fmt, ...) { - u_char *p, *last; - va_list args; - xmlError *err; - u_char errstr[NJS_MAX_ERROR_STR]; + u_char *p, *last; + va_list args; + const xmlError *err; + u_char errstr[NJS_MAX_ERROR_STR]; last = &errstr[NJS_MAX_ERROR_STR]; From vl at inspert.ru Wed Nov 29 08:20:33 2023 From: vl at inspert.ru (Vladimir Homutov) Date: Wed, 29 Nov 2023 11:20:33 +0300 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start In-Reply-To: References: <6f957e137407d8f3f7e3.1699607515@vlws> Message-ID: On Tue, Nov 28, 2023 at 05:57:39AM +0300, Maxim Dounin wrote: > Hello! > > On Fri, Nov 10, 2023 at 12:11:55PM +0300, Vladimir Homutov via nginx-devel wrote: > > > > > It is no longer used since the refactoring in 8e5bf1bc87e2 (2008). > > Neither r->port_start nor r->port_end were ever used. > > The r->port_end is set by the parser, though it was never used by > the following code (and was never usable, since not copied by the > ngx_http_alloc_large_header_buffer() without r->port_start set). > > The 8e5bf1bc87e2 commit is completely unrelated, it is about > refactoring of the ngx_parse_inet_url() function, which had a > local variable named "port_start". exactly, thanks for noticing. > > > > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > > --- a/src/http/ngx_http_request.c > > +++ b/src/http/ngx_http_request.c > > @@ -1744,8 +1744,7 @@ ngx_http_alloc_large_header_buffer(ngx_h > > } > > } > > > > - if (r->port_start) { > > - r->port_start = new + (r->port_start - old); > > + if (r->port_end) { > > r->port_end = new + (r->port_end - old); > > } > > > > diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h > > --- a/src/http/ngx_http_request.h > > +++ b/src/http/ngx_http_request.h > > @@ -597,7 +597,6 @@ struct ngx_http_request_s { > > u_char *schema_end; > > u_char *host_start; > > u_char *host_end; > > - u_char *port_start; > > u_char *port_end; > > > > unsigned http_minor:16; > > I don't think it's a good change. Rather, we should either remove > both, or (eventually) fix these and provide some valid usage of > the port as parsed either from the request line or from the Host > header, similarly to the $host variable. > I think that we should remove both, as unused code still needs to be maintained without any advantage, as this example shows. Restoring it will be trivial, if ever required. -------------- next part -------------- A non-text attachment was scrubbed... Name: remove_start_end.diff Type: text/x-diff Size: 2400 bytes Desc: not available URL: From Johannes.Baiter at bsb-muenchen.de Wed Nov 29 08:21:15 2023 From: Johannes.Baiter at bsb-muenchen.de (Johannes Baiter) Date: Wed, 29 Nov 2023 09:21:15 +0100 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start (In Elternzeit bis 7. Januar 2024) In-Reply-To: References: <061981710200000973776C76@gwia.bsb-muenchen.de> <5BC6796F0200001273776C76@gwia.bsb-muenchen.de> Message-ID: <6566F47B020000370001BC2E@gwia.bsb-muenchen.de> Sehr geehrte Damen und Herren, liebe Kolleginnen und Kollegen, vielen Dank für Ihre E-Mail. Dies ist eine automatisch erstelle Antwort. Ich befinde mich derzeit in Elternzeit und bin ab dem 7. Januar 2024 wieder im Dienst. Bitte wenden Sie sich in dringenden Fällen an marcus.bitzl at bsb-muenchen.de. -------------------------- Thank you for your email. This is an automatic reply. I'm currently on parental leave until Januar 7 2024. In urgent cases, please contact marcus.bitzl at bsb-muenchen.de. -------------------------- Mit freundlichen Grüßen Johannes Baiter Bayerische Staatsbibliothek Digitale Bibliothek/Münchener DigitalisierungsZentrum (MDZ) Ludwigstr. 16 D-80539 München Tel.: +49 89 28638 2970 eMail: johannes.baiter at bsb-muenchen.de From Johannes.Baiter at bsb-muenchen.de Wed Nov 29 08:21:35 2023 From: Johannes.Baiter at bsb-muenchen.de (Johannes Baiter) Date: Wed, 29 Nov 2023 09:21:35 +0100 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start (In Elternzeit bis 7. Januar 2024) In-Reply-To: <6566F47B020000370001BC2E@gwia.bsb-muenchen.de> References: <061981710200000973776C76@gwia.bsb-muenchen.de> <5BC6796F0200001273776C76@gwia.bsb-muenchen.de> <6566F47B020000370001BC2E@gwia.bsb-muenchen.de> Message-ID: <6566F48F020000370001BC32@gwia.bsb-muenchen.de> Sehr geehrte Damen und Herren, liebe Kolleginnen und Kollegen, vielen Dank für Ihre E-Mail. Dies ist eine automatisch erstelle Antwort. Ich befinde mich derzeit in Elternzeit und bin ab dem 7. Januar 2024 wieder im Dienst. Bitte wenden Sie sich in dringenden Fällen an marcus.bitzl at bsb-muenchen.de. -------------------------- Thank you for your email. This is an automatic reply. I'm currently on parental leave until Januar 7 2024. In urgent cases, please contact marcus.bitzl at bsb-muenchen.de. -------------------------- Mit freundlichen Grüßen Johannes Baiter Bayerische Staatsbibliothek Digitale Bibliothek/Münchener DigitalisierungsZentrum (MDZ) Ludwigstr. 16 D-80539 München Tel.: +49 89 28638 2970 eMail: johannes.baiter at bsb-muenchen.de From vl at inspert.ru Wed Nov 29 08:24:03 2023 From: vl at inspert.ru (Vladimir Homutov) Date: Wed, 29 Nov 2023 11:24:03 +0300 Subject: [PATCH 1 of 2] HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer In-Reply-To: References: <505e927eb7a75f0fdce4.1699607514@vlws> Message-ID: On Tue, Nov 28, 2023 at 05:58:23AM +0300, Maxim Dounin wrote: > Hello! > > On Fri, Nov 10, 2023 at 12:11:54PM +0300, Vladimir Homutov via nginx-devel wrote: > > > If URI is not fully parsed yet, some pointers are not set. > > As a result, the calculation of "new + (ptr - old)" expression > > may overflow. In such a case, just avoid calculating it, as value > > will be set correctly later by the parser in any case. > > > > The issue was found by GCC undefined behaviour sanitizer. > > > > > > src/http/ngx_http_request.c | 34 ++++++++++++++++++++++++++-------- > > 1 files changed, 26 insertions(+), 8 deletions(-) > > > > > > > # HG changeset patch > > # User Vladimir Khomutov > > # Date 1699604478 -10800 > > # Fri Nov 10 11:21:18 2023 +0300 > > # Node ID 505e927eb7a75f0fdce4caddb4ab9d9c71c9b9c8 > > # Parent dadd13fdcf5228c8e8380e120d4621002e3b0919 > > HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer. > > > > If URI is not fully parsed yet, some pointers are not set. > > As a result, the calculation of "new + (ptr - old)" expression > > may overflow. In such a case, just avoid calculating it, as value > > will be set correctly later by the parser in any case. > > > > The issue was found by GCC undefined behaviour sanitizer. > > I would rather refrain from saying this is an issue, it is not > (unless a compiler actually starts to do silly things as long as > it sees something formally defined as "undefined behavior" in C > standard, and this would be indeed an issue - in the compiler). > As already noted in the initial review, the code as written is > completely safe in practice. For such mostly style commits we > usually write something like "Prodded by...". totally agreed > > Also note that the issue is not an overflow, but rather > subtraction of pointers which do not belong to the same array > object (C11, 6.5.6 Additive operators, p.9): > > : When two pointers are subtracted, both shall point to elements > : of the same array object, or one past the last element of the > : array object > > The undefined behaviour is present as long as "ptr" and "old" are > not in the same buffer (i.e., array object), which is the case > when "ptr" is not set. And another one follows when trying to add > the (already undefined) subtraction result to "new" (since the > result is not going to belong to the same array object): > > : If both the pointer operand and the result point to elements of > : the same array object, or one past the last element of the array > : object, the evaluation shall not produce an overflow; otherwise, > : the behavior is undefined. > > Overflow here might be an indicator that certainly there is an > undefined behaviour, but it's just an indicator. > > You may want to rewrite commit log accordingly. The commit log was updated. > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > > --- a/src/http/ngx_http_request.c > > +++ b/src/http/ngx_http_request.c > > @@ -1718,14 +1718,23 @@ ngx_http_alloc_large_header_buffer(ngx_h [...] > > if (r->host_start) { > > See review of the second patch about r->port_start / r->port_end. > I would rather change it similarly for now. I would prefer to remove both, so this patch has nothing about it. updated patch: -------------- next part -------------- A non-text attachment was scrubbed... Name: request_checks.diff Type: text/x-diff Size: 2485 bytes Desc: not available URL: From mdounin at mdounin.ru Thu Nov 30 01:13:23 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 30 Nov 2023 04:13:23 +0300 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start In-Reply-To: References: <6f957e137407d8f3f7e3.1699607515@vlws> Message-ID: Hello! On Wed, Nov 29, 2023 at 11:20:33AM +0300, Vladimir Homutov via nginx-devel wrote: > On Tue, Nov 28, 2023 at 05:57:39AM +0300, Maxim Dounin wrote: > > Hello! > > > > On Fri, Nov 10, 2023 at 12:11:55PM +0300, Vladimir Homutov via nginx-devel wrote: > > > > > > > > It is no longer used since the refactoring in 8e5bf1bc87e2 (2008). > > > > Neither r->port_start nor r->port_end were ever used. > > > > The r->port_end is set by the parser, though it was never used by > > the following code (and was never usable, since not copied by the > > ngx_http_alloc_large_header_buffer() without r->port_start set). > > > > The 8e5bf1bc87e2 commit is completely unrelated, it is about > > refactoring of the ngx_parse_inet_url() function, which had a > > local variable named "port_start". > > exactly, thanks for noticing. > > > > > > > > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > > > --- a/src/http/ngx_http_request.c > > > +++ b/src/http/ngx_http_request.c > > > @@ -1744,8 +1744,7 @@ ngx_http_alloc_large_header_buffer(ngx_h > > > } > > > } > > > > > > - if (r->port_start) { > > > - r->port_start = new + (r->port_start - old); > > > + if (r->port_end) { > > > r->port_end = new + (r->port_end - old); > > > } > > > > > > diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h > > > --- a/src/http/ngx_http_request.h > > > +++ b/src/http/ngx_http_request.h > > > @@ -597,7 +597,6 @@ struct ngx_http_request_s { > > > u_char *schema_end; > > > u_char *host_start; > > > u_char *host_end; > > > - u_char *port_start; > > > u_char *port_end; > > > > > > unsigned http_minor:16; > > > > I don't think it's a good change. Rather, we should either remove > > both, or (eventually) fix these and provide some valid usage of > > the port as parsed either from the request line or from the Host > > header, similarly to the $host variable. > > > > I think that we should remove both, as unused code still needs to be > maintained without any advantage, as this example shows. > Restoring it will be trivial, if ever required. > > > > # HG changeset patch > # User Vladimir Khomutov > # Date 1701165434 -10800 > # Tue Nov 28 12:57:14 2023 +0300 > # Node ID dacad3a9c7b8435a4c67ad2b67f261e7b4e36d8e > # Parent f366007dd23a6ce8e8427c1b3042781b618a2ade > HTTP: removed unused r->port_start and r->port_end. > > Neither r->port_start nor r->port_end were ever used. > > The r->port_end is set by the parser, though it was never used by > the following code (and was never usable, since not copied by the > ngx_http_alloc_large_header_buffer() without r->port_start set). > > diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c > --- a/src/http/ngx_http_parse.c > +++ b/src/http/ngx_http_parse.c > @@ -451,19 +451,16 @@ ngx_http_parse_request_line(ngx_http_req > > switch (ch) { > case '/': > - r->port_end = p; > r->uri_start = p; > state = sw_after_slash_in_uri; > break; > case '?': > - r->port_end = p; > r->uri_start = p; > r->args_start = p + 1; > r->empty_path_in_uri = 1; > state = sw_uri; > break; > case ' ': > - r->port_end = p; > /* > * use single "/" from request line to preserve pointers, > * if request line will be copied to large client buffer > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > --- a/src/http/ngx_http_request.c > +++ b/src/http/ngx_http_request.c > @@ -1735,11 +1735,6 @@ ngx_http_alloc_large_header_buffer(ngx_h > } > } > > - if (r->port_start) { > - r->port_start = new + (r->port_start - old); > - r->port_end = new + (r->port_end - old); > - } > - > if (r->uri_ext) { > r->uri_ext = new + (r->uri_ext - old); > } > diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h > --- a/src/http/ngx_http_request.h > +++ b/src/http/ngx_http_request.h > @@ -597,8 +597,6 @@ struct ngx_http_request_s { > u_char *schema_end; > u_char *host_start; > u_char *host_end; > - u_char *port_start; > - u_char *port_end; > > unsigned http_minor:16; > unsigned http_major:16; Looks good, pushed to http://mdounin.ru/hg/nginx/. -- Maxim Dounin http://mdounin.ru/ From Johannes.Baiter at bsb-muenchen.de Thu Nov 30 01:13:52 2023 From: Johannes.Baiter at bsb-muenchen.de (Johannes Baiter) Date: Thu, 30 Nov 2023 02:13:52 +0100 Subject: [PATCH 2 of 2] HTTP: removed unused r->port_start (In Elternzeit bis 7. Januar 2024) In-Reply-To: <176B8F71020000293098D702@gwia.bsb-muenchen.de> References: <061981710200000973776C76@gwia.bsb-muenchen.de> <5BC6796F0200001273776C76@gwia.bsb-muenchen.de> <176B8F71020000293098D702@gwia.bsb-muenchen.de> Message-ID: <6567E1D0020000370001BC45@gwia.bsb-muenchen.de> Sehr geehrte Damen und Herren, liebe Kolleginnen und Kollegen, vielen Dank für Ihre E-Mail. Dies ist eine automatisch erstelle Antwort. Ich befinde mich derzeit in Elternzeit und bin ab dem 7. Januar 2024 wieder im Dienst. Bitte wenden Sie sich in dringenden Fällen an marcus.bitzl at bsb-muenchen.de. -------------------------- Thank you for your email. This is an automatic reply. I'm currently on parental leave until Januar 7 2024. In urgent cases, please contact marcus.bitzl at bsb-muenchen.de. -------------------------- Mit freundlichen Grüßen Johannes Baiter Bayerische Staatsbibliothek Digitale Bibliothek/Münchener DigitalisierungsZentrum (MDZ) Ludwigstr. 16 D-80539 München Tel.: +49 89 28638 2970 eMail: johannes.baiter at bsb-muenchen.de From mdounin at mdounin.ru Thu Nov 30 01:15:53 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 30 Nov 2023 04:15:53 +0300 Subject: [PATCH 1 of 2] HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer In-Reply-To: References: <505e927eb7a75f0fdce4.1699607514@vlws> Message-ID: Hello! On Wed, Nov 29, 2023 at 11:24:03AM +0300, Vladimir Homutov via nginx-devel wrote: > On Tue, Nov 28, 2023 at 05:58:23AM +0300, Maxim Dounin wrote: > > Hello! > > > > On Fri, Nov 10, 2023 at 12:11:54PM +0300, Vladimir Homutov via nginx-devel wrote: > > > > > If URI is not fully parsed yet, some pointers are not set. > > > As a result, the calculation of "new + (ptr - old)" expression > > > may overflow. In such a case, just avoid calculating it, as value > > > will be set correctly later by the parser in any case. > > > > > > The issue was found by GCC undefined behaviour sanitizer. > > > > > > > > > src/http/ngx_http_request.c | 34 ++++++++++++++++++++++++++-------- > > > 1 files changed, 26 insertions(+), 8 deletions(-) > > > > > > > > > > > # HG changeset patch > > > # User Vladimir Khomutov > > > # Date 1699604478 -10800 > > > # Fri Nov 10 11:21:18 2023 +0300 > > > # Node ID 505e927eb7a75f0fdce4caddb4ab9d9c71c9b9c8 > > > # Parent dadd13fdcf5228c8e8380e120d4621002e3b0919 > > > HTTP: uniform overflow checks in ngx_http_alloc_large_header_buffer. > > > > > > If URI is not fully parsed yet, some pointers are not set. > > > As a result, the calculation of "new + (ptr - old)" expression > > > may overflow. In such a case, just avoid calculating it, as value > > > will be set correctly later by the parser in any case. > > > > > > The issue was found by GCC undefined behaviour sanitizer. > > > > I would rather refrain from saying this is an issue, it is not > > (unless a compiler actually starts to do silly things as long as > > it sees something formally defined as "undefined behavior" in C > > standard, and this would be indeed an issue - in the compiler). > > As already noted in the initial review, the code as written is > > completely safe in practice. For such mostly style commits we > > usually write something like "Prodded by...". > > totally agreed > > > > > Also note that the issue is not an overflow, but rather > > subtraction of pointers which do not belong to the same array > > object (C11, 6.5.6 Additive operators, p.9): > > > > : When two pointers are subtracted, both shall point to elements > > : of the same array object, or one past the last element of the > > : array object > > > > The undefined behaviour is present as long as "ptr" and "old" are > > not in the same buffer (i.e., array object), which is the case > > when "ptr" is not set. And another one follows when trying to add > > the (already undefined) subtraction result to "new" (since the > > result is not going to belong to the same array object): > > > > : If both the pointer operand and the result point to elements of > > : the same array object, or one past the last element of the array > > : object, the evaluation shall not produce an overflow; otherwise, > > : the behavior is undefined. > > > > Overflow here might be an indicator that certainly there is an > > undefined behaviour, but it's just an indicator. > > > > You may want to rewrite commit log accordingly. > > The commit log was updated. > > > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > > > --- a/src/http/ngx_http_request.c > > > +++ b/src/http/ngx_http_request.c > > > @@ -1718,14 +1718,23 @@ ngx_http_alloc_large_header_buffer(ngx_h > [...] > > > if (r->host_start) { > > > > See review of the second patch about r->port_start / r->port_end. > > I would rather change it similarly for now. > > I would prefer to remove both, so this patch has nothing about it. > > updated patch: > > > # HG changeset patch > # User Vladimir Khomutov > # Date 1701245585 -10800 > # Wed Nov 29 11:13:05 2023 +0300 > # Node ID 7c8ecb3fee20dfbb9a627441377dd09509988e2a > # Parent dacad3a9c7b8435a4c67ad2b67f261e7b4e36d8e > HTTP: uniform checks in ngx_http_alloc_large_header_buffer(). > > If URI is not fully parsed yet, some pointers are not set. As a result, > the calculation of "new + (ptr - old)" expression is flawed. > > According to C11, 6.5.6 Additive operators, p.9: Seems to be an extra space in "to C11". > > : When two pointers are subtracted, both shall point to elements > : of the same array object, or one past the last element of the > : array object > > Since "ptr" is not set, subtraction leads to undefined behaviour, because > "ptr" and "old" are not in the same buffer (i.e. array objects). > > Prodded by GCC undefined behaviour sanitizer. > > diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c > --- a/src/http/ngx_http_request.c > +++ b/src/http/ngx_http_request.c > @@ -1718,14 +1718,23 @@ ngx_http_alloc_large_header_buffer(ngx_h > r->request_end = new + (r->request_end - old); > } > > - r->method_end = new + (r->method_end - old); > - > - r->uri_start = new + (r->uri_start - old); > - r->uri_end = new + (r->uri_end - old); > + if (r->method_end) { > + r->method_end = new + (r->method_end - old); > + } > + > + if (r->uri_start) { > + r->uri_start = new + (r->uri_start - old); > + } > + > + if (r->uri_end) { > + r->uri_end = new + (r->uri_end - old); > + } > > if (r->schema_start) { > r->schema_start = new + (r->schema_start - old); > - r->schema_end = new + (r->schema_end - old); > + if (r->schema_end) { > + r->schema_end = new + (r->schema_end - old); > + } > } > > if (r->host_start) { > @@ -1749,9 +1758,18 @@ ngx_http_alloc_large_header_buffer(ngx_h > > } else { > r->header_name_start = new; > - r->header_name_end = new + (r->header_name_end - old); > - r->header_start = new + (r->header_start - old); > - r->header_end = new + (r->header_end - old); > + > + if (r->header_name_end) { > + r->header_name_end = new + (r->header_name_end - old); > + } > + > + if (r->header_start) { > + r->header_start = new + (r->header_start - old); > + } > + > + if (r->header_end) { > + r->header_end = new + (r->header_end - old); > + } > } > > r->header_in = b; Otherwise looks good, pushed to http://mdounin.ru/hg/nginx/ (with the extra space removed), thanks. -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Thu Nov 30 03:53:38 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Thu, 30 Nov 2023 03:53:38 +0000 Subject: [njs] Tests: detection when "xml" module is disabled. Message-ID: details: https://hg.nginx.org/njs/rev/9a6a79e21822 branches: changeset: 2241:9a6a79e21822 user: Dmitry Volyntsev date: Wed Nov 29 18:43:37 2023 -0800 description: Tests: detection when "xml" module is disabled. diffstat: test/harness/compatXml.js | 11 ++++++++++- test/xml/external_entity_ignored.t.js | 3 +-- test/xml/saml_verify.t.js | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diffs (44 lines): diff -r 6a72cee054f6 -r 9a6a79e21822 test/harness/compatXml.js --- a/test/harness/compatXml.js Mon Nov 27 18:43:36 2023 -0800 +++ b/test/harness/compatXml.js Wed Nov 29 18:43:37 2023 -0800 @@ -4,5 +4,14 @@ if (typeof require == 'function' && typeof njs == 'object' && typeof njs.version == 'string') { - xml = require('xml'); + try { + xml = require('xml'); + + } catch (e) { + // ignore + } } + +function has_xml() { + return xml; +} diff -r 6a72cee054f6 -r 9a6a79e21822 test/xml/external_entity_ignored.t.js --- a/test/xml/external_entity_ignored.t.js Mon Nov 27 18:43:36 2023 -0800 +++ b/test/xml/external_entity_ignored.t.js Wed Nov 29 18:43:37 2023 -0800 @@ -11,8 +11,7 @@ let data = ` &c; `; -if (has_njs()) { - const xml = require('xml'); +if (has_njs() && has_xml()) { let doc = xml.parse(data); assert.sameValue(doc.$root.$text, ""); } diff -r 6a72cee054f6 -r 9a6a79e21822 test/xml/saml_verify.t.js --- a/test/xml/saml_verify.t.js Mon Nov 27 18:43:36 2023 -0800 +++ b/test/xml/saml_verify.t.js Wed Nov 29 18:43:37 2023 -0800 @@ -280,7 +280,7 @@ function p(args, default_opts) { let saml_verify_tsuite = { name: "SAML verify", - skip: () => (!has_njs() || !has_fs() || !has_webcrypto()), + skip: () => (!has_njs() || !has_fs() || !has_webcrypto() || !has_xml()), T: verify, prepare_args: p, opts: { From xeioex at nginx.com Thu Nov 30 03:53:40 2023 From: xeioex at nginx.com (=?utf-8?q?Dmitry_Volyntsev?=) Date: Thu, 30 Nov 2023 03:53:40 +0000 Subject: [njs] Modules: simplified setTimeout() timer callback. Message-ID: details: https://hg.nginx.org/njs/rev/f64d1f9f19e5 branches: changeset: 2242:f64d1f9f19e5 user: Dmitry Volyntsev date: Wed Nov 29 18:43:45 2023 -0800 description: Modules: simplified setTimeout() timer callback. diffstat: nginx/ngx_js.c | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-) diffs (19 lines): diff -r 9a6a79e21822 -r f64d1f9f19e5 nginx/ngx_js.c --- a/nginx/ngx_js.c Wed Nov 29 18:43:37 2023 -0800 +++ b/nginx/ngx_js.c Wed Nov 29 18:43:45 2023 -0800 @@ -973,14 +973,12 @@ ngx_js_timer_handler(ngx_event_t *ev) ngx_js_event_t *event; ngx_connection_t *c; njs_external_ptr_t external; - njs_opaque_value_t retval; event = (ngx_js_event_t *) ((u_char *) ev - offsetof(ngx_js_event_t, ev)); vm = event->vm; - ret = njs_vm_invoke(vm, event->function, event->args, event->nargs, - njs_value_arg(&retval)); + ret = njs_vm_call(vm, event->function, event->args, event->nargs); external = njs_vm_external_ptr(vm); ctx = ngx_external_ctx(vm, external); From Johannes.Baiter at bsb-muenchen.de Thu Nov 30 03:53:57 2023 From: Johannes.Baiter at bsb-muenchen.de (Johannes Baiter) Date: Thu, 30 Nov 2023 04:53:57 +0100 Subject: [njs] Tests: detection when "xml" module is disabled. (In Elternzeit bis 7. Januar 2024) In-Reply-To: References: Message-ID: <65680755020000370001BC49@gwia.bsb-muenchen.de> Sehr geehrte Damen und Herren, liebe Kolleginnen und Kollegen, vielen Dank für Ihre E-Mail. Dies ist eine automatisch erstelle Antwort. Ich befinde mich derzeit in Elternzeit und bin ab dem 7. Januar 2024 wieder im Dienst. Bitte wenden Sie sich in dringenden Fällen an marcus.bitzl at bsb-muenchen.de. -------------------------- Thank you for your email. This is an automatic reply. I'm currently on parental leave until Januar 7 2024. In urgent cases, please contact marcus.bitzl at bsb-muenchen.de. -------------------------- Mit freundlichen Grüßen Johannes Baiter Bayerische Staatsbibliothek Digitale Bibliothek/Münchener DigitalisierungsZentrum (MDZ) Ludwigstr. 16 D-80539 München Tel.: +49 89 28638 2970 eMail: johannes.baiter at bsb-muenchen.de From v.zhestikov at f5.com Thu Nov 30 04:48:02 2023 From: v.zhestikov at f5.com (=?utf-8?q?Vadim_Zhestikov?=) Date: Thu, 30 Nov 2023 04:48:02 +0000 Subject: [njs] Fixed memory over-read in njs_utf8_prev() and njs_utf8_next(). Message-ID: details: https://hg.nginx.org/njs/rev/a3364db5fdef branches: changeset: 2243:a3364db5fdef user: Vadim Zhestikov date: Wed Nov 29 20:46:32 2023 -0800 description: Fixed memory over-read in njs_utf8_prev() and njs_utf8_next(). Previously, njs_utf8_next() might over-read up to 1 byte beyond the string memory. Whereas njs_utf8_prev() might over-read unlimited number of bytes before the string. diffstat: src/njs_iterator.c | 2 +- src/njs_string.c | 4 ++-- src/njs_utf8.h | 11 ++++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diffs (66 lines): diff -r f64d1f9f19e5 -r a3364db5fdef src/njs_iterator.c --- a/src/njs_iterator.c Wed Nov 29 18:43:45 2023 -0800 +++ b/src/njs_iterator.c Wed Nov 29 20:46:32 2023 -0800 @@ -542,7 +542,7 @@ njs_object_iterate_reverse(njs_vm_t *vm, } while (i-- > to) { - pos = njs_utf8_prev(p); + pos = njs_utf8_prev(p, string_prop.start); /* This cannot fail. */ (void) njs_string_new(vm, &character, pos, p - pos , 1); diff -r f64d1f9f19e5 -r a3364db5fdef src/njs_string.c --- a/src/njs_string.c Wed Nov 29 18:43:45 2023 -0800 +++ b/src/njs_string.c Wed Nov 29 20:46:32 2023 -0800 @@ -1884,7 +1884,7 @@ njs_string_prototype_last_index_of(njs_v p = njs_string_utf8_offset(string.start, end, index); - for (; p >= string.start; p = njs_utf8_prev(p)) { + for (; p >= string.start; p = njs_utf8_prev(p, string.start)) { if ((p + s.size) <= end && memcmp(p, s.start, s.size) == 0) { goto done; } @@ -2408,7 +2408,7 @@ njs_string_trim(const njs_value_t *value break; } - prev = njs_utf8_prev(prev); + prev = njs_utf8_prev(prev, start); p = prev; cp = njs_utf8_decode(&ctx, &p, end); diff -r f64d1f9f19e5 -r a3364db5fdef src/njs_utf8.h --- a/src/njs_utf8.h Wed Nov 29 18:43:45 2023 -0800 +++ b/src/njs_utf8.h Wed Nov 29 20:46:32 2023 -0800 @@ -53,6 +53,10 @@ njs_utf8_next(const u_char *p, const u_c if ((c & 0x80) != 0) { + if (njs_slow_path(p >= end)) { + return p; + } + do { c = *p; @@ -70,12 +74,17 @@ njs_utf8_next(const u_char *p, const u_c njs_inline const u_char * -njs_utf8_prev(const u_char *p) +njs_utf8_prev(const u_char *p, const u_char *start) { u_char c; do { p--; + + if (njs_slow_path(p < start)) { + break; + } + c = *p; } while ((c & 0xC0) == 0x80); From v.zhestikov at f5.com Thu Nov 30 04:48:04 2023 From: v.zhestikov at f5.com (=?utf-8?q?Vadim_Zhestikov?=) Date: Thu, 30 Nov 2023 04:48:04 +0000 Subject: [njs] Removed remnants of GC code. Message-ID: details: https://hg.nginx.org/njs/rev/439ea33e531c branches: changeset: 2244:439ea33e531c user: Vadim Zhestikov date: Wed Nov 29 20:46:36 2023 -0800 description: Removed remnants of GC code. The code never worked and comments are in a broken state. diffstat: src/njs_array.c | 10 --------- src/njs_function.c | 3 -- src/njs_iterator.c | 2 - src/njs_object.c | 5 ---- src/njs_object.h | 1 - src/njs_scope.c | 1 - src/njs_string.c | 6 ----- src/njs_string.h | 1 - src/njs_value.c | 57 ------------------------------------------------------ src/njs_value.h | 29 --------------------------- src/njs_vmcode.c | 6 +---- 11 files changed, 1 insertions(+), 120 deletions(-) diffs (343 lines): diff -r a3364db5fdef -r 439ea33e531c src/njs_array.c --- a/src/njs_array.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_array.c Wed Nov 29 20:46:36 2023 -0800 @@ -163,8 +163,6 @@ njs_array_convert_to_slow_array(njs_vm_t } } - /* GC: release value. */ - njs_mp_free(vm->mem_pool, array->data); array->start = NULL; @@ -338,7 +336,6 @@ njs_array_add(njs_vm_t *vm, njs_array_t ret = njs_array_expand(vm, array, 0, 1); if (njs_fast_path(ret == NJS_OK)) { - /* GC: retain value. */ array->start[array->length++] = *value; } @@ -466,7 +463,6 @@ njs_array_constructor(njs_vm_t *vm, njs_ } else { while (size != 0) { - njs_retain(args); *value++ = *args++; size--; } @@ -967,7 +963,6 @@ njs_array_prototype_push(njs_vm_t *vm, n } for (i = 1; i < nargs; i++) { - /* GC: njs_retain(&args[i]); */ array->start[array->length++] = args[i]; } } @@ -1094,7 +1089,6 @@ njs_array_prototype_unshift(njs_vm_t *vm do { n--; - /* GC: njs_retain(&args[n]); */ array->start--; array->start[0] = args[n]; } while (n > 1); @@ -2133,8 +2127,6 @@ njs_array_iterator_call(njs_vm_t *vm, nj { njs_value_t arguments[3]; - /* GC: array elt, array */ - arguments[0] = *entry; njs_set_number(&arguments[1], n); njs_value_assign(&arguments[2], &args->value); @@ -2303,8 +2295,6 @@ njs_array_handler_reduce(njs_vm_t *vm, n return NJS_OK; } - /* GC: array elt, array */ - njs_set_undefined(&arguments[0]); njs_value_assign(&arguments[1], &args->argument); arguments[2] = *entry; diff -r a3364db5fdef -r 439ea33e531c src/njs_function.c --- a/src/njs_function.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_function.c Wed Nov 29 20:46:36 2023 -0800 @@ -310,7 +310,6 @@ njs_function_rest_parameters_init(njs_vm return NJS_ERROR; } - /* GC: retain. */ njs_set_array(rest_arguments, array); vm->top_frame->local[n] = rest_arguments; @@ -1419,8 +1418,6 @@ njs_function_prototype_bind(njs_vm_t *vm function->bound = values; - /* GC: ? retain args. */ - memcpy(values, args, size); njs_set_function(retval, function); diff -r a3364db5fdef -r 439ea33e531c src/njs_iterator.c --- a/src/njs_iterator.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_iterator.c Wed Nov 29 20:46:36 2023 -0800 @@ -50,7 +50,6 @@ njs_array_iterator_create(njs_vm_t *vm, return NJS_ERROR; } - /* GC retain it->target */ it->target = *target; it->next = 0; it->kind = kind; @@ -163,7 +162,6 @@ njs_array_iterator_next(njs_vm_t *vm, nj release: - /* GC release it->target */ njs_mp_free(vm->mem_pool, it); njs_set_invalid(njs_object_value(iterator)); diff -r a3364db5fdef -r 439ea33e531c src/njs_object.c --- a/src/njs_object.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_object.c Wed Nov 29 20:46:36 2023 -0800 @@ -279,7 +279,6 @@ njs_object_create(njs_vm_t *vm, njs_valu } if (!njs_is_null(value)) { - /* GC */ object->__proto__ = njs_object(value); } else { @@ -2022,8 +2021,6 @@ njs_property_prototype_create(njs_vm_t * return NULL; } - /* GC */ - njs_set_type_object(njs_prop_value(prop), prototype, prototype->type); lhq.value = prop; @@ -2273,8 +2270,6 @@ njs_property_constructor_set(njs_vm_t *v return NULL; } - /* GC */ - njs_value_assign(njs_prop_value(prop), constructor); prop->enumerable = 0; diff -r a3364db5fdef -r 439ea33e531c src/njs_object.h --- a/src/njs_object.h Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_object.h Wed Nov 29 20:46:36 2023 -0800 @@ -196,7 +196,6 @@ njs_primitive_value_to_key(njs_vm_t *vm, case NJS_SYMBOL: case NJS_STRING: - /* GC: njs_retain(src); */ value = src; break; diff -r a3364db5fdef -r 439ea33e531c src/njs_scope.c --- a/src/njs_scope.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_scope.c Wed Nov 29 20:46:36 2023 -0800 @@ -235,7 +235,6 @@ njs_scope_value_index(njs_vm_t *vm, cons string->start = (u_char *) string + sizeof(njs_string_t); string->length = src->long_string.data->length; - string->retain = 0xffff; memcpy(string->start, start, size); } diff -r a3364db5fdef -r 439ea33e531c src/njs_string.c --- a/src/njs_string.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_string.c Wed Nov 29 20:46:36 2023 -0800 @@ -120,7 +120,6 @@ njs_string_set(njs_vm_t *vm, njs_value_t string->start = (u_char *) start; string->length = 0; - string->retain = 1; } return NJS_OK; @@ -233,7 +232,6 @@ njs_string_alloc(njs_vm_t *vm, njs_value string->start = (u_char *) string + sizeof(njs_string_t); string->length = length; - string->retain = 1; if (map_offset != 0) { map = (uint32_t *) (string->start + map_offset); @@ -520,8 +518,6 @@ void njs_string_copy(njs_value_t *dst, njs_value_t *src) { *dst = *src; - - /* GC: long string retain */ } @@ -701,8 +697,6 @@ njs_string_instance_length(njs_vm_t *vm, njs_set_number(retval, length); - njs_release(vm, value); - return NJS_OK; } diff -r a3364db5fdef -r 439ea33e531c src/njs_string.h --- a/src/njs_string.h Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_string.h Wed Nov 29 20:46:36 2023 -0800 @@ -73,7 +73,6 @@ struct njs_string_s { u_char *start; uint32_t length; /* Length in UTF-8 characters. */ - uint32_t retain; /* Link counter. */ }; diff -r a3364db5fdef -r 439ea33e531c src/njs_value.c --- a/src/njs_value.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_value.c Wed Nov 29 20:46:36 2023 -0800 @@ -58,61 +58,6 @@ const njs_value_t njs_string_anonymous const njs_value_t njs_string_memory_error = njs_string("MemoryError"); -void -njs_value_retain(njs_value_t *value) -{ - njs_string_t *string; - - if (njs_is_string(value)) { - - if (value->long_string.external != 0xff) { - string = value->long_string.data; - - njs_thread_log_debug("retain:%uxD \"%*s\"", string->retain, - value->long_string.size, string->start); - - if (string->retain != 0xffff) { - string->retain++; - } - } - } -} - - -void -njs_value_release(njs_vm_t *vm, njs_value_t *value) -{ - njs_string_t *string; - - if (njs_is_string(value)) { - - if (value->long_string.external != 0xff) { - string = value->long_string.data; - - njs_thread_log_debug("release:%uxD \"%*s\"", string->retain, - value->long_string.size, string->start); - - if (string->retain != 0xffff) { - string->retain--; - -#if 0 - if (string->retain == 0) { - if ((u_char *) string + sizeof(njs_string_t) - != string->start) - { - njs_memcache_pool_free(vm->mem_pool, - string->start); - } - - njs_memcache_pool_free(vm->mem_pool, string); - } -#endif - } - } - } -} - - /* * A hint value is 0 for numbers and 1 for strings. The value chooses * method calls order specified by ECMAScript 5.1: "valueOf", "toString" @@ -140,7 +85,6 @@ njs_value_to_primitive(njs_vm_t *vm, njs if (njs_is_primitive(value)) { - /* GC */ *dst = *value; return NJS_OK; } @@ -1622,7 +1566,6 @@ njs_primitive_value_to_string(njs_vm_t * return NJS_ERROR; case NJS_STRING: - /* GC: njs_retain(src); */ value = src; break; diff -r a3364db5fdef -r 439ea33e531c src/njs_value.h --- a/src/njs_value.h Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_value.h Wed Nov 29 20:46:36 2023 -0800 @@ -1029,35 +1029,6 @@ njs_set_object_value(njs_value_t *value, #define njs_set_invalid(value) \ (value)->type = NJS_INVALID - -#if 0 /* GC: todo */ - -#define njs_retain(value) \ - do { \ - if ((value)->data.truth == NJS_STRING_LONG) { \ - njs_value_retain(value); \ - } \ - } while (0) - - -#define njs_release(vm, value) \ - do { \ - if ((value)->data.truth == NJS_STRING_LONG) { \ - njs_value_release((vm), (value)); \ - } \ - } while (0) - -#else - -#define njs_retain(value) -#define njs_release(vm, value) - -#endif - - - -void njs_value_retain(njs_value_t *value); -void njs_value_release(njs_vm_t *vm, njs_value_t *value); njs_int_t njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, njs_uint_t hint); njs_array_t *njs_value_enumerate(njs_vm_t *vm, njs_value_t *value, diff -r a3364db5fdef -r 439ea33e531c src/njs_vmcode.c --- a/src/njs_vmcode.c Wed Nov 29 20:46:32 2023 -0800 +++ b/src/njs_vmcode.c Wed Nov 29 20:46:36 2023 -0800 @@ -2030,7 +2030,6 @@ njs_vmcode_property_init(njs_vm_t *vm, n array->length = index + 1; } - /* GC: retain. */ array->start[index] = *init; break; @@ -2569,10 +2568,7 @@ njs_vmcode_return(njs_vm_t *vm, njs_valu frame = (njs_frame_t *) vm->top_frame; if (frame->native.ctor) { - if (njs_is_object(retval)) { - njs_release(vm, frame->native.local[0]); - - } else { + if (!njs_is_object(retval)) { retval = frame->native.local[0]; } } From arut at nginx.com Thu Nov 30 11:05:26 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:26 +0400 Subject: [PATCH 0 of 6] QUIC PATH_CHALLENGE-related series In-Reply-To: References: Message-ID: Hi, A number of patches discussed previously. -- Roman Arutyunyan From arut at nginx.com Thu Nov 30 11:05:27 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:27 +0400 Subject: [PATCH 1 of 6] QUIC: avoid partial expansion of PATH_CHALLENGE/PATH_RESPONSE In-Reply-To: References: Message-ID: <088ee5449fdc67ee8ab9.1701342327@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1701267205 -14400 # Wed Nov 29 18:13:25 2023 +0400 # Node ID 088ee5449fdc67ee8ab983910d98599652683434 # Parent 7ec761f0365f418511e30b82e9adf80bc56681df QUIC: avoid partial expansion of PATH_CHALLENGE/PATH_RESPONSE. By default packets with these frames are expanded to 1200 bytes. Previously, if anti-amplification limit did not allow this expansion, it was limited to whatever size was allowed. However RFC 9000 clearly states no partial expansion should happen in both cases. Section 8.2.1. Initiating Path Validation: An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame to at least the smallest allowed maximum datagram size of 1200 bytes, unless the anti-amplification limit for the path does not permit sending a datagram of this size. Section 8.2.2. Path Validation Responses: An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame to at least the smallest allowed maximum datagram size of 1200 bytes. ... However, an endpoint MUST NOT expand the datagram containing the PATH_RESPONSE if the resulting data exceeds the anti-amplification limit. diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -106,8 +106,7 @@ struct ngx_quic_path_s { size_t max_mtu; off_t sent; off_t received; - u_char challenge1[8]; - u_char challenge2[8]; + u_char challenge[2][8]; uint64_t seqnum; uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES]; ngx_str_t addr_text; diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -36,6 +36,7 @@ ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) { + size_t min; ngx_quic_frame_t frame, *fp; ngx_quic_connection_t *qc; @@ -57,8 +58,14 @@ ngx_quic_handle_path_challenge_frame(ngx /* * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame * to at least the smallest allowed maximum datagram size of 1200 bytes. + * ... + * However, an endpoint MUST NOT expand the datagram containing the + * PATH_RESPONSE if the resulting data exceeds the anti-amplification limit. */ - if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) == NGX_ERROR) { + + min = (ngx_quic_path_limit(c, pkt->path, 1200) < 1200) ? 0 : 1200; + + if (ngx_quic_frame_sendto(c, &frame, min, pkt->path) == NGX_ERROR) { return NGX_ERROR; } @@ -113,8 +120,8 @@ ngx_quic_handle_path_response_frame(ngx_ continue; } - if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 - || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) + if (ngx_memcmp(path->challenge[0], f->data, sizeof(f->data)) == 0 + || ngx_memcmp(path->challenge[1], f->data, sizeof(f->data)) == 0) { goto valid; } @@ -510,11 +517,7 @@ ngx_quic_validate_path(ngx_connection_t path->tries = 0; - if (RAND_bytes(path->challenge1, 8) != 1) { - return NGX_ERROR; - } - - if (RAND_bytes(path->challenge2, 8) != 1) { + if (RAND_bytes((u_char *) path->challenge, sizeof(path->challenge)) != 1) { return NGX_ERROR; } @@ -535,6 +538,8 @@ ngx_quic_validate_path(ngx_connection_t static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) { + size_t min; + ngx_uint_t n; ngx_quic_frame_t frame; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -546,26 +551,24 @@ ngx_quic_send_path_challenge(ngx_connect frame.level = ssl_encryption_application; frame.type = NGX_QUIC_FT_PATH_CHALLENGE; - ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); + for (n = 0; n < 2; n++) { + + ngx_memcpy(frame.u.path_challenge.data, path->challenge[n], 8); - /* - * RFC 9000, 8.2.1. Initiating Path Validation - * - * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame - * to at least the smallest allowed maximum datagram size of 1200 bytes, - * unless the anti-amplification limit for the path does not permit - * sending a datagram of this size. - */ + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes, + * unless the anti-amplification limit for the path does not permit + * sending a datagram of this size. + */ - /* same applies to PATH_RESPONSE frames */ - if (ngx_quic_frame_sendto(c, &frame, 1200, path) == NGX_ERROR) { - return NGX_ERROR; - } + min = (ngx_quic_path_limit(c, path, 1200) < 1200) ? 0 : 1200; - ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); - - if (ngx_quic_frame_sendto(c, &frame, 1200, path) == NGX_ERROR) { - return NGX_ERROR; + if (ngx_quic_frame_sendto(c, &frame, min, path) == NGX_ERROR) { + return NGX_ERROR; + } } return NGX_OK; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -63,8 +63,6 @@ static ssize_t ngx_quic_send(ngx_connect struct sockaddr *sockaddr, socklen_t socklen); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); -static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, - size_t size); ngx_int_t @@ -1250,7 +1248,7 @@ ngx_quic_frame_sendto(ngx_connection_t * } -static size_t +size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) { off_t max; diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h --- a/src/event/quic/ngx_event_quic_output.h +++ b/src/event/quic/ngx_event_quic_output.h @@ -34,5 +34,7 @@ ngx_int_t ngx_quic_send_ack_range(ngx_co ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, size_t min, ngx_quic_path_t *path); +size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, + size_t size); #endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ From arut at nginx.com Thu Nov 30 11:05:28 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:28 +0400 Subject: [PATCH 2 of 6] QUIC: fixed anti-amplification with explicit send In-Reply-To: References: Message-ID: <87290bcf25a6fc624487.1701342328@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1700650341 -14400 # Wed Nov 22 14:52:21 2023 +0400 # Node ID 87290bcf25a6fc62448722f5a72327a30fdf31d9 # Parent 088ee5449fdc67ee8ab983910d98599652683434 QUIC: fixed anti-amplification with explicit send. Previously, when using ngx_quic_frame_sendto() to explicitly send a packet with a single frame, anti-amplification limit was not properly enforced. Even when there was no quota left for the packet, it was sent anyway, but with no padding. Now the packet is not sent at all. This function is called to send PATH_CHALLENGE/PATH_RESPONSE, PMTUD and probe packets. For all these cases packet send is retried later in case the send was not successful. diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -872,6 +872,7 @@ ngx_quic_expire_path_mtu_discovery(ngx_c static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path) { + size_t mtu; ngx_int_t rc; ngx_uint_t log_error; ngx_quic_frame_t frame; @@ -895,7 +896,12 @@ ngx_quic_send_path_mtu_probe(ngx_connect log_error = c->log_error; c->log_error = NGX_ERROR_IGNORE_EMSGSIZE; + mtu = path->mtu; + path->mtu = path->mtud; + rc = ngx_quic_frame_sendto(c, &frame, path->mtud, path); + + path->mtu = mtu; c->log_error = log_error; if (rc == NGX_ERROR) { diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1181,7 +1181,7 @@ ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, size_t min, ngx_quic_path_t *path) { - size_t min_payload, pad; + size_t max, max_payload, min_payload, pad; ssize_t len, sent; ngx_str_t res; ngx_quic_header_t pkt; @@ -1194,15 +1194,25 @@ ngx_quic_frame_sendto(ngx_connection_t * qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, frame->level); + max = ngx_quic_path_limit(c, path, path->mtu); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic sendto %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + ngx_quic_init_packet(c, ctx, &pkt, path); - min = ngx_quic_path_limit(c, path, min); + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); - min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; - + /* RFC 9001, 5.4.2. Header Protection Sample */ pad = 4 - pkt.num_len; min_payload = ngx_max(min_payload, pad); + if (min_payload > max_payload) { + return NGX_AGAIN; + } + #if (NGX_DEBUG) frame->pnum = pkt.number; #endif @@ -1210,8 +1220,8 @@ ngx_quic_frame_sendto(ngx_connection_t * ngx_quic_log_frame(c->log, frame, 1); len = ngx_quic_create_frame(NULL, frame); - if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { - return NGX_ERROR; + if ((size_t) len > max_payload) { + return NGX_AGAIN; } len = ngx_quic_create_frame(src, frame); @@ -1258,8 +1268,6 @@ ngx_quic_path_limit(ngx_connection_t *c, max = (path->sent >= max) ? 0 : max - path->sent; if ((off_t) size > max) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path limit %uz - %O", size, max); return max; } } From arut at nginx.com Thu Nov 30 11:05:29 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:29 +0400 Subject: [PATCH 3 of 6] QUIC: ignore duplicate PATH_CHALLENGE frames In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1700650092 -14400 # Wed Nov 22 14:48:12 2023 +0400 # Node ID fcfe832f2590ad81fc74bc479e1b3ccf8c14683a # Parent 87290bcf25a6fc62448722f5a72327a30fdf31d9 QUIC: ignore duplicate PATH_CHALLENGE frames. According to RFC 9000, an endpoint SHOULD NOT send multiple PATH_CHALLENGE frames in a single packet. The change adds a check to enforce this claim to optimize server behavior. Previously each PATH_CHALLENGE always resulted in a single response datagram being sent to client. The effect of this was however limited by QUIC flood protection. Also, PATH_CHALLENGE is explicitly disabled in Initial and Handshake levels, see RFC 9000, Table 3. However, technically it may be sent by client in 0-RTT over a new path without actual migration, even though the migration itself is prohibited during handshake. This allows client to coalesce multiple 0-RTT packets each carrying a PATH_CHALLENGE and end up with multiple PATH_CHALLENGEs per datagram. This again leads to suboptimal behavior, see above. Since the purpose of sending PATH_CHALLENGE frames in 0-RTT is unclear, these frames are now only allowed in 1-RTT. For 0-RTT they are silently ignored. diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -40,6 +40,14 @@ ngx_quic_handle_path_challenge_frame(ngx ngx_quic_frame_t frame, *fp; ngx_quic_connection_t *qc; + if (pkt->level != ssl_encryption_application || pkt->path_challenged) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ignoring PATH_CHALLENGE"); + return NGX_OK; + } + + pkt->path_challenged = 1; + qc = ngx_quic_get_connection(c); ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -336,6 +336,7 @@ typedef struct { unsigned retried:1; unsigned first:1; unsigned rebound:1; + unsigned path_challenged:1; } ngx_quic_header_t; From arut at nginx.com Thu Nov 30 11:05:30 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:30 +0400 Subject: [PATCH 4 of 6] QUIC: congestion control in ngx_quic_frame_sendto() In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1701279689 -14400 # Wed Nov 29 21:41:29 2023 +0400 # Node ID f3632aaea9f59221fb77d88ccd9fb1aac114d5a3 # Parent fcfe832f2590ad81fc74bc479e1b3ccf8c14683a QUIC: congestion control in ngx_quic_frame_sendto(). Previously ngx_quic_frame_sendto() ignored congestion control and did not contribute to in_flight counter. Now congestion control window is checked unless ignore_congestion flag is set. Also, in_flight counter is incremented and the frame is stored in ctx->sent queue if it's ack-eliciting. This behavior is now similar to ngx_quic_output_packet(). diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -593,6 +593,7 @@ ngx_quic_resend_frames(ngx_connection_t break; case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_CHALLENGE: case NGX_QUIC_FT_PATH_RESPONSE: case NGX_QUIC_FT_CONNECTION_CLOSE: ngx_quic_free_frame(c, f); @@ -824,11 +825,11 @@ void ngx_quic_lost_handler(ngx_event_t * void ngx_quic_pto_handler(ngx_event_t *ev) { - ngx_uint_t i; + ngx_uint_t i, n; ngx_msec_t now; ngx_queue_t *q; ngx_connection_t *c; - ngx_quic_frame_t *f, frame; + ngx_quic_frame_t *f; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -865,16 +866,20 @@ ngx_quic_pto_handler(ngx_event_t *ev) "quic pto %s pto_count:%ui", ngx_quic_level_name(ctx->level), qc->pto_count); - ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + for (n = 0; n < 2; n++) { - frame.level = ctx->level; - frame.type = NGX_QUIC_FT_PING; + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + goto failed; + } - if (ngx_quic_frame_sendto(c, &frame, 0, qc->path) != NGX_OK - || ngx_quic_frame_sendto(c, &frame, 0, qc->path) != NGX_OK) - { - ngx_quic_close_connection(c, NGX_ERROR); - return; + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->ignore_congestion = 1; + + if (ngx_quic_frame_sendto(c, f, 0, qc->path) == NGX_ERROR) { + goto failed; + } } } @@ -883,6 +888,13 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_quic_set_lost_timer(c); ngx_quic_connstate_dbg(c); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); + return; } diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -37,7 +37,7 @@ ngx_quic_handle_path_challenge_frame(ngx ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) { size_t min; - ngx_quic_frame_t frame, *fp; + ngx_quic_frame_t *fp; ngx_quic_connection_t *qc; if (pkt->level != ssl_encryption_application || pkt->path_challenged) { @@ -50,11 +50,14 @@ ngx_quic_handle_path_challenge_frame(ngx qc = ngx_quic_get_connection(c); - ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } - frame.level = ssl_encryption_application; - frame.type = NGX_QUIC_FT_PATH_RESPONSE; - frame.u.path_response = *f; + fp->level = ssl_encryption_application; + fp->type = NGX_QUIC_FT_PATH_RESPONSE; + fp->u.path_response = *f; /* * RFC 9000, 8.2.2. Path Validation Responses @@ -73,7 +76,7 @@ ngx_quic_handle_path_challenge_frame(ngx min = (ngx_quic_path_limit(c, pkt->path, 1200) < 1200) ? 0 : 1200; - if (ngx_quic_frame_sendto(c, &frame, min, pkt->path) == NGX_ERROR) { + if (ngx_quic_frame_sendto(c, fp, min, pkt->path) == NGX_ERROR) { return NGX_ERROR; } @@ -546,22 +549,25 @@ ngx_quic_validate_path(ngx_connection_t static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) { - size_t min; - ngx_uint_t n; - ngx_quic_frame_t frame; + size_t min; + ngx_uint_t n; + ngx_quic_frame_t *frame; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic path seq:%uL send path_challenge tries:%ui", path->seqnum, path->tries); - ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); - - frame.level = ssl_encryption_application; - frame.type = NGX_QUIC_FT_PATH_CHALLENGE; - for (n = 0; n < 2; n++) { - ngx_memcpy(frame.u.path_challenge.data, path->challenge[n], 8); + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_PATH_CHALLENGE; + + ngx_memcpy(frame->u.path_challenge.data, path->challenge[n], 8); /* * RFC 9000, 8.2.1. Initiating Path Validation @@ -574,7 +580,7 @@ ngx_quic_send_path_challenge(ngx_connect min = (ngx_quic_path_limit(c, path, 1200) < 1200) ? 0 : 1200; - if (ngx_quic_frame_sendto(c, &frame, min, path) == NGX_ERROR) { + if (ngx_quic_frame_sendto(c, frame, min, path) == NGX_ERROR) { return NGX_ERROR; } } @@ -883,14 +889,17 @@ ngx_quic_send_path_mtu_probe(ngx_connect size_t mtu; ngx_int_t rc; ngx_uint_t log_error; - ngx_quic_frame_t frame; + ngx_quic_frame_t *frame; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } - frame.level = ssl_encryption_application; - frame.type = NGX_QUIC_FT_PING; + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_PING; qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); @@ -907,7 +916,7 @@ ngx_quic_send_path_mtu_probe(ngx_connect mtu = path->mtu; path->mtu = path->mtud; - rc = ngx_quic_frame_sendto(c, &frame, path->mtud, path); + rc = ngx_quic_frame_sendto(c, frame, path->mtud, path); path->mtu = mtu; c->log_error = log_error; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -844,7 +844,7 @@ ngx_quic_send_stateless_reset(ngx_connec ngx_int_t ngx_quic_send_cc(ngx_connection_t *c) { - ngx_quic_frame_t frame; + ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -860,22 +860,27 @@ ngx_quic_send_cc(ngx_connection_t *c) return NGX_OK; } - ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } - frame.level = qc->error_level; - frame.type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP - : NGX_QUIC_FT_CONNECTION_CLOSE; - frame.u.close.error_code = qc->error; - frame.u.close.frame_type = qc->error_ftype; + frame->level = qc->error_level; + frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = qc->error; + frame->u.close.frame_type = qc->error_ftype; if (qc->error_reason) { - frame.u.close.reason.len = ngx_strlen(qc->error_reason); - frame.u.close.reason.data = (u_char *) qc->error_reason; + frame->u.close.reason.len = ngx_strlen(qc->error_reason); + frame->u.close.reason.data = (u_char *) qc->error_reason; } + frame->ignore_congestion = 1; + qc->last_cc = ngx_current_msec; - return ngx_quic_frame_sendto(c, &frame, 0, qc->path); + return ngx_quic_frame_sendto(c, frame, 0, qc->path); } @@ -1184,22 +1189,32 @@ ngx_quic_frame_sendto(ngx_connection_t * size_t max, max_payload, min_payload, pad; ssize_t len, sent; ngx_str_t res; + ngx_msec_t now; ngx_quic_header_t pkt; ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; qc = ngx_quic_get_connection(c); + cg = &qc->congestion; ctx = ngx_quic_get_send_ctx(qc, frame->level); + now = ngx_current_msec; + max = ngx_quic_path_limit(c, path, path->mtu); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic sendto %s packet max:%uz min:%uz", ngx_quic_level_name(ctx->level), max, min); + if (cg->in_flight >= cg->window && !frame->ignore_congestion) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + ngx_quic_init_packet(c, ctx, &pkt, path); min_payload = ngx_quic_payload_size(&pkt, min); @@ -1210,6 +1225,7 @@ ngx_quic_frame_sendto(ngx_connection_t * min_payload = ngx_max(min_payload, pad); if (min_payload > max_payload) { + ngx_quic_free_frame(c, frame); return NGX_AGAIN; } @@ -1221,11 +1237,13 @@ ngx_quic_frame_sendto(ngx_connection_t * len = ngx_quic_create_frame(NULL, frame); if ((size_t) len > max_payload) { + ngx_quic_free_frame(c, frame); return NGX_AGAIN; } len = ngx_quic_create_frame(src, frame); if (len == -1) { + ngx_quic_free_frame(c, frame); return NGX_ERROR; } @@ -1242,18 +1260,45 @@ ngx_quic_frame_sendto(ngx_connection_t * ngx_quic_log_packet(c->log, &pkt); if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + ngx_quic_free_frame(c, frame); return NGX_ERROR; } + frame->pnum = ctx->pnum; + frame->first = now; + frame->last = now; + frame->plen = res.len; + ctx->pnum++; sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen); if (sent < 0) { + ngx_quic_free_frame(c, frame); return sent; } path->sent += sent; + if (frame->need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, &frame->queue); + + cg->in_flight += frame->plen; + + } else { + ngx_quic_free_frame(c, frame); + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); + + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -271,6 +271,7 @@ struct ngx_quic_frame_s { ssize_t len; unsigned need_ack:1; unsigned pkt_need_ack:1; + unsigned ignore_congestion:1; ngx_chain_t *data; union { From arut at nginx.com Thu Nov 30 11:05:31 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:31 +0400 Subject: [PATCH 5 of 6] QUIC: ngx_quic_frame_t time fields cleanup In-Reply-To: References: Message-ID: <4b7663d9146ce9baeb78.1701342331@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1701342186 -14400 # Thu Nov 30 15:03:06 2023 +0400 # Node ID 4b7663d9146ce9baeb78fb57c3fed7368f25dae9 # Parent f3632aaea9f59221fb77d88ccd9fb1aac114d5a3 QUIC: ngx_quic_frame_t time fields cleanup. The field "first" is removed. It's unused since 909b989ec088. The field "last" is renamed to "send_time". It holds frame send time. diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -265,16 +265,16 @@ ngx_quic_handle_ack_frame_range(ngx_conn } if (f->pnum == max) { - st->max_pn = f->last; + st->max_pn = f->send_time; } /* save earliest and latest send times of frames ack'ed */ - if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) { - st->oldest = f->last; + if (st->oldest == NGX_TIMER_INFINITE || f->send_time < st->oldest) { + st->oldest = f->send_time; } - if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) { - st->newest = f->last; + if (st->newest == NGX_TIMER_INFINITE || f->send_time > st->newest) { + st->newest = f->send_time; } ngx_queue_remove(&f->queue); @@ -329,7 +329,7 @@ ngx_quic_congestion_ack(ngx_connection_t cg->in_flight -= f->plen; - timer = f->last - cg->recovery_start; + timer = f->send_time - cg->recovery_start; if ((ngx_msec_int_t) timer <= 0) { ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -465,7 +465,7 @@ ngx_quic_detect_lost(ngx_connection_t *c break; } - wait = start->last + thr - now; + wait = start->send_time + thr - now; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", @@ -477,14 +477,14 @@ ngx_quic_detect_lost(ngx_connection_t *c break; } - if (start->last > qc->first_rtt) { + if (start->send_time > qc->first_rtt) { - if (oldest == NGX_TIMER_INFINITE || start->last < oldest) { - oldest = start->last; + if (oldest == NGX_TIMER_INFINITE || start->send_time < oldest) { + oldest = start->send_time; } - if (newest == NGX_TIMER_INFINITE || start->last > newest) { - newest = start->last; + if (newest == NGX_TIMER_INFINITE || start->send_time > newest) { + newest = start->send_time; } nlost++; @@ -672,7 +672,7 @@ ngx_quic_congestion_lost(ngx_connection_ cg->in_flight -= f->plen; f->plen = 0; - timer = f->last - cg->recovery_start; + timer = f->send_time - cg->recovery_start; if ((ngx_msec_int_t) timer <= 0) { ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -730,7 +730,8 @@ ngx_quic_set_lost_timer(ngx_connection_t if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { q = ngx_queue_head(&ctx->sent); f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); + w = (ngx_msec_int_t) + (f->send_time + ngx_quic_lost_threshold(qc) - now); if (f->pnum <= ctx->largest_ack) { if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { @@ -745,8 +746,8 @@ ngx_quic_set_lost_timer(ngx_connection_t q = ngx_queue_last(&ctx->sent); f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) - - now); + w = (ngx_msec_int_t) + (f->send_time + (ngx_quic_pto(c, ctx) << qc->pto_count) - now); if (w < 0) { w = 0; @@ -828,6 +829,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_uint_t i, n; ngx_msec_t now; ngx_queue_t *q; + ngx_msec_int_t w; ngx_connection_t *c; ngx_quic_frame_t *f; ngx_quic_send_ctx_t *ctx; @@ -849,6 +851,8 @@ ngx_quic_pto_handler(ngx_event_t *ev) q = ngx_queue_last(&ctx->sent); f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + (ngx_quic_pto(c, ctx) << qc->pto_count) - now); if (f->pnum <= ctx->largest_ack && ctx->largest_ack != NGX_QUIC_UNSET_PN) @@ -856,9 +860,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) continue; } - if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) - - now) > 0) - { + if (w > 0) { continue; } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -586,8 +586,7 @@ ngx_quic_output_packet(ngx_connection_t } f->pnum = ctx->pnum; - f->first = now; - f->last = now; + f->send_time = now; f->plen = 0; ngx_quic_log_frame(c->log, f, 1); @@ -1265,8 +1264,7 @@ ngx_quic_frame_sendto(ngx_connection_t * } frame->pnum = ctx->pnum; - frame->first = now; - frame->last = now; + frame->send_time = now; frame->plen = res.len; ctx->pnum++; diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -266,8 +266,7 @@ struct ngx_quic_frame_s { ngx_queue_t queue; uint64_t pnum; size_t plen; - ngx_msec_t first; - ngx_msec_t last; + ngx_msec_t send_time; ssize_t len; unsigned need_ack:1; unsigned pkt_need_ack:1; From arut at nginx.com Thu Nov 30 11:05:32 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 30 Nov 2023 15:05:32 +0400 Subject: [PATCH 6 of 6] QUIC: path revalidation after expansion failure In-Reply-To: References: Message-ID: <82fa5941af6fecb4fc7f.1701342332@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1701241101 -14400 # Wed Nov 29 10:58:21 2023 +0400 # Node ID 82fa5941af6fecb4fc7f0ac6308ae6c266d5e545 # Parent 4b7663d9146ce9baeb78fb57c3fed7368f25dae9 QUIC: path revalidation after expansion failure. As per RFC 9000, Section 8.2.1: When an endpoint is unable to expand the datagram size to 1200 bytes due to the anti-amplification limit, the path MTU will not be validated. To ensure that the path MTU is large enough, the endpoint MUST perform a second path validation by sending a PATH_CHALLENGE frame in a datagram of at least 1200 bytes. diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -111,7 +111,8 @@ struct ngx_quic_path_s { uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES]; ngx_str_t addr_text; u_char text[NGX_SOCKADDR_STRLEN]; - ngx_uint_t validated; /* unsigned validated:1; */ + unsigned validated:1; + unsigned mtu_unvalidated:1; }; diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -169,6 +169,7 @@ valid: path->mtu = prev->mtu; path->max_mtu = prev->max_mtu; + path->mtu_unvalidated = 0; } } @@ -182,6 +183,13 @@ valid: qc->congestion.recovery_start = ngx_current_msec; } + path->validated = 1; + + if (path->mtu_unvalidated) { + path->mtu_unvalidated = 0; + return ngx_quic_validate_path(c, path); + } + /* * RFC 9000, 9.3. Responding to Connection Migration * @@ -199,8 +207,6 @@ valid: ngx_quic_path_dbg(c, "is validated", path); - path->validated = 1; - ngx_quic_discover_path_mtu(c, path); return NGX_OK; @@ -578,7 +584,15 @@ ngx_quic_send_path_challenge(ngx_connect * sending a datagram of this size. */ - min = (ngx_quic_path_limit(c, path, 1200) < 1200) ? 0 : 1200; + if (path->mtu_unvalidated + || ngx_quic_path_limit(c, path, 1200) < 1200) + { + min = 0; + path->mtu_unvalidated = 1; + + } else { + min = 1200; + } if (ngx_quic_frame_sendto(c, frame, min, path) == NGX_ERROR) { return NGX_ERROR; From Johannes.Baiter at bsb-muenchen.de Thu Nov 30 11:09:43 2023 From: Johannes.Baiter at bsb-muenchen.de (Johannes Baiter) Date: Thu, 30 Nov 2023 12:09:43 +0100 Subject: [PATCH 1 of 6] QUIC: avoid partial expansion of PATH_CHALLENGE/PATH_RESPONSE (In Elternzeit bis 7. Januar 2024) In-Reply-To: <5A9812CD0200006BB0061052@gwia.bsb-muenchen.de> References: <15146F5A0200008CB0061052@gwia.bsb-muenchen.de> <5A9812CD0200006BB0061052@gwia.bsb-muenchen.de> Message-ID: <65686D77020000370001BC5C@gwia.bsb-muenchen.de> Sehr geehrte Damen und Herren, liebe Kolleginnen und Kollegen, vielen Dank für Ihre E-Mail. Dies ist eine automatisch erstelle Antwort. Ich befinde mich derzeit in Elternzeit und bin ab dem 7. Januar 2024 wieder im Dienst. Bitte wenden Sie sich in dringenden Fällen an marcus.bitzl at bsb-muenchen.de. -------------------------- Thank you for your email. This is an automatic reply. I'm currently on parental leave until Januar 7 2024. In urgent cases, please contact marcus.bitzl at bsb-muenchen.de. -------------------------- Mit freundlichen Grüßen Johannes Baiter Bayerische Staatsbibliothek Digitale Bibliothek/Münchener DigitalisierungsZentrum (MDZ) Ludwigstr. 16 D-80539 München Tel.: +49 89 28638 2970 eMail: johannes.baiter at bsb-muenchen.de