From pluknet at nginx.com Mon May 1 14:46:25 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 01 May 2023 18:46:25 +0400 Subject: [PATCH] Tests: HTTP/2 tests with error_page and return Message-ID: <90aaa942972884dcd67b.1682952385@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1682952238 -14400 # Mon May 01 18:43:58 2023 +0400 # Node ID 90aaa942972884dcd67b6744fde39a154fec5d13 # Parent 36a4563f7f005184547575f5ac4f22ef53a59c72 Tests: HTTP/2 tests with error_page and return. diff --git a/h2_error_page.t b/h2_error_page.t new file mode 100644 --- /dev/null +++ b/h2_error_page.t @@ -0,0 +1,88 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for HTTP/2 protocol with error_page directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(2) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + server_name localhost; + + lingering_close off; + + error_page 400 = /close; + + location / { } + + location /close { + return 444; + } + } +} + +EOF + +$t->run(); + +############################################################################### + +my ($sid, $frames, $frame); + +# tests for socket leak with "return 444" in error_page + +# ticket #274 + +my $s1 = Test::Nginx::HTTP2->new(); +$sid = $s1->new_stream({ headers => [ + { name => ':method', value => 'GET' }, + { name => ':path', value => '/' }, + { name => ':authority', value => 'localhost' }]}); +$frames = $s1->read(all => [{ type => 'RST_STREAM' }]); + +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +is($frame->{sid}, $sid, 'error 400 return 444 - missing header'); + +# ticket #2455 + +my $s2 = Test::Nginx::HTTP2->new(); +$sid = $s2->new_stream({ method => 'foo' }); +$frames = $s2->read(all => [{ type => 'RST_STREAM' }]); + +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +is($frame->{sid}, $sid, 'error 400 return 444 - invalid header'); + +$t->stop(); + +############################################################################### From pluknet at nginx.com Mon May 1 15:20:49 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 01 May 2023 15:20:49 +0000 Subject: [nginx] Variables: avoid possible buffer overrun with some "$sent_http_*". Message-ID: details: https://hg.nginx.org/nginx/rev/b71e69247483 branches: changeset: 8164:b71e69247483 user: Sergey Kandaurov date: Mon May 01 19:16:05 2023 +0400 description: Variables: avoid possible buffer overrun with some "$sent_http_*". The existing logic to evaluate multi header "$sent_http_*" variables, such as $sent_http_cache_control, as previously introduced in 1.23.0, doesn't take into account that one or more elements can be cleared, yet still present in a linked list, pointed to by the next field. Such elements don't contribute to the resulting variable length, an attempt to append a separator for them ends up in out of bounds write. This is not possible with standard modules, though at least one third party module is known to override multi header values this way, so it makes sense to harden the logic. The fix restores a generic boundary check. diffstat: src/http/ngx_http_variables.c | 6 ++++-- 1 files changed, 4 insertions(+), 2 deletions(-) diffs (30 lines): diff -r 77d5c662f3d9 -r b71e69247483 src/http/ngx_http_variables.c --- a/src/http/ngx_http_variables.c Tue Apr 18 06:28:46 2023 +0300 +++ b/src/http/ngx_http_variables.c Mon May 01 19:16:05 2023 +0400 @@ -828,7 +828,7 @@ ngx_http_variable_headers_internal(ngx_h ngx_http_variable_value_t *v, uintptr_t data, u_char sep) { size_t len; - u_char *p; + u_char *p, *end; ngx_table_elt_t *h, *th; h = *(ngx_table_elt_t **) ((char *) r + data); @@ -870,6 +870,8 @@ ngx_http_variable_headers_internal(ngx_h v->len = len; v->data = p; + end = p + len; + for (th = h; th; th = th->next) { if (th->hash == 0) { @@ -878,7 +880,7 @@ ngx_http_variable_headers_internal(ngx_h p = ngx_copy(p, th->value.data, th->value.len); - if (th->next == NULL) { + if (p == end) { break; } From pluknet at nginx.com Mon May 1 15:26:13 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 01 May 2023 19:26:13 +0400 Subject: [PATCH 0 of 2] [quic] error handling hardening Message-ID: Hi. This series fixes two most annoying bugs seen with intentionally broken malloc. From pluknet at nginx.com Mon May 1 15:26:14 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 01 May 2023 19:26:14 +0400 Subject: [PATCH 1 of 2] HTTP/3: fixed ngx_http_v3_init_session() error handling In-Reply-To: References: Message-ID: <8aa3363bc83d4354b314.1682954774@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1682954723 -14400 # Mon May 01 19:25:23 2023 +0400 # Branch quic # Node ID 8aa3363bc83d4354b3142e3972cce5c0ef523539 # Parent 9ea62b6250f225578f703da5e230853a7a84df7d HTTP/3: fixed ngx_http_v3_init_session() error handling. A QUIC connection is not usable yet at this early stage of spin up. diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -59,9 +59,6 @@ ngx_http_v3_init_session(ngx_connection_ failed: ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "failed to create http3 session"); return NGX_ERROR; } From pluknet at nginx.com Mon May 1 15:26:15 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 01 May 2023 19:26:15 +0400 Subject: [PATCH 2 of 2] QUIC: improved split frames error handling In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1682954724 -14400 # Mon May 01 19:25:24 2023 +0400 # Branch quic # Node ID b10aa30b15a802870eb23716ce3937a1085c4c98 # Parent 8aa3363bc83d4354b3142e3972cce5c0ef523539 QUIC: improved split frames error handling. Do not update frame data chain on ngx_quic_read_buffer() error. It may be used later as part of error handling, which envolves writing CONNECTION_CLOSE and pending frames, including the one with the corrupted chain pointer. Since ngx_quic_read_buffer() returns the original chain, there is no point in updating it, so this was simplified. diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -359,8 +359,7 @@ ngx_quic_split_frame(ngx_connection_t *c ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); qb.chain = f->data; - f->data = ngx_quic_read_buffer(c, &qb, of->length); - if (f->data == NGX_CHAIN_ERROR) { + if (ngx_quic_read_buffer(c, &qb, of->length) == NGX_CHAIN_ERROR) { return NGX_ERROR; } From pluknet at nginx.com Mon May 1 16:58:55 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 1 May 2023 20:58:55 +0400 Subject: [PATCH 3 of 3] QUIC: path MTU discovery In-Reply-To: <13d43a278510f131101c.1680015100@arut-laptop> References: <13d43a278510f131101c.1680015100@arut-laptop> Message-ID: <765817FB-D1C5-45AB-A20D-86E00A2CB4EE@nginx.com> > On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1679993500 -14400 > # Tue Mar 28 12:51:40 2023 +0400 > # Branch quic > # Node ID 13d43a278510f131101c7b19d87455a0171ebe2f > # Parent c686c97f4abd6e1ca9a2cc2324d5a24f3d035c58 > QUIC: path MTU discovery. > > MTU selection starts by probing the maximum allowed MTU first. After that, > binary search is used to find the path MTU. > > Maximum allowed MTU is calculated as the minimum of max_udp_payload for client > and server, and local interface MTU. > > diff --git a/auto/unix b/auto/unix > --- a/auto/unix > +++ b/auto/unix > @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ > . auto/feature > > > +# IP packet fragmentation flags > + > +ngx_feature="IP_DONTFRAG" > +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" > +ngx_feature_run=no > +ngx_feature_incs="#include > + #include " > +ngx_feature_path= > +ngx_feature_libs= > +ngx_feature_test="getsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" > +. auto/feature > + > + > +ngx_feature="IPV6_DONTFRAG" > +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" > +ngx_feature_run=no > +ngx_feature_incs="#include > + #include " > +ngx_feature_path= > +ngx_feature_libs= > +ngx_feature_test="getsockopt(0, IPPROTO_IPV6, IPV6_DONTFRAG, NULL, 0)" > +. auto/feature > + > + > +# Linux MTU flags > + > +ngx_feature="IP_PMTUDISC_DO" > +ngx_feature_name="NGX_HAVE_IP_PMTUDISC_DO" > +ngx_feature_run=no > +ngx_feature_incs="#include > + #include " > +ngx_feature_path= > +ngx_feature_libs= > +ngx_feature_test="getsockopt(0, IPPROTO_IP, IP_PMTUDISC_DO, NULL, 0)" > +. auto/feature > + > + > +ngx_feature="IPV6_PMTUDISC_DO" > +ngx_feature_name="NGX_HAVE_IPV6_PMTUDISC_DO" > +ngx_feature_run=no > +ngx_feature_incs="#include > + #include " > +ngx_feature_path= > +ngx_feature_libs= > +ngx_feature_test="getsockopt(0, IPPROTO_IPV6, IPV6_PMTUDISC_DO, NULL, 0)" > +. auto/feature > + > + > ngx_feature="TCP_DEFER_ACCEPT" > ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" > ngx_feature_run=no > @@ -920,6 +968,19 @@ ngx_feature_test="int i = FIONREAD; prin > . auto/feature > > > +ngx_feature="ioctl(SIOCGIFMTU)" > +ngx_feature_name="NGX_HAVE_SIOCGIFMTU" > +ngx_feature_run=no > +ngx_feature_incs="#include > + #include > + #include " > +ngx_feature_path= > +ngx_feature_libs= > +ngx_feature_test="int i = SIOCGIFMTU; struct ifreq ifr; > + ifr.ifr_name[0] = 'e'; printf(\"%d\", i)" > +. auto/feature > + > + > ngx_feature="struct tm.tm_gmtoff" > ngx_feature_name="NGX_HAVE_GMTOFF" > ngx_feature_run=no > @@ -1002,3 +1063,17 @@ ngx_feature_test='struct addrinfo *res; > if (getaddrinfo("localhost", NULL, NULL, &res) != 0) return 1; > freeaddrinfo(res)' > . auto/feature > + > + > +ngx_feature="getifaddrs()" > +ngx_feature_name="NGX_HAVE_GETIFADDRS" > +ngx_feature_run=no > +ngx_feature_incs="#include > + #include > + #include " > +ngx_feature_path= > +ngx_feature_libs= > +ngx_feature_test='struct ifaddrs *ifaddr; > + if (getifaddrs(&ifaddr) != 0) return 1; > + freeifaddrs(ifaddr)' > +. auto/feature > diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c > --- a/src/core/ngx_connection.c > +++ b/src/core/ngx_connection.c > @@ -1010,6 +1010,74 @@ ngx_configure_listening_sockets(ngx_cycl > } > > #endif > + > +#if (NGX_HAVE_IP_PMTUDISC_DO) > + > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { > + value = 1; > + > + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_PMTUDISC_DO, > + (const void *) &value, sizeof(int)) > + == -1) > + { > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > + "setsockopt(IP_PMTUDISC_DO) " > + "for %V failed, ignored", > + &ls[i].addr_text); > + } > + } > + > +#elif (NGX_HAVE_IP_DONTFRAG) > + > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { > + value = 1; > + > + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, > + (const void *) &value, sizeof(int)) > + == -1) > + { > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > + "setsockopt(IP_DONTFRAG) " > + "for %V failed, ignored", > + &ls[i].addr_text); > + } > + } > + > +#endif > + > +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_PMTUDISC_DO) > + > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { > + value = 1; > + > + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_PMTUDISC_DO, > + (const void *) &value, sizeof(int)) > + == -1) > + { > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > + "setsockopt(IPV6_PMTUDISC_DO) " > + "for %V failed, ignored", > + &ls[i].addr_text); > + } > + } > + > +#elif (NGX_HAVE_INET6 && NGX_HAVE_IPV6_DONTFRAG) > + > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { > + value = 1; > + > + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, > + (const void *) &value, sizeof(int)) > + == -1) > + { > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > + "setsockopt(IPV6_DONTFRAG) " > + "for %V failed, ignored", > + &ls[i].addr_text); > + } > + } > + > +#endif > } > > return; > @@ -1507,6 +1575,10 @@ ngx_connection_error(ngx_connection_t *c > } > #endif > > + if (err == NGX_EMSGSIZE && c->log_error == NGX_ERROR_IGNORE_EMSGSIZE) { > + return 0; > + } > + > if (err == 0 > || err == NGX_ECONNRESET > #if (NGX_WIN32) > @@ -1524,6 +1596,7 @@ ngx_connection_error(ngx_connection_t *c > { > switch (c->log_error) { > > + case NGX_ERROR_IGNORE_EMSGSIZE: > case NGX_ERROR_IGNORE_EINVAL: > case NGX_ERROR_IGNORE_ECONNRESET: > case NGX_ERROR_INFO: > diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h > --- a/src/core/ngx_connection.h > +++ b/src/core/ngx_connection.h > @@ -97,7 +97,8 @@ typedef enum { > NGX_ERROR_ERR, > NGX_ERROR_INFO, > NGX_ERROR_IGNORE_ECONNRESET, > - NGX_ERROR_IGNORE_EINVAL > + NGX_ERROR_IGNORE_EINVAL, > + NGX_ERROR_IGNORE_EMSGSIZE > } ngx_connection_log_error_e; > > I'd move the dontfrag part to a separate change for clarity. It can be seen as a foundation for succeeding PLPMTUD work not strictly related to it. (Further, PLPMTUD is an optional feature, while dontfrag is a MUST per RFC 9000, section 14.) > 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 > @@ -10,8 +10,17 @@ > #include > > > +#define NGX_QUIC_UDP4_MAX_PACKET 65535 > +#define NGX_QUIC_UDP4_HEADER_SIZE 28 > + > +#define NGX_QUIC_UDP6_MAX_PAYLOAD 65535 > +#define NGX_QUIC_UDP6_HEADER_SIZE 48 > + > + > static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, > ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); > +static ssize_t ngx_quic_get_local_mtu(ngx_connection_t *c, > + struct sockaddr *sockaddr); > static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, > ngx_quic_header_t *pkt); > static void ngx_quic_input_handler(ngx_event_t *rev); > @@ -149,11 +158,6 @@ ngx_quic_apply_transport_params(ngx_conn > ngx_log_error(NGX_LOG_INFO, c->log, 0, > "quic maximum packet size is invalid"); > return NGX_ERROR; > - > - } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) { > - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); > - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, > - "quic client maximum packet size truncated"); > } > > if (ctp->active_connection_id_limit < 2) { > @@ -228,6 +232,7 @@ static ngx_quic_connection_t * > ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, > ngx_quic_header_t *pkt) > { > + ssize_t mtu; > ngx_uint_t i; > ngx_quic_tp_t *ctp; > ngx_quic_connection_t *qc; > @@ -297,7 +302,7 @@ ngx_quic_new_connection(ngx_connection_t > ctp = &qc->ctp; > > /* defaults to be used before actual client parameters are received */ > - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); > + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; > ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; > ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; > ctp->active_connection_id_limit = 2; > @@ -317,6 +322,18 @@ ngx_quic_new_connection(ngx_connection_t > qc->congestion.ssthresh = (size_t) -1; > qc->congestion.recovery_start = ngx_current_msec; > > + qc->max_mtu = ngx_min(qc->tp.max_udp_payload_size, > + qc->ctp.max_udp_payload_size); > + max_udp_payload_size is not negotiable, it is a property of the endpoint and not the path, because of possible asymmetric routes. > + mtu = ngx_quic_get_local_mtu(c, c->local_sockaddr); > + if (mtu == NGX_ERROR) { > + return NULL; > + } > + > + if (mtu > 0 && (size_t) mtu < qc->max_mtu) { It makes sense to ensure UDP is of at least 1200 bytes. The checks inside ngx_quic_get_local_mtu() make it possible to ignore link MTU below 1200, probably it should behave stricter like error log at appropriate level or discarding connection state. > + qc->max_mtu = mtu; > + } > + > if (pkt->validated && pkt->retried) { > qc->tp.retry_scid.len = pkt->dcid.len; > qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); > @@ -347,6 +364,90 @@ ngx_quic_new_connection(ngx_connection_t > } > > > +static ssize_t > +ngx_quic_get_local_mtu(ngx_connection_t *c, struct sockaddr *sockaddr) > +{ > +#if (NGX_HAVE_GETIFADDRS && NGX_HAVE_SIOCGIFMTU) > + > + size_t mtu; > + struct ifreq ifr; > + struct ifaddrs *ifaddrs, *ifa; > + > + if (sockaddr->sa_family != AF_INET > +#if (NGX_HAVE_INET6) > + && sockaddr->sa_family != AF_INET6 > +#endif > + ) > + { > + return NGX_DECLINED; > + } > + > + if (getifaddrs(&ifaddrs) == -1) { > + ngx_log_error(NGX_LOG_INFO, c->log, 0, "getifaddrs() failed"); > + return NGX_ERROR; > + } > + > + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { > + if (ifa->ifa_addr == NULL) { > + continue; > + } > + > + if (ngx_cmp_sockaddr(sockaddr, 0, ifa->ifa_addr, 0, 0) != NGX_OK) { > + continue; > + } > + > + ngx_memzero(&ifr, sizeof(struct ifreq)); > + strcpy(ifr.ifr_name, ifa->ifa_name); > + > + freeifaddrs(ifaddrs); > + > + if (ioctl(c->fd, SIOCGIFMTU, &ifr)) { > + ngx_log_error(NGX_LOG_INFO, c->log, 0, "ioctl(SIOCGIFMTU) failed"); > + return NGX_ERROR; > + } > + > + mtu = ifr.ifr_mtu; > + > + if (sockaddr->sa_family == AF_INET) { > + if (mtu > NGX_QUIC_UDP4_MAX_PACKET) { > + mtu = NGX_QUIC_UDP4_MAX_PACKET; > + } > + > + if (mtu <= NGX_QUIC_UDP4_HEADER_SIZE) { > + return NGX_DECLINED; > + } > + > + mtu -= NGX_QUIC_UDP4_HEADER_SIZE; > + > +#if (NGX_HAVE_INET6) > + } else { /* sockaddr->sa_family == AF_INET6 */ > + > + if (mtu <= NGX_QUIC_UDP6_HEADER_SIZE) { > + return NGX_DECLINED; > + } > + > + mtu -= NGX_QUIC_UDP6_HEADER_SIZE; > + > + if (mtu > NGX_QUIC_UDP6_MAX_PAYLOAD) { > + mtu = NGX_QUIC_UDP6_MAX_PAYLOAD; > + } > +#endif > + } > + It makes sense to further limit link MTU to system constraints. BSD is known to have a system limitation for a maximum outgoing UDP datagram size set for some reason. In all known distributions which derive from 4.3BSD-Reno, it defaults to 9216. For that, we can query the limit using sysctl. Limited, this reduces the number of MTU probes. In particular on lo0, before the change: - 16356 + 8778 - 12567 - 10672 - 9725 - 9251 + 9014 Legend: '-' is rejected with EMSGSIZE, '+' sent to network After the change, it is the only successful probe of maxdgram. On real networks there will be different results, but the general pattern can be traced. Aside from that, I noticed that we have split FreeBSD and Darwin tests/sources but didn't accommodate for that. Below to address this. The first one or two patches of three could go directly to the default branch if approved, the last one set upper bound for maxdgram where appropriate. # HG changeset patch # User Sergey Kandaurov # Date 1682957319 -14400 # Mon May 01 20:08:39 2023 +0400 # Branch quic # Node ID b4d35da933cc3df9a098feb6d06957b19e228c39 # Parent cc5d2e648dd4359d77c28120e6e02f9b5842024e Fixed Darwin support for the "net.inet.tcp.sendspace" sysctl. After Darwin support was split in separate files in 345a014436d4 (0.7.7), sysctl variables received a separate prefix, but common sources were not updated. In particular, the "net.inet.tcp.sendspace" sysctl value wasn't checked as a limit for the send_lowat directive and friends. The change unifies a prefix for the "sendspace" variable in both FreeBSD and Darwin. Other extern variables aren't touched: their usage is either limited to os-specific source files or they aren't used at all. diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -3905,14 +3905,14 @@ ngx_http_fastcgi_cache_key(ngx_conf_t *c static char * ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, void *data) { -#if (NGX_FREEBSD) +#if (NGX_FREEBSD || NGX_DARWIN) ssize_t *np = data; - if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { + if ((u_long) *np >= ngx_net_inet_tcp_sendspace) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"fastcgi_send_lowat\" must be less than %d " "(sysctl net.inet.tcp.sendspace)", - ngx_freebsd_net_inet_tcp_sendspace); + ngx_net_inet_tcp_sendspace); return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -4890,14 +4890,14 @@ ngx_http_proxy_ssl_password_file(ngx_con static char * ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data) { -#if (NGX_FREEBSD) +#if (NGX_FREEBSD || NGX_DARWIN) ssize_t *np = data; - if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { + if ((u_long) *np >= ngx_net_inet_tcp_sendspace) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_send_lowat\" must be less than %d " "(sysctl net.inet.tcp.sendspace)", - ngx_freebsd_net_inet_tcp_sendspace); + ngx_net_inet_tcp_sendspace); return NGX_CONF_ERROR; } diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -5288,14 +5288,14 @@ ngx_http_disable_symlinks(ngx_conf_t *cf static char * ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data) { -#if (NGX_FREEBSD) +#if (NGX_FREEBSD || NGX_DARWIN) ssize_t *np = data; - if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { + if ((u_long) *np >= ngx_net_inet_tcp_sendspace) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"send_lowat\" must be less than %d " "(sysctl net.inet.tcp.sendspace)", - ngx_freebsd_net_inet_tcp_sendspace); + ngx_net_inet_tcp_sendspace); return NGX_CONF_ERROR; } diff --git a/src/os/unix/ngx_darwin.h b/src/os/unix/ngx_darwin.h --- a/src/os/unix/ngx_darwin.h +++ b/src/os/unix/ngx_darwin.h @@ -15,7 +15,7 @@ ngx_chain_t *ngx_darwin_sendfile_chain(n extern int ngx_darwin_kern_osreldate; extern int ngx_darwin_hw_ncpu; -extern u_long ngx_darwin_net_inet_tcp_sendspace; +extern u_long ngx_net_inet_tcp_sendspace; extern ngx_uint_t ngx_debug_malloc; diff --git a/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c --- a/src/os/unix/ngx_darwin_init.c +++ b/src/os/unix/ngx_darwin_init.c @@ -13,7 +13,7 @@ char ngx_darwin_kern_ostype[16]; char ngx_darwin_kern_osrelease[128]; int ngx_darwin_hw_ncpu; int ngx_darwin_kern_ipc_somaxconn; -u_long ngx_darwin_net_inet_tcp_sendspace; +u_long ngx_net_inet_tcp_sendspace; ngx_uint_t ngx_debug_malloc; @@ -49,8 +49,8 @@ sysctl_t sysctls[] = { sizeof(ngx_darwin_hw_ncpu), 0 }, { "net.inet.tcp.sendspace", - &ngx_darwin_net_inet_tcp_sendspace, - sizeof(ngx_darwin_net_inet_tcp_sendspace), 0 }, + &ngx_net_inet_tcp_sendspace, + sizeof(ngx_net_inet_tcp_sendspace), 0 }, { "kern.ipc.somaxconn", &ngx_darwin_kern_ipc_somaxconn, diff --git a/src/os/unix/ngx_freebsd.h b/src/os/unix/ngx_freebsd.h --- a/src/os/unix/ngx_freebsd.h +++ b/src/os/unix/ngx_freebsd.h @@ -15,7 +15,7 @@ ngx_chain_t *ngx_freebsd_sendfile_chain( extern int ngx_freebsd_kern_osreldate; extern int ngx_freebsd_hw_ncpu; -extern u_long ngx_freebsd_net_inet_tcp_sendspace; +extern u_long ngx_net_inet_tcp_sendspace; extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug; extern ngx_uint_t ngx_freebsd_use_tcp_nopush; diff --git a/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c --- a/src/os/unix/ngx_freebsd_init.c +++ b/src/os/unix/ngx_freebsd_init.c @@ -15,7 +15,7 @@ char ngx_freebsd_kern_osrelease[128]; int ngx_freebsd_kern_osreldate; int ngx_freebsd_hw_ncpu; int ngx_freebsd_kern_ipc_somaxconn; -u_long ngx_freebsd_net_inet_tcp_sendspace; +u_long ngx_net_inet_tcp_sendspace; /* FreeBSD 4.9 */ int ngx_freebsd_machdep_hlt_logical_cpus; @@ -62,8 +62,8 @@ sysctl_t sysctls[] = { sizeof(ngx_freebsd_machdep_hlt_logical_cpus), 0 }, { "net.inet.tcp.sendspace", - &ngx_freebsd_net_inet_tcp_sendspace, - sizeof(ngx_freebsd_net_inet_tcp_sendspace), 0 }, + &ngx_net_inet_tcp_sendspace, + sizeof(ngx_net_inet_tcp_sendspace), 0 }, { "kern.ipc.somaxconn", &ngx_freebsd_kern_ipc_somaxconn, # HG changeset patch # User Sergey Kandaurov # Date 1682957408 -14400 # Mon May 01 20:10:08 2023 +0400 # Branch quic # Node ID 976e2e40f0f58a5bbcd89b76e24909be0c8d337d # Parent b4d35da933cc3df9a098feb6d06957b19e228c39 Introduced the "net.inet.udp.maxdgram" sysctl variable. diff --git a/src/os/unix/ngx_darwin.h b/src/os/unix/ngx_darwin.h --- a/src/os/unix/ngx_darwin.h +++ b/src/os/unix/ngx_darwin.h @@ -16,6 +16,7 @@ ngx_chain_t *ngx_darwin_sendfile_chain(n extern int ngx_darwin_kern_osreldate; extern int ngx_darwin_hw_ncpu; extern u_long ngx_net_inet_tcp_sendspace; +extern u_long ngx_net_inet_udp_maxdgram; extern ngx_uint_t ngx_debug_malloc; diff --git a/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c --- a/src/os/unix/ngx_darwin_init.c +++ b/src/os/unix/ngx_darwin_init.c @@ -14,6 +14,7 @@ char ngx_darwin_kern_osrelease[128]; int ngx_darwin_hw_ncpu; int ngx_darwin_kern_ipc_somaxconn; u_long ngx_net_inet_tcp_sendspace; +u_long ngx_net_inet_udp_maxdgram; ngx_uint_t ngx_debug_malloc; @@ -52,6 +53,10 @@ sysctl_t sysctls[] = { &ngx_net_inet_tcp_sendspace, sizeof(ngx_net_inet_tcp_sendspace), 0 }, + { "net.inet.udp.maxdgram", + &ngx_net_inet_udp_maxdgram, + sizeof(ngx_net_inet_udp_maxdgram), 0 }, + { "kern.ipc.somaxconn", &ngx_darwin_kern_ipc_somaxconn, sizeof(ngx_darwin_kern_ipc_somaxconn), 0 }, diff --git a/src/os/unix/ngx_freebsd.h b/src/os/unix/ngx_freebsd.h --- a/src/os/unix/ngx_freebsd.h +++ b/src/os/unix/ngx_freebsd.h @@ -16,6 +16,7 @@ ngx_chain_t *ngx_freebsd_sendfile_chain( extern int ngx_freebsd_kern_osreldate; extern int ngx_freebsd_hw_ncpu; extern u_long ngx_net_inet_tcp_sendspace; +extern u_long ngx_net_inet_udp_maxdgram; extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug; extern ngx_uint_t ngx_freebsd_use_tcp_nopush; diff --git a/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c --- a/src/os/unix/ngx_freebsd_init.c +++ b/src/os/unix/ngx_freebsd_init.c @@ -16,6 +16,7 @@ int ngx_freebsd_kern_osreldate; int ngx_freebsd_hw_ncpu; int ngx_freebsd_kern_ipc_somaxconn; u_long ngx_net_inet_tcp_sendspace; +u_long ngx_net_inet_udp_maxdgram; /* FreeBSD 4.9 */ int ngx_freebsd_machdep_hlt_logical_cpus; @@ -65,6 +66,10 @@ sysctl_t sysctls[] = { &ngx_net_inet_tcp_sendspace, sizeof(ngx_net_inet_tcp_sendspace), 0 }, + { "net.inet.udp.maxdgram", + &ngx_net_inet_udp_maxdgram, + sizeof(ngx_net_inet_udp_maxdgram), 0 }, + { "kern.ipc.somaxconn", &ngx_freebsd_kern_ipc_somaxconn, sizeof(ngx_freebsd_kern_ipc_somaxconn), 0 }, # HG changeset patch # User Sergey Kandaurov # Date 1682959469 -14400 # Mon May 01 20:44:29 2023 +0400 # Branch quic # Node ID 4f32cf8fe9241fe002701d3bf2dd38ea358c0b5d # Parent 976e2e40f0f58a5bbcd89b76e24909be0c8d337d QUIC: limited link MTU upper bound to maxdgram. 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 @@ -434,6 +434,10 @@ ngx_quic_get_local_mtu(ngx_connection_t #endif } +#if (NGX_FREEBSD || NGX_DARWIN) + mtu = ngx_min(mtu, ngx_net_inet_udp_maxdgram); +#endif + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic local mtu:%uz", mtu); > + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic local mtu:%uz", mtu); > + > + return mtu; > + } > + > + freeifaddrs(ifaddrs); > + > +#endif > + > + return NGX_DECLINED; > +} > + > + > static ngx_int_t > ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) > { > 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 > @@ -229,6 +229,12 @@ ngx_quic_handle_ack_frame_range(ngx_conn > > qc = ngx_quic_get_connection(c); > > + if (ctx->level == ssl_encryption_application) { > + if (ngx_quic_handle_path_mtu_ack(c, qc->path, min, max) != NGX_OK) { > + return NGX_ERROR; > + } > + } > + > st->max_pn = NGX_TIMER_INFINITE; > found = 0; > > 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 > @@ -89,14 +89,21 @@ struct ngx_quic_path_s { > ngx_sockaddr_t sa; > socklen_t socklen; > ngx_quic_client_id_t *cid; > - ngx_msec_t expires; > - ngx_uint_t tries; > + ngx_msec_t valid_expires; > + ngx_msec_t mtu_expires; > + ngx_uint_t valid_tries; > + ngx_uint_t mtu_tries; > + ngx_uint_t mtu_steps; > ngx_uint_t tag; > + size_t mtu; > + size_t mtud; > + size_t max_mtu; > off_t sent; > off_t received; > u_char challenge1[8]; > u_char challenge2[8]; > uint64_t seqnum; > + uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES]; > ngx_str_t addr_text; > u_char text[NGX_SOCKADDR_STRLEN]; > unsigned validated:1; > @@ -206,6 +213,8 @@ struct ngx_quic_connection_s { > uint64_t server_seqnum; > uint64_t path_seqnum; > > + size_t max_mtu; > + > ngx_quic_tp_t tp; > ngx_quic_tp_t ctp; > > 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 > @@ -10,6 +10,10 @@ > #include > > > +#define NGX_QUIC_MAX_MTU_STEPS 7 > +#define NGX_QUIC_MTU_PRECISION 4 > + > + For the record, rebased on top off my previous changes sent previously for the first patch (this doesn't include series for maxdgram). # HG changeset patch # User Roman Arutyunyan # Date 1679993500 -14400 # Tue Mar 28 12:51:40 2023 +0400 # Branch quic # Node ID cc5d2e648dd4359d77c28120e6e02f9b5842024e # Parent 81c439faaa38dc9dd5c3af5e5b1dc0e3c36f6367 QUIC: path MTU discovery. MTU selection starts by probing the maximum allowed MTU first. After that, binary search is used to find the path MTU. Maximum allowed MTU is calculated as the minimum of max_udp_payload for client and server, and local interface MTU. diff --git a/auto/unix b/auto/unix --- a/auto/unix +++ b/auto/unix @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ . auto/feature +# IP packet fragmentation flags + +ngx_feature="IP_DONTFRAG" +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="getsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_DONTFRAG" +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="getsockopt(0, IPPROTO_IPV6, IPV6_DONTFRAG, NULL, 0)" +. auto/feature + + +# Linux MTU flags + +ngx_feature="IP_PMTUDISC_DO" +ngx_feature_name="NGX_HAVE_IP_PMTUDISC_DO" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="getsockopt(0, IPPROTO_IP, IP_PMTUDISC_DO, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_PMTUDISC_DO" +ngx_feature_name="NGX_HAVE_IPV6_PMTUDISC_DO" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="getsockopt(0, IPPROTO_IPV6, IPV6_PMTUDISC_DO, NULL, 0)" +. auto/feature + + ngx_feature="TCP_DEFER_ACCEPT" ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" ngx_feature_run=no @@ -920,6 +968,19 @@ ngx_feature_test="int i = FIONREAD; prin . auto/feature +ngx_feature="ioctl(SIOCGIFMTU)" +ngx_feature_name="NGX_HAVE_SIOCGIFMTU" +ngx_feature_run=no +ngx_feature_incs="#include + #include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="int i = SIOCGIFMTU; struct ifreq ifr; + ifr.ifr_name[0] = 'e'; printf(\"%d\", i)" +. auto/feature + + ngx_feature="struct tm.tm_gmtoff" ngx_feature_name="NGX_HAVE_GMTOFF" ngx_feature_run=no @@ -1002,3 +1063,17 @@ ngx_feature_test='struct addrinfo *res; if (getaddrinfo("localhost", NULL, NULL, &res) != 0) return 1; freeaddrinfo(res)' . auto/feature + + +ngx_feature="getifaddrs()" +ngx_feature_name="NGX_HAVE_GETIFADDRS" +ngx_feature_run=no +ngx_feature_incs="#include + #include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test='struct ifaddrs *ifaddr; + if (getifaddrs(&ifaddr) != 0) return 1; + freeifaddrs(ifaddr)' +. auto/feature diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1010,6 +1010,74 @@ ngx_configure_listening_sockets(ngx_cycl } #endif + +#if (NGX_HAVE_IP_PMTUDISC_DO) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_PMTUDISC_DO, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_PMTUDISC_DO) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_PMTUDISC_DO) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_PMTUDISC_DO, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_PMTUDISC_DO) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_INET6 && NGX_HAVE_IPV6_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif } return; @@ -1507,6 +1575,10 @@ ngx_connection_error(ngx_connection_t *c } #endif + if (err == NGX_EMSGSIZE && c->log_error == NGX_ERROR_IGNORE_EMSGSIZE) { + return 0; + } + if (err == 0 || err == NGX_ECONNRESET #if (NGX_WIN32) @@ -1524,6 +1596,7 @@ ngx_connection_error(ngx_connection_t *c { switch (c->log_error) { + case NGX_ERROR_IGNORE_EMSGSIZE: case NGX_ERROR_IGNORE_EINVAL: case NGX_ERROR_IGNORE_ECONNRESET: case NGX_ERROR_INFO: diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -97,7 +97,8 @@ typedef enum { NGX_ERROR_ERR, NGX_ERROR_INFO, NGX_ERROR_IGNORE_ECONNRESET, - NGX_ERROR_IGNORE_EINVAL + NGX_ERROR_IGNORE_EINVAL, + NGX_ERROR_IGNORE_EMSGSIZE } ngx_connection_log_error_e; 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 @@ -10,8 +10,17 @@ #include +#define NGX_QUIC_UDP4_MAX_PACKET 65535 +#define NGX_QUIC_UDP4_HEADER_SIZE 28 + +#define NGX_QUIC_UDP6_MAX_PAYLOAD 65535 +#define NGX_QUIC_UDP6_HEADER_SIZE 48 + + static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ssize_t ngx_quic_get_local_mtu(ngx_connection_t *c, + struct sockaddr *sockaddr); static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -149,11 +158,6 @@ ngx_quic_apply_transport_params(ngx_conn ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic maximum packet size is invalid"); return NGX_ERROR; - - } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) { - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic client maximum packet size truncated"); } if (ctp->active_connection_id_limit < 2) { @@ -228,6 +232,7 @@ static ngx_quic_connection_t * ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { + ssize_t mtu; ngx_uint_t i; ngx_quic_tp_t *ctp; ngx_quic_connection_t *qc; @@ -297,7 +302,7 @@ ngx_quic_new_connection(ngx_connection_t ctp = &qc->ctp; /* defaults to be used before actual client parameters are received */ - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; ctp->active_connection_id_limit = 2; @@ -317,6 +322,18 @@ ngx_quic_new_connection(ngx_connection_t qc->congestion.ssthresh = (size_t) -1; qc->congestion.recovery_start = ngx_current_msec; + qc->max_mtu = ngx_min(qc->tp.max_udp_payload_size, + qc->ctp.max_udp_payload_size); + + mtu = ngx_quic_get_local_mtu(c, c->local_sockaddr); + if (mtu == NGX_ERROR) { + return NULL; + } + + if (mtu > 0 && (size_t) mtu < qc->max_mtu) { + qc->max_mtu = mtu; + } + if (pkt->validated && pkt->retried) { qc->tp.retry_scid.len = pkt->dcid.len; qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); @@ -347,6 +364,90 @@ ngx_quic_new_connection(ngx_connection_t } +static ssize_t +ngx_quic_get_local_mtu(ngx_connection_t *c, struct sockaddr *sockaddr) +{ +#if (NGX_HAVE_GETIFADDRS && NGX_HAVE_SIOCGIFMTU) + + size_t mtu; + struct ifreq ifr; + struct ifaddrs *ifaddrs, *ifa; + + if (sockaddr->sa_family != AF_INET +#if (NGX_HAVE_INET6) + && sockaddr->sa_family != AF_INET6 +#endif + ) + { + return NGX_DECLINED; + } + + if (getifaddrs(&ifaddrs) == -1) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "getifaddrs() failed"); + return NGX_ERROR; + } + + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) { + continue; + } + + if (ngx_cmp_sockaddr(sockaddr, 0, ifa->ifa_addr, 0, 0) != NGX_OK) { + continue; + } + + ngx_memzero(&ifr, sizeof(struct ifreq)); + strcpy(ifr.ifr_name, ifa->ifa_name); + + freeifaddrs(ifaddrs); + + if (ioctl(c->fd, SIOCGIFMTU, &ifr)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "ioctl(SIOCGIFMTU) failed"); + return NGX_ERROR; + } + + mtu = ifr.ifr_mtu; + + if (sockaddr->sa_family == AF_INET) { + if (mtu > NGX_QUIC_UDP4_MAX_PACKET) { + mtu = NGX_QUIC_UDP4_MAX_PACKET; + } + + if (mtu <= NGX_QUIC_UDP4_HEADER_SIZE) { + return NGX_DECLINED; + } + + mtu -= NGX_QUIC_UDP4_HEADER_SIZE; + +#if (NGX_HAVE_INET6) + } else { /* sockaddr->sa_family == AF_INET6 */ + + if (mtu <= NGX_QUIC_UDP6_HEADER_SIZE) { + return NGX_DECLINED; + } + + mtu -= NGX_QUIC_UDP6_HEADER_SIZE; + + if (mtu > NGX_QUIC_UDP6_MAX_PAYLOAD) { + mtu = NGX_QUIC_UDP6_MAX_PAYLOAD; + } +#endif + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic local mtu:%uz", mtu); + + return mtu; + } + + freeifaddrs(ifaddrs); + +#endif + + return NGX_DECLINED; +} + + static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { 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 @@ -229,6 +229,12 @@ ngx_quic_handle_ack_frame_range(ngx_conn qc = ngx_quic_get_connection(c); + if (ctx->level == ssl_encryption_application) { + if (ngx_quic_handle_path_mtu_ack(c, qc->path, min, max) != NGX_OK) { + return NGX_ERROR; + } + } + st->max_pn = NGX_TIMER_INFINITE; found = 0; 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 @@ -89,14 +89,21 @@ struct ngx_quic_path_s { ngx_sockaddr_t sa; socklen_t socklen; ngx_quic_client_id_t *cid; - ngx_msec_t expires; - ngx_uint_t tries; + ngx_msec_t valid_expires; + ngx_msec_t mtu_expires; + ngx_uint_t valid_tries; + ngx_uint_t mtu_tries; + ngx_uint_t mtu_steps; ngx_uint_t tag; + size_t mtu; + size_t mtud; + size_t max_mtu; off_t sent; off_t received; u_char challenge1[8]; u_char challenge2[8]; uint64_t seqnum; + uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES]; ngx_str_t addr_text; u_char text[NGX_SOCKADDR_STRLEN]; unsigned validated:1; @@ -206,6 +213,8 @@ struct ngx_quic_connection_s { uint64_t server_seqnum; uint64_t path_seqnum; + size_t max_mtu; + ngx_quic_tp_t tp; ngx_quic_tp_t ctp; 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 @@ -10,13 +10,23 @@ #include +#define NGX_QUIC_MAX_MTU_STEPS 7 +#define NGX_QUIC_MTU_PRECISION 4 + + static void ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path); static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path); static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); +static ngx_int_t ngx_quic_expire_path_mtu(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, ngx_quic_path_t *path, ngx_msec_int_t *next); +static ngx_int_t ngx_quic_expire_path(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, ngx_quic_path_t *path, ngx_msec_int_t *next); static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); +static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c, + ngx_quic_path_t *path); ngx_int_t @@ -169,6 +179,10 @@ valid: path->validating = 0; path->limited = 0; + if (ngx_quic_discover_path_mtu(c, path) != NGX_OK) { + return NGX_ERROR; + } + return NGX_OK; } @@ -207,6 +221,8 @@ ngx_quic_new_path(ngx_connection_t *c, path->limited = 1; + path->mtu = NGX_QUIC_MIN_INITIAL_SIZE; + path->seqnum = qc->path_seqnum++; path->sockaddr = &path->sa.sockaddr; @@ -496,7 +512,7 @@ ngx_quic_validate_path(ngx_connection_t "quic initiated validation of path seq:%uL", path->seqnum); path->validating = 1; - path->tries = 0; + path->valid_tries = 0; if (RAND_bytes(path->challenge1, 8) != 1) { return NGX_ERROR; @@ -513,7 +529,7 @@ ngx_quic_validate_path(ngx_connection_t ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); pto = ngx_max(ngx_quic_pto(c, ctx), 1000); - path->expires = ngx_current_msec + pto; + path->valid_expires = ngx_current_msec + pto; if (!qc->path_validation.timer_set) { ngx_add_timer(&qc->path_validation, pto); @@ -530,7 +546,7 @@ ngx_quic_send_path_challenge(ngx_connect ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic path seq:%uL send path_challenge tries:%ui", - path->seqnum, path->tries); + path->seqnum, path->valid_tries); ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); @@ -566,10 +582,9 @@ ngx_quic_send_path_challenge(ngx_connect void ngx_quic_path_validation_handler(ngx_event_t *ev) { - ngx_msec_t now; ngx_queue_t *q; - ngx_msec_int_t left, next, pto; - ngx_quic_path_t *path, *bkp; + ngx_msec_int_t next; + ngx_quic_path_t *path; ngx_connection_t *c; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -580,7 +595,6 @@ ngx_quic_path_validation_handler(ngx_eve ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); next = -1; - now = ngx_current_msec; q = ngx_queue_head(&qc->paths); @@ -589,78 +603,12 @@ ngx_quic_path_validation_handler(ngx_eve path = ngx_queue_data(q, ngx_quic_path_t, queue); q = ngx_queue_next(q); - if (!path->validating) { - continue; - } - - left = path->expires - now; - - if (left > 0) { - - if (next == -1 || left < next) { - next = left; - } - - continue; - } - - if (++path->tries < NGX_QUIC_PATH_RETRIES) { - pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; - - path->expires = ngx_current_msec + pto; - - if (next == -1 || pto < next) { - next = pto; - } - - /* retransmit */ - (void) ngx_quic_send_path_challenge(c, path); - - continue; + if (ngx_quic_expire_path_mtu(c, ctx, path, &next) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, - "quic path seq:%uL validation failed", path->seqnum); - - /* found expired path */ - - path->validated = 0; - path->validating = 0; - path->limited = 1; - - - /* RFC 9000, 9.3.2. On-Path Address Spoofing - * - * To protect the connection from failing due to such a spurious - * migration, an endpoint MUST revert to using the last validated - * peer address when validation of a new peer address fails. - */ - - if (qc->path == path) { - /* active path validation failed */ - - bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); - - if (bkp == NULL) { - qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; - qc->error_reason = "no viable path"; - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - - qc->path = bkp; - qc->path->tag = NGX_QUIC_PATH_ACTIVE; - - ngx_quic_set_connection_path(c, qc->path); - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic path seq:%uL addr:%V is restored from backup", - qc->path->seqnum, &qc->path->addr_text); - - ngx_quic_path_dbg(c, "is active", qc->path); - } - - if (ngx_quic_free_path(c, path) != NGX_OK) { + if (ngx_quic_expire_path(c, ctx, path, &next) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -670,3 +618,293 @@ ngx_quic_path_validation_handler(ngx_eve ngx_add_timer(&qc->path_validation, next); } } + + +static ngx_int_t +ngx_quic_expire_path_mtu(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_path_t *path, ngx_msec_int_t *next) +{ + ngx_int_t rc; + ngx_msec_t now; + ngx_msec_int_t left, pto; + + if (!path->mtud) { + return NGX_OK; + } + + now = ngx_current_msec; + + left = path->mtu_expires - now; + + if (left > 0) { + + if (*next == -1 || left < *next) { + *next = left; + } + + return NGX_OK; + } + + if (++path->mtu_tries < NGX_QUIC_PATH_RETRIES) { + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->mtu_tries; + + path->mtu_expires = ngx_current_msec + pto; + + if (*next == -1 || pto < *next) { + *next = pto; + } + + rc = ngx_quic_send_path_mtu_probe(c, path); + if (rc != NGX_DECLINED) { + return rc; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL mtu probe failed", path->seqnum); + + path->max_mtu = path->mtud; + path->mtud = 0; + + return ngx_quic_discover_path_mtu(c, path); +} + + +static ngx_int_t +ngx_quic_expire_path(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_path_t *path, ngx_msec_int_t *next) +{ + ngx_msec_t now; + ngx_msec_int_t left, pto; + ngx_quic_path_t *bkp; + ngx_quic_connection_t *qc; + + if (!path->validating) { + return NGX_OK; + } + + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + + left = path->valid_expires - now; + + if (left > 0) { + + if (*next == -1 || left < *next) { + *next = left; + } + + return NGX_OK; + } + + if (++path->valid_tries < NGX_QUIC_PATH_RETRIES) { + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->valid_tries; + + path->valid_expires = ngx_current_msec + pto; + + if (*next == -1 || pto < *next) { + *next = pto; + } + + /* retransmit */ + (void) ngx_quic_send_path_challenge(c, path); + + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL validation failed", path->seqnum); + + /* found expired path */ + + path->validated = 0; + path->validating = 0; + path->limited = 1; + + + /* RFC 9000, 9.3.2. On-Path Address Spoofing + * + * To protect the connection from failing due to such a spurious + * migration, an endpoint MUST revert to using the last validated + * peer address when validation of a new peer address fails. + */ + + if (qc->path == path) { + /* active path validation failed */ + + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp == NULL) { + qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; + qc->error_reason = "no viable path"; + return NGX_ERROR; + } + + qc->path = bkp; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, qc->path); + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path seq:%uL addr:%V is restored from backup", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is active", qc->path); + } + + if (ngx_quic_free_path(c, path) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_msec_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + +again: + + if (path->mtu_steps == 0) { + path->max_mtu = qc->max_mtu; + path->mtud = path->max_mtu; + + } else if (path->mtu_steps >= NGX_QUIC_MAX_MTU_STEPS + || (path->max_mtu - path->mtu) <= NGX_QUIC_MTU_PRECISION) + { + return NGX_OK; + + } else { + path->mtud = (path->mtu + path->max_mtu) / 2; + } + + path->mtu_steps++; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic initiated mtu discovery of path seq:%uL", + path->seqnum); + + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) { + path->mtu_pnum[i] = NGX_QUIC_UNSET_PN; + } + + path->mtu_tries = 0; + + rc = ngx_quic_send_path_mtu_probe(c, path); + + if (rc == NGX_DECLINED) { + path->max_mtu = path->mtud; + path->mtud = 0; + goto again; + } + + if (rc == NGX_ERROR) { + path->mtud = 0; + return NGX_ERROR; + } + + /* rc == NGX_OK */ + + pto = ngx_quic_pto(c, ctx); + path->mtu_expires = ngx_current_msec + pto; + + if (!qc->path_validation.timer_set) { + ngx_add_timer(&qc->path_validation, pto); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_int_t rc; + ngx_uint_t log_error; + 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.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); + path->mtu_pnum[path->mtu_tries] = ctx->pnum; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL send mtu probe " + "size:%uz pnum:%uL tries:%ui", + path->seqnum, path->mtud, ctx->pnum, path->mtu_tries); + + log_error = c->log_error; + c->log_error = NGX_ERROR_IGNORE_EMSGSIZE; + + rc = ngx_quic_frame_sendto(c, &frame, path->mtud, path); + c->log_error = log_error; + + if (rc == NGX_ERROR) { + if (c->write->error) { + c->write->error = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rejected mtu probe of path seq:%uL", + path->seqnum); + + return NGX_DECLINED; + } + + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_mtu_ack(ngx_connection_t *c, ngx_quic_path_t *path, + uint64_t min, uint64_t max) +{ + uint64_t pnum; + ngx_uint_t i; + + if (!path->mtud) { + return NGX_OK; + } + + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) { + pnum = path->mtu_pnum[i]; + + if (pnum == NGX_QUIC_UNSET_PN) { + break; + } + + if (pnum < min || pnum > max) { + continue; + } + + path->mtu = path->mtud; + path->mtud = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL mtu ack size:%uz", + path->seqnum, path->mtu); + + return ngx_quic_discover_path_mtu(c, path); + } + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -39,4 +39,9 @@ ngx_int_t ngx_quic_handle_migration(ngx_ void ngx_quic_path_validation_handler(ngx_event_t *ev); +ngx_int_t ngx_quic_discover_path_mtu(ngx_connection_t *c, + ngx_quic_path_t *path); +ngx_int_t ngx_quic_handle_path_mtu_ack(ngx_connection_t *c, + ngx_quic_path_t *path, uint64_t min, uint64_t max); + #endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ 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 @@ -10,9 +10,6 @@ #include -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 - #define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */ #define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */ @@ -61,21 +58,6 @@ static size_t ngx_quic_path_limit(ngx_co size_t size); -size_t -ngx_quic_max_udp_payload(ngx_connection_t *c) -{ - /* TODO: path MTU discovery */ - -#if (NGX_HAVE_INET6) - if (c->sockaddr->sa_family == AF_INET6) { - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; - } -#endif - - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; -} - - ngx_int_t ngx_quic_output(ngx_connection_t *c) { @@ -142,10 +124,7 @@ ngx_quic_create_datagrams(ngx_connection p = dst; - len = ngx_min(qc->ctp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - len = ngx_quic_path_limit(c, path, len); + len = ngx_quic_path_limit(c, path, path->mtu); pad = ngx_quic_get_padding_level(c); @@ -271,17 +250,19 @@ ngx_quic_allow_segmentation(ngx_connecti { size_t bytes, len; ngx_queue_t *q; + ngx_quic_path_t *path; ngx_quic_frame_t *f; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); + path = qc->path; if (!qc->conf->gso_enabled) { return 0; } - if (qc->path->limited) { + if (path->limited) { /* don't even try to be faster on non-validated paths */ return 0; } @@ -299,9 +280,7 @@ ngx_quic_allow_segmentation(ngx_connecti ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); bytes = 0; - - len = ngx_min(qc->ctp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_SEGMENT_BUF); + len = path->mtu; for (q = ngx_queue_head(&ctx->frames); q != ngx_queue_sentinel(&ctx->frames); @@ -345,8 +324,7 @@ ngx_quic_create_segments(ngx_connection_ return NGX_ERROR; } - segsize = ngx_min(qc->ctp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_SEGMENT_BUF); + segsize = ngx_min(path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF); p = dst; end = dst + sizeof(dst); 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 @@ -12,8 +12,6 @@ #include -size_t ngx_quic_max_udp_payload(ngx_connection_t *c); - ngx_int_t ngx_quic_output(ngx_connection_t *c); ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -499,6 +499,10 @@ ngx_quic_crypto_input(ngx_connection_t * return NGX_ERROR; } + if (ngx_quic_discover_path_mtu(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + if (ngx_quic_init_streams(c) != NGX_OK) { return NGX_ERROR; } diff --git a/src/os/unix/ngx_darwin_config.h b/src/os/unix/ngx_darwin_config.h --- a/src/os/unix/ngx_darwin_config.h +++ b/src/os/unix/ngx_darwin_config.h @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include diff --git a/src/os/unix/ngx_errno.h b/src/os/unix/ngx_errno.h --- a/src/os/unix/ngx_errno.h +++ b/src/os/unix/ngx_errno.h @@ -54,6 +54,7 @@ typedef int ngx_err_t; #define NGX_ENOMOREFILES 0 #define NGX_ELOOP ELOOP #define NGX_EBADF EBADF +#define NGX_EMSGSIZE EMSGSIZE #if (NGX_HAVE_OPENAT) #define NGX_EMLINK EMLINK diff --git a/src/os/unix/ngx_freebsd_config.h b/src/os/unix/ngx_freebsd_config.h --- a/src/os/unix/ngx_freebsd_config.h +++ b/src/os/unix/ngx_freebsd_config.h @@ -48,6 +48,9 @@ #include /* setproctitle() before 4.1 */ #include #include +#include +#include +#include #include diff --git a/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h --- a/src/os/unix/ngx_linux_config.h +++ b/src/os/unix/ngx_linux_config.h @@ -54,6 +54,8 @@ #include #include #include /* uname() */ +#include +#include #include diff --git a/src/os/unix/ngx_posix_config.h b/src/os/unix/ngx_posix_config.h --- a/src/os/unix/ngx_posix_config.h +++ b/src/os/unix/ngx_posix_config.h @@ -140,6 +140,17 @@ typedef struct aiocb ngx_aiocb_t; #endif +#if (NGX_HAVE_SIOCGIFMTU) +#include +#include +#endif + + +#if (NGX_HAVE_GETIFADDRS) +#include +#endif + + #define NGX_LISTEN_BACKLOG 511 #define ngx_debug_init() diff --git a/src/os/unix/ngx_solaris_config.h b/src/os/unix/ngx_solaris_config.h --- a/src/os/unix/ngx_solaris_config.h +++ b/src/os/unix/ngx_solaris_config.h @@ -88,6 +88,17 @@ #endif +#if (NGX_HAVE_SIOCGIFMTU) +#include +#include +#endif + + +#if (NGX_HAVE_GETIFADDRS) +#include +#endif + + #define NGX_LISTEN_BACKLOG 511 -- Sergey Kandaurov From mdounin at mdounin.ru Mon May 1 20:39:59 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 1 May 2023 23:39:59 +0300 Subject: [PATCH 3 of 3] QUIC: path MTU discovery In-Reply-To: <765817FB-D1C5-45AB-A20D-86E00A2CB4EE@nginx.com> References: <13d43a278510f131101c.1680015100@arut-laptop> <765817FB-D1C5-45AB-A20D-86E00A2CB4EE@nginx.com> Message-ID: Hello! On Mon, May 01, 2023 at 08:58:55PM +0400, Sergey Kandaurov wrote: [...] > It makes sense to further limit link MTU to system constraints. > > BSD is known to have a system limitation for a maximum outgoing UDP > datagram size set for some reason. In all known distributions which > derive from 4.3BSD-Reno, it defaults to 9216. > For that, we can query the limit using sysctl. > > Limited, this reduces the number of MTU probes. > In particular on lo0, before the change: > > - 16356 > + 8778 > - 12567 > - 10672 > - 9725 > - 9251 > + 9014 > > Legend: '-' is rejected with EMSGSIZE, '+' sent to network > After the change, it is the only successful probe of maxdgram. > > On real networks there will be different results, > but the general pattern can be traced. > > Aside from that, I noticed that we have split FreeBSD > and Darwin tests/sources but didn't accommodate for that. > > Below to address this. The first one or two patches of three > could go directly to the default branch if approved, > the last one set upper bound for maxdgram where appropriate. > > # HG changeset patch > # User Sergey Kandaurov > # Date 1682957319 -14400 > # Mon May 01 20:08:39 2023 +0400 > # Branch quic > # Node ID b4d35da933cc3df9a098feb6d06957b19e228c39 > # Parent cc5d2e648dd4359d77c28120e6e02f9b5842024e > Fixed Darwin support for the "net.inet.tcp.sendspace" sysctl. > > After Darwin support was split in separate files in 345a014436d4 (0.7.7), > sysctl variables received a separate prefix, but common sources were not > updated. In particular, the "net.inet.tcp.sendspace" sysctl value wasn't > checked as a limit for the send_lowat directive and friends. > > The change unifies a prefix for the "sendspace" variable in both FreeBSD > and Darwin. Other extern variables aren't touched: their usage is either > limited to os-specific source files or they aren't used at all. > > diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c > --- a/src/http/modules/ngx_http_fastcgi_module.c > +++ b/src/http/modules/ngx_http_fastcgi_module.c > @@ -3905,14 +3905,14 @@ ngx_http_fastcgi_cache_key(ngx_conf_t *c > static char * > ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, void *data) > { > -#if (NGX_FREEBSD) > +#if (NGX_FREEBSD || NGX_DARWIN) > ssize_t *np = data; > > - if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { > + if ((u_long) *np >= ngx_net_inet_tcp_sendspace) { > ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > "\"fastcgi_send_lowat\" must be less than %d " > "(sysctl net.inet.tcp.sendspace)", > - ngx_freebsd_net_inet_tcp_sendspace); > + ngx_net_inet_tcp_sendspace); > > return NGX_CONF_ERROR; > } > diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c > --- a/src/http/modules/ngx_http_proxy_module.c > +++ b/src/http/modules/ngx_http_proxy_module.c > @@ -4890,14 +4890,14 @@ ngx_http_proxy_ssl_password_file(ngx_con > static char * > ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data) > { > -#if (NGX_FREEBSD) > +#if (NGX_FREEBSD || NGX_DARWIN) > ssize_t *np = data; > > - if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { > + if ((u_long) *np >= ngx_net_inet_tcp_sendspace) { > ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > "\"proxy_send_lowat\" must be less than %d " > "(sysctl net.inet.tcp.sendspace)", > - ngx_freebsd_net_inet_tcp_sendspace); > + ngx_net_inet_tcp_sendspace); > > return NGX_CONF_ERROR; > } > diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c > --- a/src/http/ngx_http_core_module.c > +++ b/src/http/ngx_http_core_module.c > @@ -5288,14 +5288,14 @@ ngx_http_disable_symlinks(ngx_conf_t *cf > static char * > ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data) > { > -#if (NGX_FREEBSD) > +#if (NGX_FREEBSD || NGX_DARWIN) > ssize_t *np = data; > > - if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { > + if ((u_long) *np >= ngx_net_inet_tcp_sendspace) { > ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > "\"send_lowat\" must be less than %d " > "(sysctl net.inet.tcp.sendspace)", > - ngx_freebsd_net_inet_tcp_sendspace); > + ngx_net_inet_tcp_sendspace); > > return NGX_CONF_ERROR; > } > diff --git a/src/os/unix/ngx_darwin.h b/src/os/unix/ngx_darwin.h > --- a/src/os/unix/ngx_darwin.h > +++ b/src/os/unix/ngx_darwin.h > @@ -15,7 +15,7 @@ ngx_chain_t *ngx_darwin_sendfile_chain(n > > extern int ngx_darwin_kern_osreldate; > extern int ngx_darwin_hw_ncpu; > -extern u_long ngx_darwin_net_inet_tcp_sendspace; > +extern u_long ngx_net_inet_tcp_sendspace; > > extern ngx_uint_t ngx_debug_malloc; > > diff --git a/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c > --- a/src/os/unix/ngx_darwin_init.c > +++ b/src/os/unix/ngx_darwin_init.c > @@ -13,7 +13,7 @@ char ngx_darwin_kern_ostype[16]; > char ngx_darwin_kern_osrelease[128]; > int ngx_darwin_hw_ncpu; > int ngx_darwin_kern_ipc_somaxconn; > -u_long ngx_darwin_net_inet_tcp_sendspace; > +u_long ngx_net_inet_tcp_sendspace; > > ngx_uint_t ngx_debug_malloc; > > @@ -49,8 +49,8 @@ sysctl_t sysctls[] = { > sizeof(ngx_darwin_hw_ncpu), 0 }, > > { "net.inet.tcp.sendspace", > - &ngx_darwin_net_inet_tcp_sendspace, > - sizeof(ngx_darwin_net_inet_tcp_sendspace), 0 }, > + &ngx_net_inet_tcp_sendspace, > + sizeof(ngx_net_inet_tcp_sendspace), 0 }, > > { "kern.ipc.somaxconn", > &ngx_darwin_kern_ipc_somaxconn, > diff --git a/src/os/unix/ngx_freebsd.h b/src/os/unix/ngx_freebsd.h > --- a/src/os/unix/ngx_freebsd.h > +++ b/src/os/unix/ngx_freebsd.h > @@ -15,7 +15,7 @@ ngx_chain_t *ngx_freebsd_sendfile_chain( > > extern int ngx_freebsd_kern_osreldate; > extern int ngx_freebsd_hw_ncpu; > -extern u_long ngx_freebsd_net_inet_tcp_sendspace; > +extern u_long ngx_net_inet_tcp_sendspace; > > extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug; > extern ngx_uint_t ngx_freebsd_use_tcp_nopush; > diff --git a/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c > --- a/src/os/unix/ngx_freebsd_init.c > +++ b/src/os/unix/ngx_freebsd_init.c > @@ -15,7 +15,7 @@ char ngx_freebsd_kern_osrelease[128]; > int ngx_freebsd_kern_osreldate; > int ngx_freebsd_hw_ncpu; > int ngx_freebsd_kern_ipc_somaxconn; > -u_long ngx_freebsd_net_inet_tcp_sendspace; > +u_long ngx_net_inet_tcp_sendspace; > > /* FreeBSD 4.9 */ > int ngx_freebsd_machdep_hlt_logical_cpus; > @@ -62,8 +62,8 @@ sysctl_t sysctls[] = { > sizeof(ngx_freebsd_machdep_hlt_logical_cpus), 0 }, > > { "net.inet.tcp.sendspace", > - &ngx_freebsd_net_inet_tcp_sendspace, > - sizeof(ngx_freebsd_net_inet_tcp_sendspace), 0 }, > + &ngx_net_inet_tcp_sendspace, > + sizeof(ngx_net_inet_tcp_sendspace), 0 }, > > { "kern.ipc.somaxconn", > &ngx_freebsd_kern_ipc_somaxconn, I don't think it worth the effort, since Darwin / macOS isn't used as a server OS, and optimizing behaviour for it isn't really important. But if at all, it should be kept with appropriate ngx_freebsd_/ngx_darwin_ prefixes, and either tested separately, or applied to a generic ngx_net_inet_tcp_sendspace variable kept in ngx_os.h, similarly to how ngx_ncpu and ngx_tcp_nodelay_and_tcp_nopush are handled. > # HG changeset patch > # User Sergey Kandaurov > # Date 1682957408 -14400 > # Mon May 01 20:10:08 2023 +0400 > # Branch quic > # Node ID 976e2e40f0f58a5bbcd89b76e24909be0c8d337d > # Parent b4d35da933cc3df9a098feb6d06957b19e228c39 > Introduced the "net.inet.udp.maxdgram" sysctl variable. > > diff --git a/src/os/unix/ngx_darwin.h b/src/os/unix/ngx_darwin.h > --- a/src/os/unix/ngx_darwin.h > +++ b/src/os/unix/ngx_darwin.h > @@ -16,6 +16,7 @@ ngx_chain_t *ngx_darwin_sendfile_chain(n > extern int ngx_darwin_kern_osreldate; > extern int ngx_darwin_hw_ncpu; > extern u_long ngx_net_inet_tcp_sendspace; > +extern u_long ngx_net_inet_udp_maxdgram; > > extern ngx_uint_t ngx_debug_malloc; > > diff --git a/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c > --- a/src/os/unix/ngx_darwin_init.c > +++ b/src/os/unix/ngx_darwin_init.c > @@ -14,6 +14,7 @@ char ngx_darwin_kern_osrelease[128]; > int ngx_darwin_hw_ncpu; > int ngx_darwin_kern_ipc_somaxconn; > u_long ngx_net_inet_tcp_sendspace; > +u_long ngx_net_inet_udp_maxdgram; > > ngx_uint_t ngx_debug_malloc; > > @@ -52,6 +53,10 @@ sysctl_t sysctls[] = { > &ngx_net_inet_tcp_sendspace, > sizeof(ngx_net_inet_tcp_sendspace), 0 }, > > + { "net.inet.udp.maxdgram", > + &ngx_net_inet_udp_maxdgram, > + sizeof(ngx_net_inet_udp_maxdgram), 0 }, > + > { "kern.ipc.somaxconn", > &ngx_darwin_kern_ipc_somaxconn, > sizeof(ngx_darwin_kern_ipc_somaxconn), 0 }, > diff --git a/src/os/unix/ngx_freebsd.h b/src/os/unix/ngx_freebsd.h > --- a/src/os/unix/ngx_freebsd.h > +++ b/src/os/unix/ngx_freebsd.h > @@ -16,6 +16,7 @@ ngx_chain_t *ngx_freebsd_sendfile_chain( > extern int ngx_freebsd_kern_osreldate; > extern int ngx_freebsd_hw_ncpu; > extern u_long ngx_net_inet_tcp_sendspace; > +extern u_long ngx_net_inet_udp_maxdgram; > > extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug; > extern ngx_uint_t ngx_freebsd_use_tcp_nopush; > diff --git a/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c > --- a/src/os/unix/ngx_freebsd_init.c > +++ b/src/os/unix/ngx_freebsd_init.c > @@ -16,6 +16,7 @@ int ngx_freebsd_kern_osreldate; > int ngx_freebsd_hw_ncpu; > int ngx_freebsd_kern_ipc_somaxconn; > u_long ngx_net_inet_tcp_sendspace; > +u_long ngx_net_inet_udp_maxdgram; > > /* FreeBSD 4.9 */ > int ngx_freebsd_machdep_hlt_logical_cpus; > @@ -65,6 +66,10 @@ sysctl_t sysctls[] = { > &ngx_net_inet_tcp_sendspace, > sizeof(ngx_net_inet_tcp_sendspace), 0 }, > > + { "net.inet.udp.maxdgram", > + &ngx_net_inet_udp_maxdgram, > + sizeof(ngx_net_inet_udp_maxdgram), 0 }, > + > { "kern.ipc.somaxconn", > &ngx_freebsd_kern_ipc_somaxconn, > sizeof(ngx_freebsd_kern_ipc_somaxconn), 0 }, Same here (though this night be slightly more meaningful). > # HG changeset patch > # User Sergey Kandaurov > # Date 1682959469 -14400 > # Mon May 01 20:44:29 2023 +0400 > # Branch quic > # Node ID 4f32cf8fe9241fe002701d3bf2dd38ea358c0b5d > # Parent 976e2e40f0f58a5bbcd89b76e24909be0c8d337d > QUIC: limited link MTU upper bound to maxdgram. > > 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 > @@ -434,6 +434,10 @@ ngx_quic_get_local_mtu(ngx_connection_t > #endif > } > > +#if (NGX_FREEBSD || NGX_DARWIN) > + mtu = ngx_min(mtu, ngx_net_inet_udp_maxdgram); > +#endif > + > ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, > "quic local mtu:%uz", mtu); > Note that with a generic ngx_os.h variable this won't require conditional compilation. Also, it might be a good idea to reduce the variable on EMSGSIZE errors: this will make handling close to optimal even on OSes where we don't use net.inet.udp.maxdgram sysctl. [...] -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon May 1 20:59:06 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 1 May 2023 23:59:06 +0300 Subject: [PATCH] Tests: HTTP/2 tests with error_page and return In-Reply-To: <90aaa942972884dcd67b.1682952385@enoparse.local> References: <90aaa942972884dcd67b.1682952385@enoparse.local> Message-ID: Hello! On Mon, May 01, 2023 at 06:46:25PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1682952238 -14400 > # Mon May 01 18:43:58 2023 +0400 > # Node ID 90aaa942972884dcd67b6744fde39a154fec5d13 > # Parent 36a4563f7f005184547575f5ac4f22ef53a59c72 > Tests: HTTP/2 tests with error_page and return. > > diff --git a/h2_error_page.t b/h2_error_page.t > new file mode 100644 > --- /dev/null > +++ b/h2_error_page.t > @@ -0,0 +1,88 @@ > +#!/usr/bin/perl > + > +# (C) Sergey Kandaurov > +# (C) Nginx, Inc. > + > +# Tests for HTTP/2 protocol with error_page directive. > + > +############################################################################### > + > +use warnings; > +use strict; > + > +use Test::More; > + > +BEGIN { use FindBin; chdir($FindBin::Bin); } > + > +use lib 'lib'; > +use Test::Nginx; > +use Test::Nginx::HTTP2; > + > +############################################################################### > + > +select STDERR; $| = 1; > +select STDOUT; $| = 1; > + > +my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(2) > + ->write_file_expand('nginx.conf', <<'EOF'); > + > +%%TEST_GLOBALS%% > + > +daemon off; > + > +events { > +} > + > +http { > + %%TEST_GLOBALS_HTTP%% > + > + server { > + listen 127.0.0.1:8080 http2; > + server_name localhost; > + > + lingering_close off; > + > + error_page 400 = /close; > + > + location / { } > + > + location /close { > + return 444; > + } > + } > +} > + > +EOF > + > +$t->run(); > + > +############################################################################### > + > +my ($sid, $frames, $frame); > + > +# tests for socket leak with "return 444" in error_page > + > +# ticket #274 > + > +my $s1 = Test::Nginx::HTTP2->new(); > +$sid = $s1->new_stream({ headers => [ > + { name => ':method', value => 'GET' }, > + { name => ':path', value => '/' }, > + { name => ':authority', value => 'localhost' }]}); > +$frames = $s1->read(all => [{ type => 'RST_STREAM' }]); > + > +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; > +is($frame->{sid}, $sid, 'error 400 return 444 - missing header'); This clearly needs details about the header being missed, as well as expected and observed behaviour, not just the ticket number. > + > +# ticket #2455 > + > +my $s2 = Test::Nginx::HTTP2->new(); > +$sid = $s2->new_stream({ method => 'foo' }); > +$frames = $s2->read(all => [{ type => 'RST_STREAM' }]); > + > +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; > +is($frame->{sid}, $sid, 'error 400 return 444 - invalid header'); Same here. > + > +$t->stop(); > + > +############################################################################### -- Maxim Dounin http://mdounin.ru/ From pluknet at nginx.com Tue May 2 12:14:58 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 2 May 2023 16:14:58 +0400 Subject: [PATCH 2 of 2] QUIC: improved split frames error handling In-Reply-To: References: Message-ID: > On 1 May 2023, at 19:26, Sergey Kandaurov wrote: > > # HG changeset patch > # User Sergey Kandaurov > # Date 1682954724 -14400 > # Mon May 01 19:25:24 2023 +0400 > # Branch quic > # Node ID b10aa30b15a802870eb23716ce3937a1085c4c98 > # Parent 8aa3363bc83d4354b3142e3972cce5c0ef523539 > QUIC: improved split frames error handling. > > Do not update frame data chain on ngx_quic_read_buffer() error. > It may be used later as part of error handling, which envolves > writing CONNECTION_CLOSE and pending frames, including the one > with the corrupted chain pointer. Since ngx_quic_read_buffer() > returns the original chain, there is no point in updating it, > so this was simplified. > > diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c > --- a/src/event/quic/ngx_event_quic_frames.c > +++ b/src/event/quic/ngx_event_quic_frames.c > @@ -359,8 +359,7 @@ ngx_quic_split_frame(ngx_connection_t *c > ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); > qb.chain = f->data; > > - f->data = ngx_quic_read_buffer(c, &qb, of->length); > - if (f->data == NGX_CHAIN_ERROR) { > + if (ngx_quic_read_buffer(c, &qb, of->length) == NGX_CHAIN_ERROR) { > return NGX_ERROR; > } > Below is an updated version after discussion with Roman. It uses conservative approach to maintain API and not rely on that the passed output chain pointer will not be modified. Theoretically it can if the limit inside ngx_quic_read_buffer() is zero on the first iteration so that the passed chain pointer will be zeroed. Another possibility is ngx_quic_split_chain() potentially may be rewritten to insert a new element in ngx_quic_split_chain() first. # HG changeset patch # User Sergey Kandaurov # Date 1683029654 -14400 # Tue May 02 16:14:14 2023 +0400 # Branch quic # Node ID 491623c24eb30876f60196e6ca99e5284de43cd5 # Parent 8aa3363bc83d4354b3142e3972cce5c0ef523539 QUIC: fixed split frames error handling. Do not corrupt frame data chain pointer on ngx_quic_read_buffer() error. The error leads to closing a QUIC connection where the frame may be used as part of the QUIC connection tear down, which envolves writing pending frames, including this one. diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -319,6 +319,7 @@ ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) { size_t shrink; + ngx_chain_t *out; ngx_quic_frame_t *nf; ngx_quic_buffer_t qb; ngx_quic_ordered_frame_t *of, *onf; @@ -359,11 +360,13 @@ ngx_quic_split_frame(ngx_connection_t *c ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); qb.chain = f->data; - f->data = ngx_quic_read_buffer(c, &qb, of->length); - if (f->data == NGX_CHAIN_ERROR) { + out = ngx_quic_read_buffer(c, &qb, of->length); + if (out == NGX_CHAIN_ERROR) { return NGX_ERROR; } + f->data = out; + nf = ngx_quic_alloc_frame(c); if (nf == NULL) { return NGX_ERROR; -- Sergey Kandaurov From arut at nginx.com Tue May 2 12:34:12 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Tue, 02 May 2023 16:34:12 +0400 Subject: [PATCH 0 of 3] QUIC post-migration address issues Message-ID: The first issue was reported in ticket #2488. The second and third issues were observed while working on the first one. The issues manifest themselves best when migrating to another IP address. When testing migration with ngtcp2 --change-local-addr, only the port is changed. I had to patch ngtcp2 client to make it migrate to a new IP address, which has to be bigger in text representation to trigger the length issues. However, while doing that, it became clear that nginx does not handle well this kind of migrations. I observed the following issues: - After address validation completes, in_flight counter is reset, but in-flight packets which contributed to the old value of the counter are not ignored. This resulted in in_flight counter underflow, followed by a connection stall. The counter reset does not happen if the IP address stays the same. - While congestion controller is reset on successful address validation, RTT estimator is not. According to RFC 900, both should be reset. - While address validation is in progress, nginx sends packets using the new path. This is something allowed by RFC 9000. However, it's not clear which congestion/RTT/PTO/loss detection to use during that period. The safest solution is to block all output until PATH_RESPONSE is received or validation timeout expires. These issues were partially addressed in this patchset by Sergey: https://mailman.nginx.org/pipermail/nginx-devel/2022-December/IMZ7IUAF67OO6OVFHQHTFMUODV6OA73M.html The series is not yet commited. Hopefully we'll get back to it shortly. -- Roman Arutyunyan From arut at nginx.com Tue May 2 12:34:13 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Tue, 02 May 2023 16:34:13 +0400 Subject: [PATCH 1 of 3] QUIC: fixed addr_text after migration (ticket #2488) In-Reply-To: References: Message-ID: <746a3a71e428796b0593.1683030853@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1682610760 -14400 # Thu Apr 27 19:52:40 2023 +0400 # Branch quic # Node ID 746a3a71e428796b0593d098fcd299b94cc00108 # Parent 8347620e0e762c5dea99247dc70fbbffd0c6b175 QUIC: fixed addr_text after migration (ticket #2488). Previously, the post-migration value of addr_text could be truncated, if it was longer than the previous one. Also, the new value always included port, which should not be there. 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 @@ -384,16 +384,13 @@ ngx_quic_free_path(ngx_connection_t *c, static void ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) { - size_t len; - ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); c->socklen = path->socklen; if (c->addr_text.data) { - len = ngx_min(c->addr_text.len, path->addr_text.len); - - ngx_memcpy(c->addr_text.data, path->addr_text.data, len); - c->addr_text.len = len; + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + c->listening->addr_text_max_len, 0); } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, From arut at nginx.com Tue May 2 12:34:14 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Tue, 02 May 2023 16:34:14 +0400 Subject: [PATCH 2 of 3] QUIC: set c->socklen for streams In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1682610545 -14400 # Thu Apr 27 19:49:05 2023 +0400 # Branch quic # Node ID cdc41ec778ffae822fefce639e67f2f57e3667f0 # Parent 746a3a71e428796b0593d098fcd299b94cc00108 QUIC: set c->socklen for streams. Previously, the value was not set and remained zero. While in nginx code the value of c->sockaddr is accessed without taking c->socklen into account, invalid c->socklen could lead to unexpected results in third-party modules. diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -711,6 +711,7 @@ ngx_quic_create_stream(ngx_connection_t sc->pool = pool; sc->ssl = c->ssl; sc->sockaddr = c->sockaddr; + sc->socklen = c->socklen; sc->listening = c->listening; sc->addr_text = c->addr_text; sc->local_sockaddr = c->local_sockaddr; From arut at nginx.com Tue May 2 12:34:15 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Tue, 02 May 2023 16:34:15 +0400 Subject: [PATCH 3 of 3] QUIC: keep stream sockaddr and addr_text constant In-Reply-To: References: Message-ID: <43f0ceffa227a33e5c5c.1683030855@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1682679819 -14400 # Fri Apr 28 15:03:39 2023 +0400 # Branch quic # Node ID 43f0ceffa227a33e5c5ceb35b77f9a1f86dd2481 # Parent cdc41ec778ffae822fefce639e67f2f57e3667f0 QUIC: keep stream sockaddr and addr_text constant. HTTP and Stream variables $remote_addr and $binary_remote_addr rely on constant client address, particularly because they are cacheable. However, QUIC client may migrate to a new address. While there's no perfect way to handle this, the proposed solution is to copy client address to QUIC stream at stream creation. Previously, the address was only referenced, which could result in changing it while stream was active, which in turn would lead to broken cached variables values, since address length is cached as well. diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -637,10 +637,12 @@ ngx_quic_do_init_streams(ngx_connection_ static ngx_quic_stream_t * ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) { + ngx_str_t addr_text; ngx_log_t *log; ngx_pool_t *pool; ngx_uint_t reusable; ngx_queue_t *q; + struct sockaddr *sockaddr; ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_pool_cleanup_t *cln; @@ -692,6 +694,30 @@ ngx_quic_create_stream(ngx_connection_t *log = *c->log; pool->log = log; + sockaddr = ngx_palloc(pool, c->socklen); + if (sockaddr == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(sockaddr, c->sockaddr, c->socklen); + + if (c->addr_text.data) { + addr_text.data = ngx_pnalloc(pool, c->addr_text.len); + if (addr_text.data == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(addr_text.data, c->addr_text.data, c->addr_text.len); + addr_text.len = c->addr_text.len; + + } else { + addr_text.len = 0; + } + reusable = c->reusable; ngx_reusable_connection(c, 0); @@ -710,10 +736,10 @@ ngx_quic_create_stream(ngx_connection_t sc->type = SOCK_STREAM; sc->pool = pool; sc->ssl = c->ssl; - sc->sockaddr = c->sockaddr; + sc->sockaddr = sockaddr; sc->socklen = c->socklen; sc->listening = c->listening; - sc->addr_text = c->addr_text; + sc->addr_text = addr_text; sc->local_sockaddr = c->local_sockaddr; sc->local_socklen = c->local_socklen; sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); From pluknet at nginx.com Tue May 2 13:10:55 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 2 May 2023 17:10:55 +0400 Subject: [PATCH] Tests: HTTP/2 tests with error_page and return In-Reply-To: References: <90aaa942972884dcd67b.1682952385@enoparse.local> Message-ID: > On 2 May 2023, at 00:59, Maxim Dounin wrote: > > Hello! > > On Mon, May 01, 2023 at 06:46:25PM +0400, Sergey Kandaurov wrote: > >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1682952238 -14400 >> # Mon May 01 18:43:58 2023 +0400 >> # Node ID 90aaa942972884dcd67b6744fde39a154fec5d13 >> # Parent 36a4563f7f005184547575f5ac4f22ef53a59c72 >> Tests: HTTP/2 tests with error_page and return. >> >> diff --git a/h2_error_page.t b/h2_error_page.t >> new file mode 100644 >> --- /dev/null >> +++ b/h2_error_page.t >> @@ -0,0 +1,88 @@ >> +#!/usr/bin/perl >> + >> +# (C) Sergey Kandaurov >> +# (C) Nginx, Inc. >> + >> +# Tests for HTTP/2 protocol with error_page directive. >> + >> +############################################################################### >> + >> +use warnings; >> +use strict; >> + >> +use Test::More; >> + >> +BEGIN { use FindBin; chdir($FindBin::Bin); } >> + >> +use lib 'lib'; >> +use Test::Nginx; >> +use Test::Nginx::HTTP2; >> + >> +############################################################################### >> + >> +select STDERR; $| = 1; >> +select STDOUT; $| = 1; >> + >> +my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(2) >> + ->write_file_expand('nginx.conf', <<'EOF'); >> + >> +%%TEST_GLOBALS%% >> + >> +daemon off; >> + >> +events { >> +} >> + >> +http { >> + %%TEST_GLOBALS_HTTP%% >> + >> + server { >> + listen 127.0.0.1:8080 http2; >> + server_name localhost; >> + >> + lingering_close off; >> + >> + error_page 400 = /close; >> + >> + location / { } >> + >> + location /close { >> + return 444; >> + } >> + } >> +} >> + >> +EOF >> + >> +$t->run(); >> + >> +############################################################################### >> + >> +my ($sid, $frames, $frame); >> + >> +# tests for socket leak with "return 444" in error_page >> + >> +# ticket #274 >> + >> +my $s1 = Test::Nginx::HTTP2->new(); >> +$sid = $s1->new_stream({ headers => [ >> + { name => ':method', value => 'GET' }, >> + { name => ':path', value => '/' }, >> + { name => ':authority', value => 'localhost' }]}); >> +$frames = $s1->read(all => [{ type => 'RST_STREAM' }]); >> + >> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; >> +is($frame->{sid}, $sid, 'error 400 return 444 - missing header'); > > This clearly needs details about the header being missed, as well > as expected and observed behaviour, not just the ticket number. The description is provided in associated commit logs, a proper source to seek for details, tagged with appropriate ticket numbers. A brief description what happens here is given above. If you insist, we can add further details inline: # a socket leak observed on missing ngx_http_run_posted_requests() such as # in ngx_http_v2_run_request(), e.g. if ngx_http_v2_construct_request_line() # failed due to missing mandatory ":scheme" pseudo-header (ticket #274) > >> + >> +# ticket #2455 >> + >> +my $s2 = Test::Nginx::HTTP2->new(); >> +$sid = $s2->new_stream({ method => 'foo' }); >> +$frames = $s2->read(all => [{ type => 'RST_STREAM' }]); >> + >> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; >> +is($frame->{sid}, $sid, 'error 400 return 444 - invalid header'); > > Same here. # another case with missing ngx_http_run_posted_requests() is in # ngx_http_v2_state_process_header() error handling (ticket #2455), # can be triggered with invalid pseudo-header > >> + >> +$t->stop(); >> + >> +############################################################################### > > -- Sergey Kandaurov From pluknet at nginx.com Tue May 2 13:49:23 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 2 May 2023 17:49:23 +0400 Subject: [PATCH 02 of 11] Tests: removed unneeded require from proxy_ssl_keepalive.t In-Reply-To: <6f0148ef1991d92a003c.1681702286@vm-bsd.mdounin.ru> References: <6f0148ef1991d92a003c.1681702286@vm-bsd.mdounin.ru> Message-ID: <44FD297A-6B6A-4625-A959-8265A51D7480@nginx.com> > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > # HG changeset patch > # User Maxim Dounin > # Date 1681702250 -10800 > # Mon Apr 17 06:30:50 2023 +0300 > # Node ID 6f0148ef1991d92a003c8529c8cce9a8dd49e706 > # Parent a01b7d84f4355073a00f43760fc512e03b4452c3 > Tests: removed unneeded require from proxy_ssl_keepalive.t. > > diff --git a/proxy_ssl_keepalive.t b/proxy_ssl_keepalive.t > --- a/proxy_ssl_keepalive.t > +++ b/proxy_ssl_keepalive.t > @@ -22,9 +22,6 @@ use Test::Nginx; > select STDERR; $| = 1; > select STDOUT; $| = 1; > > -eval { require IO::Socket::SSL; }; > -plan(skip_all => 'IO::Socket::SSL not installed') if $@; > - > my $t = Test::Nginx->new()->has(qw/http http_ssl proxy upstream_keepalive/) > ->has_daemon('openssl')->plan(3) > ->write_file_expand('nginx.conf', <<'EOF'); We can as well remove it from h2_ssl_proxy_cache.t and h2_ssl_variables.t after 45c80276d691, HTTP2 package handles that for us. (Same approach used for the crypto layer of HTTP/3 tests.) -- Sergey Kandaurov From arut at nginx.com Tue May 2 13:58:16 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Tue, 02 May 2023 17:58:16 +0400 Subject: [PATCH] QUIC: optimized immediate close Message-ID: <0ae438bff8e8788f6972.1683035896@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1683035693 -14400 # Tue May 02 17:54:53 2023 +0400 # Branch quic # Node ID 0ae438bff8e8788f6972b2f8268194b571d728c0 # Parent 558cdf07793de4108527ed6757fd3864e9694b93 QUIC: optimized immediate close. Previously, before sending CONNECTION_CLOSE to client, all pending frames were sent. This is redundant and could prevent CONNECTION_CLOSE from being sent due to congestion control. Now pending frames are freed and CONNECTION_CLOSE is sent without congestion control, as advised by RFC 9002: Packets containing frames besides ACK or CONNECTION_CLOSE frames count toward congestion control limits and are considered to be in flight. 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 @@ -482,6 +482,7 @@ ngx_quic_close_connection(ngx_connection /* drop packets from retransmit queues, no ack is expected */ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } 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 @@ -882,7 +882,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); @@ -898,27 +898,22 @@ ngx_quic_send_cc(ngx_connection_t *c) return NGX_OK; } - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); - 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; } - ngx_quic_queue_frame(qc, frame); - qc->last_cc = ngx_current_msec; - return ngx_quic_output(c); + return ngx_quic_frame_sendto(c, &frame, 0, qc->path); } From arut at nginx.com Tue May 2 13:59:26 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Tue, 2 May 2023 17:59:26 +0400 Subject: [PATCH 2 of 2] QUIC: improved split frames error handling In-Reply-To: References: Message-ID: <20230502135926.vwtnnjoyhrfopod5@N00W24XTQX> On Tue, May 02, 2023 at 04:14:58PM +0400, Sergey Kandaurov wrote: > > > On 1 May 2023, at 19:26, Sergey Kandaurov wrote: > > > > # HG changeset patch > > # User Sergey Kandaurov > > # Date 1682954724 -14400 > > # Mon May 01 19:25:24 2023 +0400 > > # Branch quic > > # Node ID b10aa30b15a802870eb23716ce3937a1085c4c98 > > # Parent 8aa3363bc83d4354b3142e3972cce5c0ef523539 > > QUIC: improved split frames error handling. > > > > Do not update frame data chain on ngx_quic_read_buffer() error. > > It may be used later as part of error handling, which envolves > > writing CONNECTION_CLOSE and pending frames, including the one > > with the corrupted chain pointer. Since ngx_quic_read_buffer() > > returns the original chain, there is no point in updating it, > > so this was simplified. > > > > diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c > > --- a/src/event/quic/ngx_event_quic_frames.c > > +++ b/src/event/quic/ngx_event_quic_frames.c > > @@ -359,8 +359,7 @@ ngx_quic_split_frame(ngx_connection_t *c > > ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); > > qb.chain = f->data; > > > > - f->data = ngx_quic_read_buffer(c, &qb, of->length); > > - if (f->data == NGX_CHAIN_ERROR) { > > + if (ngx_quic_read_buffer(c, &qb, of->length) == NGX_CHAIN_ERROR) { > > return NGX_ERROR; > > } > > > > Below is an updated version after discussion with Roman. > It uses conservative approach to maintain API and not rely on that the > passed output chain pointer will not be modified. Theoretically it can > if the limit inside ngx_quic_read_buffer() is zero on the first iteration > so that the passed chain pointer will be zeroed. Another possibility is > ngx_quic_split_chain() potentially may be rewritten to insert a new > element in ngx_quic_split_chain() first. > > # HG changeset patch > # User Sergey Kandaurov > # Date 1683029654 -14400 > # Tue May 02 16:14:14 2023 +0400 > # Branch quic > # Node ID 491623c24eb30876f60196e6ca99e5284de43cd5 > # Parent 8aa3363bc83d4354b3142e3972cce5c0ef523539 > QUIC: fixed split frames error handling. > > Do not corrupt frame data chain pointer on ngx_quic_read_buffer() error. > The error leads to closing a QUIC connection where the frame may be used > as part of the QUIC connection tear down, which envolves writing pending > frames, including this one. > > diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c > --- a/src/event/quic/ngx_event_quic_frames.c > +++ b/src/event/quic/ngx_event_quic_frames.c > @@ -319,6 +319,7 @@ ngx_int_t > ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) > { > size_t shrink; > + ngx_chain_t *out; > ngx_quic_frame_t *nf; > ngx_quic_buffer_t qb; > ngx_quic_ordered_frame_t *of, *onf; > @@ -359,11 +360,13 @@ ngx_quic_split_frame(ngx_connection_t *c > ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); > qb.chain = f->data; > > - f->data = ngx_quic_read_buffer(c, &qb, of->length); > - if (f->data == NGX_CHAIN_ERROR) { > + out = ngx_quic_read_buffer(c, &qb, of->length); > + if (out == NGX_CHAIN_ERROR) { > return NGX_ERROR; > } > > + f->data = out; > + > nf = ngx_quic_alloc_frame(c); > if (nf == NULL) { > return NGX_ERROR; > > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Both this and the patch #1 look good. -- Roman Arutyunyan From pluknet at nginx.com Tue May 2 15:34:52 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 2 May 2023 19:34:52 +0400 Subject: [PATCH] QUIC: optimized immediate close In-Reply-To: <0ae438bff8e8788f6972.1683035896@arut-laptop> References: <0ae438bff8e8788f6972.1683035896@arut-laptop> Message-ID: <2401DC1A-2234-45D6-B9B3-0B1552194AA6@nginx.com> > On 2 May 2023, at 17:58, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1683035693 -14400 > # Tue May 02 17:54:53 2023 +0400 > # Branch quic > # Node ID 0ae438bff8e8788f6972b2f8268194b571d728c0 > # Parent 558cdf07793de4108527ed6757fd3864e9694b93 > QUIC: optimized immediate close. > > Previously, before sending CONNECTION_CLOSE to client, all pending frames > were sent. This is redundant and could prevent CONNECTION_CLOSE from being > sent due to congestion control. Now pending frames are freed and > CONNECTION_CLOSE is sent without congestion control, as advised by RFC 9002: > > Packets containing frames besides ACK or CONNECTION_CLOSE frames > count toward congestion control limits and are considered to be in flight. > > 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 > @@ -482,6 +482,7 @@ ngx_quic_close_connection(ngx_connection > > /* drop packets from retransmit queues, no ack is expected */ > for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); > ngx_quic_free_frames(c, &qc->send_ctx[i].sent); > } > > 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 > @@ -882,7 +882,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); > @@ -898,27 +898,22 @@ ngx_quic_send_cc(ngx_connection_t *c) > return NGX_OK; > } > > - frame = ngx_quic_alloc_frame(c); > - if (frame == NULL) { > - return NGX_ERROR; > - } > + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); > > - 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; > } > > - ngx_quic_queue_frame(qc, frame); > - > qc->last_cc = ngx_current_msec; > > - return ngx_quic_output(c); > + return ngx_quic_frame_sendto(c, &frame, 0, qc->path); > } > It should be fine as long as we don't have pending frames neither on idle timeout (I expect we don't) nor on immediate close. Immediate close (rc == NGX_OK) are close handler, stateless reset, and receiving CONNECTION_CLOSE where we don't have to send anything. Immediate close (rc == NGX_ERROR) is something we expect to be sort of fatal errors where we should normally cease sending. To sum up, I think the change is good. -- Sergey Kandaurov From mdounin at mdounin.ru Tue May 2 21:45:43 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Wed, 3 May 2023 00:45:43 +0300 Subject: [PATCH] Tests: HTTP/2 tests with error_page and return In-Reply-To: References: <90aaa942972884dcd67b.1682952385@enoparse.local> Message-ID: Hello! On Tue, May 02, 2023 at 05:10:55PM +0400, Sergey Kandaurov wrote: > > On 2 May 2023, at 00:59, Maxim Dounin wrote: > > > > On Mon, May 01, 2023 at 06:46:25PM +0400, Sergey Kandaurov wrote: > > > >> # HG changeset patch > >> # User Sergey Kandaurov > >> # Date 1682952238 -14400 > >> # Mon May 01 18:43:58 2023 +0400 > >> # Node ID 90aaa942972884dcd67b6744fde39a154fec5d13 > >> # Parent 36a4563f7f005184547575f5ac4f22ef53a59c72 > >> Tests: HTTP/2 tests with error_page and return. > >> > >> diff --git a/h2_error_page.t b/h2_error_page.t > >> new file mode 100644 > >> --- /dev/null > >> +++ b/h2_error_page.t > >> @@ -0,0 +1,88 @@ > >> +#!/usr/bin/perl > >> + > >> +# (C) Sergey Kandaurov > >> +# (C) Nginx, Inc. > >> + > >> +# Tests for HTTP/2 protocol with error_page directive. > >> + > >> +############################################################################### > >> + > >> +use warnings; > >> +use strict; > >> + > >> +use Test::More; > >> + > >> +BEGIN { use FindBin; chdir($FindBin::Bin); } > >> + > >> +use lib 'lib'; > >> +use Test::Nginx; > >> +use Test::Nginx::HTTP2; > >> + > >> +############################################################################### > >> + > >> +select STDERR; $| = 1; > >> +select STDOUT; $| = 1; > >> + > >> +my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(2) > >> + ->write_file_expand('nginx.conf', <<'EOF'); > >> + > >> +%%TEST_GLOBALS%% > >> + > >> +daemon off; > >> + > >> +events { > >> +} > >> + > >> +http { > >> + %%TEST_GLOBALS_HTTP%% > >> + > >> + server { > >> + listen 127.0.0.1:8080 http2; > >> + server_name localhost; > >> + > >> + lingering_close off; > >> + > >> + error_page 400 = /close; > >> + > >> + location / { } > >> + > >> + location /close { > >> + return 444; > >> + } > >> + } > >> +} > >> + > >> +EOF > >> + > >> +$t->run(); > >> + > >> +############################################################################### > >> + > >> +my ($sid, $frames, $frame); > >> + > >> +# tests for socket leak with "return 444" in error_page > >> + > >> +# ticket #274 > >> + > >> +my $s1 = Test::Nginx::HTTP2->new(); > >> +$sid = $s1->new_stream({ headers => [ > >> + { name => ':method', value => 'GET' }, > >> + { name => ':path', value => '/' }, > >> + { name => ':authority', value => 'localhost' }]}); > >> +$frames = $s1->read(all => [{ type => 'RST_STREAM' }]); > >> + > >> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; > >> +is($frame->{sid}, $sid, 'error 400 return 444 - missing header'); > > > > This clearly needs details about the header being missed, as well > > as expected and observed behaviour, not just the ticket number. > > The description is provided in associated commit logs, a proper > source to seek for details, tagged with appropriate ticket numbers. > A brief description what happens here is given above. Even assuming commits are readily available (they are not in most cases), commit logs and even the code changes are not enough to see what actually missed here: that is, it worth to mention lack of mandatory ":scheme" pseudo-header. Also, it might be important to mention why the test is expected to fail without the fix (and if it's expected to fail), and why it succeeds with the fix. Note that the tickets in question are about connection being left open, and not about RST_STREAM not being sent. Note well that RST_STREAM is not something one might expect with "return 444;", and rather an implementation detail. A better approach might be to check instead for a connection being closed and not timed out on the client side (this might complicate things though, and might not worth the effort). > If you insist, we can add further details inline: > > # a socket leak observed on missing ngx_http_run_posted_requests() such as > # in ngx_http_v2_run_request(), e.g. if ngx_http_v2_construct_request_line() > # failed due to missing mandatory ":scheme" pseudo-header (ticket #274) These are indeed mostly implementation details. A better comment might be: # make sure there is no socket leak when the request is rejected # due to missing mandatory ":scheme" pseudo-header and "return 444;" # is used in error_page 400 (ticket #274) > > > >> + > >> +# ticket #2455 > >> + > >> +my $s2 = Test::Nginx::HTTP2->new(); > >> +$sid = $s2->new_stream({ method => 'foo' }); > >> +$frames = $s2->read(all => [{ type => 'RST_STREAM' }]); > >> + > >> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; > >> +is($frame->{sid}, $sid, 'error 400 return 444 - invalid header'); > > > > Same here. > > # another case with missing ngx_http_run_posted_requests() is in > # ngx_http_v2_state_process_header() error handling (ticket #2455), > # can be triggered with invalid pseudo-header Same here: # make sure there is no socket leak when the request is rejected # due to invalid method with lower-case letters and "return 444;" # is used in error_page 400 (ticket #2455) > > > > >> + > >> +$t->stop(); This also might worth an explicit comment. Something like # while keeping $s1 and $s2, stop nginx; this should result in # "open socket ... left in connection ..." alerts if any of these # sockets is still open should be good enough. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Wed May 3 01:26:18 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Wed, 3 May 2023 04:26:18 +0300 Subject: [PATCH 02 of 11] Tests: removed unneeded require from proxy_ssl_keepalive.t In-Reply-To: <44FD297A-6B6A-4625-A959-8265A51D7480@nginx.com> References: <6f0148ef1991d92a003c.1681702286@vm-bsd.mdounin.ru> <44FD297A-6B6A-4625-A959-8265A51D7480@nginx.com> Message-ID: Hello! On Tue, May 02, 2023 at 05:49:23PM +0400, Sergey Kandaurov wrote: > > > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > > > # HG changeset patch > > # User Maxim Dounin > > # Date 1681702250 -10800 > > # Mon Apr 17 06:30:50 2023 +0300 > > # Node ID 6f0148ef1991d92a003c8529c8cce9a8dd49e706 > > # Parent a01b7d84f4355073a00f43760fc512e03b4452c3 > > Tests: removed unneeded require from proxy_ssl_keepalive.t. > > > > diff --git a/proxy_ssl_keepalive.t b/proxy_ssl_keepalive.t > > --- a/proxy_ssl_keepalive.t > > +++ b/proxy_ssl_keepalive.t > > @@ -22,9 +22,6 @@ use Test::Nginx; > > select STDERR; $| = 1; > > select STDOUT; $| = 1; > > > > -eval { require IO::Socket::SSL; }; > > -plan(skip_all => 'IO::Socket::SSL not installed') if $@; > > - > > my $t = Test::Nginx->new()->has(qw/http http_ssl proxy upstream_keepalive/) > > ->has_daemon('openssl')->plan(3) > > ->write_file_expand('nginx.conf', <<'EOF'); > > We can as well remove it from h2_ssl_proxy_cache.t and h2_ssl_variables.t > after 45c80276d691, HTTP2 package handles that for us. > (Same approach used for the crypto layer of HTTP/3 tests.) Both h2_ssl_proxy_cache.t and h2_ssl_variables.t actually use IO::Socket::SSL, even if the actual use is within Test::Nginx::HTTP2, and will behave incorrectly if IO::Socket::SSL is not available (as currently written, they won't fail, but will skip individual tests with incorrect reasoning). As such, they do need these requires (and the corresponding plan(skip_all) call if IO::Socket::SSL is not available). In contrast, proxy_ssl_keepalive.t does not use IO::Socket::SSL at all. All client connections initiated by the test are plain text, and SSL is only used within nginx itself. As such, IO::Socket::SSL is not required to run the test (and hence the patch). -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Wed May 3 04:13:22 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:22 +0000 Subject: [njs] Made njs_string.h includable independently from njs_main.h. Message-ID: details: https://hg.nginx.org/njs/rev/5d814dca2327 branches: changeset: 2096:5d814dca2327 user: Dmitry Volyntsev date: Mon May 01 20:45:32 2023 -0700 description: Made njs_string.h includable independently from njs_main.h. diffstat: src/njs.h | 18 ++-- src/njs_regexp.c | 9 ++- src/njs_string.c | 60 +++++++++++++--- src/njs_string.h | 188 +++++++++++++++++++++--------------------------------- src/njs_value.h | 5 +- 5 files changed, 139 insertions(+), 141 deletions(-) diffs (402 lines): diff -r 4fa5ddc91108 -r 5d814dca2327 src/njs.h --- a/src/njs.h Thu Apr 27 17:28:52 2023 -0700 +++ b/src/njs.h Mon May 01 20:45:32 2023 -0700 @@ -27,14 +27,16 @@ #include -typedef uintptr_t njs_index_t; -typedef struct njs_vm_s njs_vm_t; -typedef struct njs_mod_s njs_mod_t; -typedef union njs_value_s njs_value_t; -typedef struct njs_function_s njs_function_t; -typedef struct njs_vm_shared_s njs_vm_shared_t; -typedef struct njs_object_prop_s njs_object_prop_t; -typedef struct njs_external_s njs_external_t; +typedef uintptr_t njs_index_t; +typedef struct njs_vm_s njs_vm_t; +typedef struct njs_mod_s njs_mod_t; +typedef union njs_value_s njs_value_t; +typedef struct njs_function_s njs_function_t; +typedef struct njs_vm_shared_s njs_vm_shared_t; +typedef struct njs_object_init_s njs_object_init_t; +typedef struct njs_object_prop_s njs_object_prop_t; +typedef struct njs_object_type_init_s njs_object_type_init_t; +typedef struct njs_external_s njs_external_t; /* * njs_opaque_value_t is the external storage type for native njs_value_t type. diff -r 4fa5ddc91108 -r 5d814dca2327 src/njs_regexp.c --- a/src/njs_regexp.c Thu Apr 27 17:28:52 2023 -0700 +++ b/src/njs_regexp.c Mon May 01 20:45:32 2023 -0700 @@ -1480,6 +1480,7 @@ njs_regexp_prototype_symbol_split(njs_vm { u_char *dst; int64_t e, i, p, q, ncaptures, length; + ssize_t len; uint32_t limit; njs_int_t ret; njs_bool_t sticky; @@ -1650,7 +1651,9 @@ njs_regexp_prototype_symbol_split(njs_vm end = &s.start[q]; } - ret = njs_string_split_part_add(vm, array, utf8, start, end - start); + len = njs_string_calc_length(utf8, start, end - start); + + ret = njs_array_string_add(vm, array, start, end - start, len); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -1698,7 +1701,9 @@ njs_regexp_prototype_symbol_split(njs_vm start = &s.start[p]; } - ret = njs_string_split_part_add(vm, array, utf8, start, end - start); + len = njs_string_calc_length(utf8, start, end - start); + + ret = njs_array_string_add(vm, array, start, end - start, len); if (njs_slow_path(ret != NJS_OK)) { return ret; } diff -r 4fa5ddc91108 -r 5d814dca2327 src/njs_string.c --- a/src/njs_string.c Thu Apr 27 17:28:52 2023 -0700 +++ b/src/njs_string.c Mon May 01 20:45:32 2023 -0700 @@ -256,6 +256,49 @@ njs_string_alloc(njs_vm_t *vm, njs_value } +uint32_t +njs_string_length(njs_value_t *string) +{ + uint32_t length, size; + + if (string->short_string.size != NJS_STRING_LONG) { + size = string->short_string.size; + length = string->short_string.length; + + } else { + size = string->long_string.size; + length = string->long_string.data->length; + } + + return (length == 0) ? size : length; +} + + +size_t +njs_string_prop(njs_string_prop_t *string, const njs_value_t *value) +{ + size_t size; + uintptr_t length; + + size = value->short_string.size; + + if (size != NJS_STRING_LONG) { + string->start = (u_char *) value->short_string.start; + length = value->short_string.length; + + } else { + string->start = (u_char *) value->long_string.data->start; + size = value->long_string.size; + length = value->long_string.data->length; + } + + string->size = size; + string->length = length; + + return (length == 0) ? size : length; +} + + void njs_string_truncate(njs_value_t *value, uint32_t size, uint32_t length) { @@ -3305,6 +3348,7 @@ njs_string_prototype_split(njs_vm_t *vm, njs_index_t unused, njs_value_t *retval) { size_t size; + ssize_t len; uint32_t limit; njs_int_t ret; njs_utf8_t utf8; @@ -3429,7 +3473,9 @@ found: size = p - start; - ret = njs_string_split_part_add(vm, array, utf8, start, size); + len = njs_string_calc_length(utf8, start, size); + + ret = njs_array_string_add(vm, array, start, size, len); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -3459,18 +3505,6 @@ done: njs_int_t -njs_string_split_part_add(njs_vm_t *vm, njs_array_t *array, njs_utf8_t utf8, - const u_char *start, size_t size) -{ - ssize_t length; - - length = njs_string_calc_length(utf8, start, size); - - return njs_array_string_add(vm, array, start, size, length); -} - - -njs_int_t njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched, njs_value_t *string, int64_t pos, njs_value_t *captures, int64_t ncaptures, njs_value_t *groups, njs_value_t *replacement, njs_value_t *retval) diff -r 4fa5ddc91108 -r 5d814dca2327 src/njs_string.h --- a/src/njs_string.h Thu Apr 27 17:28:52 2023 -0700 +++ b/src/njs_string.h Mon May 01 20:45:32 2023 -0700 @@ -104,94 +104,6 @@ typedef enum { } njs_trim_t; -njs_inline njs_bool_t -njs_is_byte_string(njs_string_prop_t *string) -{ - return (string->length == 0 && string->size != 0); -} - - -njs_inline njs_bool_t -njs_is_byte_or_ascii_string(njs_string_prop_t *string) -{ - return (string->length == 0 || string->length == string->size); -} - - -njs_inline uint32_t -njs_string_calc_length(njs_utf8_t utf8, const u_char *start, size_t size) -{ - ssize_t length; - - switch (utf8) { - - case NJS_STRING_BYTE: - return 0; - - case NJS_STRING_ASCII: - return size; - - case NJS_STRING_UTF8: - default: - length = njs_utf8_length(start, size); - - return (length >= 0) ? length : 0; - } -} - - -njs_inline uint32_t -njs_string_length(njs_value_t *string) -{ - uint32_t length, size; - - if (string->short_string.size != NJS_STRING_LONG) { - size = string->short_string.size; - length = string->short_string.length; - - } else { - size = string->long_string.size; - length = string->long_string.data->length; - } - - return (length == 0) ? size : length; -} - - -njs_inline njs_bool_t -njs_need_escape(const uint32_t *escape, uint32_t byte) -{ - return ((escape[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); -} - - -njs_inline u_char * -njs_string_encode(const uint32_t *escape, size_t size, const u_char *src, - u_char *dst) -{ - uint8_t byte; - static const u_char hex[16] = "0123456789ABCDEF"; - - do { - byte = *src++; - - if (njs_need_escape(escape, byte)) { - *dst++ = '%'; - *dst++ = hex[byte >> 4]; - *dst++ = hex[byte & 0xf]; - - } else { - *dst++ = byte; - } - - size--; - - } while (size != 0); - - return dst; -} - - njs_int_t njs_string_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size); u_char *njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint64_t size, @@ -203,6 +115,9 @@ njs_int_t njs_string_create(njs_vm_t *vm njs_int_t njs_string_create_chb(njs_vm_t *vm, njs_value_t *value, njs_chb_t *chain); +uint32_t njs_string_length(njs_value_t *string); +size_t njs_string_prop(njs_string_prop_t *string, const njs_value_t *value); + void njs_encode_hex(njs_str_t *dst, const njs_str_t *src); size_t njs_encode_hex_length(const njs_str_t *src, size_t *out_size); void njs_encode_base64(njs_str_t *dst, const njs_str_t *src); @@ -258,13 +173,81 @@ njs_int_t njs_string_atob(njs_vm_t *vm, njs_int_t njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -njs_int_t njs_string_split_part_add(njs_vm_t *vm, njs_array_t *array, - njs_utf8_t utf8, const u_char *start, size_t size); njs_int_t njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched, njs_value_t *string, int64_t pos, njs_value_t *captures, int64_t ncaptures, njs_value_t *groups, njs_value_t *replacement, njs_value_t *retval); +njs_inline njs_bool_t +njs_is_byte_string(njs_string_prop_t *string) +{ + return (string->length == 0 && string->size != 0); +} + + +njs_inline njs_bool_t +njs_is_byte_or_ascii_string(njs_string_prop_t *string) +{ + return (string->length == 0 || string->length == string->size); +} + + +njs_inline uint32_t +njs_string_calc_length(njs_utf8_t utf8, const u_char *start, size_t size) +{ + ssize_t length; + + switch (utf8) { + + case NJS_STRING_BYTE: + return 0; + + case NJS_STRING_ASCII: + return size; + + case NJS_STRING_UTF8: + default: + length = njs_utf8_length(start, size); + + return (length >= 0) ? length : 0; + } +} + + +njs_inline njs_bool_t +njs_need_escape(const uint32_t *escape, uint32_t byte) +{ + return ((escape[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0); +} + + +njs_inline u_char * +njs_string_encode(const uint32_t *escape, size_t size, const u_char *src, + u_char *dst) +{ + uint8_t byte; + static const u_char hex[16] = "0123456789ABCDEF"; + + do { + byte = *src++; + + if (njs_need_escape(escape, byte)) { + *dst++ = '%'; + *dst++ = hex[byte >> 4]; + *dst++ = hex[byte & 0xf]; + + } else { + *dst++ = byte; + } + + size--; + + } while (size != 0); + + return dst; +} + + njs_inline const u_char * njs_string_offset(njs_string_prop_t *string, int64_t index) { @@ -279,31 +262,6 @@ njs_string_offset(njs_string_prop_t *str } -njs_inline size_t -njs_string_prop(njs_string_prop_t *string, const njs_value_t *value) -{ - size_t size; - uintptr_t length; - - size = value->short_string.size; - - if (size != NJS_STRING_LONG) { - string->start = (u_char *) value->short_string.start; - length = value->short_string.length; - - } else { - string->start = (u_char *) value->long_string.data->start; - size = value->long_string.size; - length = value->long_string.data->length; - } - - string->size = size; - string->length = length; - - return (length == 0) ? size : length; -} - - extern const njs_object_init_t njs_string_instance_init; extern const njs_object_type_init_t njs_string_type_init; diff -r 4fa5ddc91108 -r 5d814dca2327 src/njs_value.h --- a/src/njs_value.h Thu Apr 27 17:28:52 2023 -0700 +++ b/src/njs_value.h Mon May 01 20:45:32 2023 -0700 @@ -88,7 +88,6 @@ typedef struct njs_regexp_s nj typedef struct njs_date_s njs_date_t; typedef struct njs_object_value_s njs_promise_t; typedef struct njs_property_next_s njs_property_next_t; -typedef struct njs_object_init_s njs_object_init_t; union njs_value_s { @@ -305,12 +304,12 @@ typedef union { } njs_object_prototype_t; -typedef struct { +struct njs_object_type_init_s { njs_function_t constructor; const njs_object_init_t *constructor_props; const njs_object_init_t *prototype_props; njs_object_prototype_t prototype_value; -} njs_object_type_init_t; +}; typedef enum { From xeioex at nginx.com Wed May 3 04:13:24 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:24 +0000 Subject: [njs] Public header cleanup. Message-ID: details: https://hg.nginx.org/njs/rev/1776189b0de6 branches: changeset: 2097:1776189b0de6 user: Dmitry Volyntsev date: Tue May 02 20:33:30 2023 -0700 description: Public header cleanup. diffstat: src/njs.h | 4 +--- src/njs_sprintf.h | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diffs (47 lines): diff -r 5d814dca2327 -r 1776189b0de6 src/njs.h --- a/src/njs.h Mon May 01 20:45:32 2023 -0700 +++ b/src/njs.h Tue May 02 20:33:30 2023 -0700 @@ -15,7 +15,7 @@ #define NJS_VERSION_NUMBER 0x000800 -#include /* STDOUT_FILENO, STDERR_FILENO */ +#include #include #include #include @@ -23,7 +23,6 @@ #include #include #include -#include #include @@ -396,7 +395,6 @@ NJS_EXPORT njs_int_t njs_vm_value(njs_vm njs_value_t *retval); NJS_EXPORT njs_function_t *njs_vm_function(njs_vm_t *vm, const njs_str_t *name); -NJS_EXPORT njs_value_t *njs_vm_retval(njs_vm_t *vm); NJS_EXPORT void njs_vm_throw(njs_vm_t *vm, const njs_value_t *value); NJS_EXPORT void njs_vm_error(njs_vm_t *vm, const char *fmt, ...); NJS_EXPORT void njs_vm_exception_get(njs_vm_t *vm, njs_value_t *retval); diff -r 5d814dca2327 -r 1776189b0de6 src/njs_sprintf.h --- a/src/njs_sprintf.h Mon May 01 20:45:32 2023 -0700 +++ b/src/njs_sprintf.h Tue May 02 20:33:30 2023 -0700 @@ -16,12 +16,12 @@ NJS_EXPORT int njs_dprint(int fd, u_char NJS_EXPORT int njs_dprintf(int fd, const char *fmt, ...); #define njs_print(buf, size) \ - njs_dprint(STDOUT_FILENO, (u_char *) buf, size) + njs_dprint(1 /* STDOUT_FILENO */, (u_char *) buf, size) #define njs_printf(fmt, ...) \ - njs_dprintf(STDOUT_FILENO, fmt, ##__VA_ARGS__) + njs_dprintf(1 /* STDOUT_FILENO */, fmt, ##__VA_ARGS__) #define njs_stderror(fmt, ...) \ - njs_dprintf(STDERR_FILENO, fmt, ##__VA_ARGS__) + njs_dprintf(2 /* STDERR_FILENO */, fmt, ##__VA_ARGS__) #endif /* _NJS_SPRINTF_H_INCLUDED_ */ From xeioex at nginx.com Wed May 3 04:13:26 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:26 +0000 Subject: [njs] Introduced njs_value_buffer_get(). Message-ID: details: https://hg.nginx.org/njs/rev/830c81bb573f branches: changeset: 2098:830c81bb573f user: Dmitry Volyntsev date: Mon May 01 17:54:48 2023 -0700 description: Introduced njs_value_buffer_get(). diffstat: external/njs_fs_module.c | 31 ++++++++++++------------------- src/njs.h | 2 ++ src/njs_buffer.c | 34 ++++++++++++++++++++++++++++++++-- src/njs_buffer.h | 4 ---- 4 files changed, 46 insertions(+), 25 deletions(-) diffs (147 lines): diff -r 1776189b0de6 -r 830c81bb573f external/njs_fs_module.c --- a/external/njs_fs_module.c Tue May 02 20:33:30 2023 -0700 +++ b/external/njs_fs_module.c Mon May 01 17:54:48 2023 -0700 @@ -1405,14 +1405,12 @@ static njs_int_t njs_fs_read(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval) { - int64_t fd, length, pos, offset; - ssize_t n; - njs_int_t ret; - njs_str_t data; - njs_uint_t fd_offset; - njs_value_t result, *buffer, *value; - njs_typed_array_t *array; - njs_array_buffer_t *array_buffer; + int64_t fd, length, pos, offset; + ssize_t n; + njs_int_t ret; + njs_str_t data; + njs_uint_t fd_offset; + njs_value_t result, *buffer, *value; fd_offset = !!(calltype == NJS_FS_DIRECT); @@ -1429,13 +1427,8 @@ njs_fs_read(njs_vm_t *vm, njs_value_t *a */ buffer = njs_arg(args, nargs, fd_offset + 1); - array = njs_buffer_slot(vm, buffer, "buffer"); - if (njs_slow_path(array == NULL)) { - return NJS_ERROR; - } - - array_buffer = njs_typed_array_writable(vm, array); - if (njs_slow_path(array_buffer == NULL)) { + ret = njs_value_buffer_get(vm, buffer, &data); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -1445,14 +1438,14 @@ njs_fs_read(njs_vm_t *vm, njs_value_t *a return ret; } - if (njs_slow_path(offset < 0 || (size_t) offset > array->byte_length)) { + if (njs_slow_path(offset < 0 || (size_t) offset > data.length)) { njs_range_error(vm, "offset is out of range (must be <= %z)", - array->byte_length); + data.length); return NJS_ERROR; } - data.length = array->byte_length - offset; - data.start = &array_buffer->u.u8[array->offset + offset]; + data.length -= offset; + data.start += offset; value = njs_arg(args, nargs, fd_offset + 3); diff -r 1776189b0de6 -r 830c81bb573f src/njs.h --- a/src/njs.h Tue May 02 20:33:30 2023 -0700 +++ b/src/njs.h Mon May 01 17:54:48 2023 -0700 @@ -423,6 +423,8 @@ NJS_EXPORT njs_int_t njs_vm_string_compa NJS_EXPORT njs_int_t njs_vm_value_array_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size); +NJS_EXPORT njs_int_t njs_value_buffer_get(njs_vm_t *vm, njs_value_t *value, + njs_str_t *dst); /* * Sets a Buffer value. * start data is not copied and should not be freed. diff -r 1776189b0de6 -r 830c81bb573f src/njs_buffer.c --- a/src/njs_buffer.c Tue May 02 20:33:30 2023 -0700 +++ b/src/njs_buffer.c Mon May 01 17:54:48 2023 -0700 @@ -183,7 +183,7 @@ njs_buffer_set(njs_vm_t *vm, njs_value_t } -njs_typed_array_t * +static njs_typed_array_t * njs_buffer_alloc(njs_vm_t *vm, size_t size, njs_bool_t zeroing) { njs_value_t value; @@ -621,7 +621,7 @@ njs_buffer_slot_internal(njs_vm_t *vm, n } -njs_typed_array_t * +static njs_typed_array_t * njs_buffer_slot(njs_vm_t *vm, njs_value_t *value, const char *name) { njs_typed_array_t *array; @@ -637,6 +637,36 @@ njs_buffer_slot(njs_vm_t *vm, njs_value_ } +njs_int_t +njs_value_buffer_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *dst) +{ + njs_typed_array_t *array; + njs_array_buffer_t *array_buffer; + + if (njs_slow_path(!(njs_is_typed_array(value) + || njs_is_data_view(value)))) + { + njs_type_error(vm, "first argument must be a Buffer or DataView"); + return NJS_ERROR; + } + + array = njs_typed_array(value); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + array_buffer = njs_typed_array_writable(vm, array); + if (njs_slow_path(array_buffer == NULL)) { + return NJS_ERROR; + } + + dst->length = array->byte_length; + dst->start = &array_buffer->u.u8[array->offset]; + + return NJS_OK; +} + + static njs_int_t njs_buffer_array_range(njs_vm_t *vm, njs_typed_array_t *array, const njs_value_t *start, const njs_value_t *end, const char *name, diff -r 1776189b0de6 -r 830c81bb573f src/njs_buffer.h --- a/src/njs_buffer.h Tue May 02 20:33:30 2023 -0700 +++ b/src/njs_buffer.h Mon May 01 17:54:48 2023 -0700 @@ -21,14 +21,10 @@ typedef struct { } njs_buffer_encoding_t; -njs_typed_array_t *njs_buffer_slot(njs_vm_t *vm, njs_value_t *value, - const char *name); njs_int_t njs_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size); njs_int_t njs_buffer_new(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size); -njs_typed_array_t *njs_buffer_alloc(njs_vm_t *vm, size_t size, - njs_bool_t zeroing); const njs_buffer_encoding_t *njs_buffer_encoding(njs_vm_t *vm, const njs_value_t *value, njs_bool_t thrw); From xeioex at nginx.com Wed May 3 04:13:27 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:27 +0000 Subject: [njs] Fixed typos introduced in 5e7fc8efebdc. Message-ID: details: https://hg.nginx.org/njs/rev/a86328810249 branches: changeset: 2099:a86328810249 user: Dmitry Volyntsev date: Tue May 02 20:50:46 2023 -0700 description: Fixed typos introduced in 5e7fc8efebdc. diffstat: external/njs_zlib_module.c | 20 ++++++++++---------- 1 files changed, 10 insertions(+), 10 deletions(-) diffs (93 lines): diff -r 830c81bb573f -r a86328810249 external/njs_zlib_module.c --- a/external/njs_zlib_module.c Mon May 01 17:54:48 2023 -0700 +++ b/external/njs_zlib_module.c Tue May 02 20:50:46 2023 -0700 @@ -14,7 +14,7 @@ static njs_int_t njs_zlib_ext_deflate(nj njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_zlib_ext_inflate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); -njs_int_t njs_zlib_contant(njs_vm_t *vm, njs_object_prop_t *prop, +njs_int_t njs_zlib_constant(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t njs_zlib_init(njs_vm_t *vm); static void *njs_zlib_alloc(void *opaque, u_int items, u_int size); @@ -28,7 +28,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_NO_COMPRESSION"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_NO_COMPRESSION, } }, @@ -38,7 +38,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_BEST_SPEED"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_BEST_SPEED, } }, @@ -48,7 +48,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_BEST_COMPRESSION"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_BEST_COMPRESSION, } }, @@ -58,7 +58,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_FILTERED"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_FILTERED, } }, @@ -68,7 +68,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_HUFFMAN_ONLY"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_HUFFMAN_ONLY, } }, @@ -78,7 +78,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_RLE"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_RLE, } }, @@ -88,7 +88,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_FIXED"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_FIXED, } }, @@ -98,7 +98,7 @@ static njs_external_t njs_ext_zlib_cons .name.string = njs_str("Z_DEFAULT_STRATEGY"), .enumerable = 1, .u.property = { - .handler = njs_zlib_contant, + .handler = njs_zlib_constant, .magic32 = Z_DEFAULT_STRATEGY, } }, @@ -514,7 +514,7 @@ fail: njs_int_t -njs_zlib_contant(njs_vm_t *vm, njs_object_prop_t *prop, +njs_zlib_constant(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { njs_value_number_set(retval, njs_vm_prop_magic32(prop)); From xeioex at nginx.com Wed May 3 04:13:29 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:29 +0000 Subject: [njs] Crypto: module is rewritten using public API. Message-ID: details: https://hg.nginx.org/njs/rev/b2cbf06ba017 branches: changeset: 2100:b2cbf06ba017 user: Dmitry Volyntsev date: Tue May 02 20:50:52 2023 -0700 description: Crypto: module is rewritten using public API. diffstat: external/njs_crypto_module.c | 149 ++++++++++++++++++++---------------------- src/test/njs_unit_test.c | 30 ++++---- 2 files changed, 87 insertions(+), 92 deletions(-) diffs (421 lines): diff -r a86328810249 -r b2cbf06ba017 external/njs_crypto_module.c --- a/external/njs_crypto_module.c Tue May 02 20:50:46 2023 -0700 +++ b/external/njs_crypto_module.c Tue May 02 20:50:52 2023 -0700 @@ -5,7 +5,12 @@ */ -#include +#include +#include +#include +#include +#include +#include typedef void (*njs_hash_init)(void *ctx); @@ -56,9 +61,9 @@ typedef struct { static njs_hash_alg_t *njs_crypto_algorithm(njs_vm_t *vm, - const njs_value_t *value); + njs_value_t *value); static njs_crypto_enc_t *njs_crypto_encoding(njs_vm_t *vm, - const njs_value_t *value); + njs_value_t *value); static njs_int_t njs_buffer_digest(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src); static njs_int_t njs_crypto_create_hash(njs_vm_t *vm, njs_value_t *args, @@ -298,9 +303,9 @@ njs_crypto_create_hash(njs_vm_t *vm, njs return NJS_ERROR; } - dgst = njs_mp_alloc(vm->mem_pool, sizeof(njs_digest_t)); + dgst = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_digest_t)); if (njs_slow_path(dgst == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -320,24 +325,22 @@ njs_hash_prototype_update(njs_vm_t *vm, njs_str_t data; njs_int_t ret; njs_hmac_t *ctx; - njs_value_t *this, dst; + njs_value_t *this, *value; njs_digest_t *dgst; - njs_typed_array_t *array; - const njs_value_t *value; - njs_array_buffer_t *buffer; - const njs_buffer_encoding_t *encoding; + njs_opaque_value_t result; + const njs_buffer_encoding_t *enc; this = njs_argument(args, 0); if (!hmac) { dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, this); if (njs_slow_path(dgst == NULL)) { - njs_type_error(vm, "\"this\" is not a hash object"); + njs_vm_error(vm, "\"this\" is not a hash object"); return NJS_ERROR; } if (njs_slow_path(dgst->alg == NULL)) { - njs_error(vm, "Digest already called"); + njs_vm_error(vm, "Digest already called"); return NJS_ERROR; } @@ -346,12 +349,12 @@ njs_hash_prototype_update(njs_vm_t *vm, } else { ctx = njs_vm_external(vm, njs_crypto_hmac_proto_id, this); if (njs_slow_path(ctx == NULL)) { - njs_type_error(vm, "\"this\" is not a hmac object"); + njs_vm_error(vm, "\"this\" is not a hmac object"); return NJS_ERROR; } if (njs_slow_path(ctx->alg == NULL)) { - njs_error(vm, "Digest already called"); + njs_vm_error(vm, "Digest already called"); return NJS_ERROR; } @@ -360,37 +363,27 @@ njs_hash_prototype_update(njs_vm_t *vm, value = njs_arg(args, nargs, 1); - switch (value->type) { - case NJS_STRING: - encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2), 1); - if (njs_slow_path(encoding == NULL)) { + if (njs_value_is_string(value)) { + enc = njs_buffer_encoding(vm, njs_arg(args, nargs, 2), 1); + if (njs_slow_path(enc == NULL)) { return NJS_ERROR; } - ret = njs_buffer_decode_string(vm, value, &dst, encoding); + ret = njs_buffer_decode_string(vm, value, njs_value_arg(&result), enc); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - njs_string_get(&dst, &data); - break; + njs_value_string_get(njs_value_arg(&result), &data); - case NJS_TYPED_ARRAY: - case NJS_DATA_VIEW: - array = njs_typed_array(value); - buffer = array->buffer; - if (njs_slow_path(njs_is_detached_buffer(buffer))) { - njs_type_error(vm, "detached buffer"); + } else if (njs_value_is_buffer(value)) { + ret = njs_value_buffer_get(vm, value, &data); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - data.start = &buffer->u.u8[array->offset]; - data.length = array->byte_length; - break; - - default: - njs_type_error(vm, "data argument \"%s\" is not a string " - "or Buffer-like object", njs_type_string(value->type)); + } else { + njs_vm_error(vm, "data is not a string or Buffer-like object"); return NJS_ERROR; } @@ -425,7 +418,7 @@ njs_hash_prototype_digest(njs_vm_t *vm, if (!hmac) { dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, this); if (njs_slow_path(dgst == NULL)) { - njs_type_error(vm, "\"this\" is not a hash object"); + njs_vm_error(vm, "\"this\" is not a hash object"); return NJS_ERROR; } @@ -438,7 +431,7 @@ njs_hash_prototype_digest(njs_vm_t *vm, } else { ctx = njs_vm_external(vm, njs_crypto_hmac_proto_id, this); if (njs_slow_path(ctx == NULL)) { - njs_type_error(vm, "\"this\" is not a hmac object"); + njs_vm_error(vm, "\"this\" is not a hmac object"); return NJS_ERROR; } @@ -477,7 +470,8 @@ njs_hash_prototype_digest(njs_vm_t *vm, exception: - njs_error(vm, "Digest already called"); + njs_vm_error(vm, "Digest already called"); + return NJS_ERROR; } @@ -490,18 +484,18 @@ njs_hash_prototype_copy(njs_vm_t *vm, nj dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, njs_argument(args, 0)); if (njs_slow_path(dgst == NULL)) { - njs_type_error(vm, "\"this\" is not a hash object"); + njs_vm_error(vm, "\"this\" is not a hash object"); return NJS_ERROR; } if (njs_slow_path(dgst->alg == NULL)) { - njs_error(vm, "Digest already called"); + njs_vm_error(vm, "Digest already called"); return NJS_ERROR; } copy = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_digest_t)); if (njs_slow_path(copy == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -516,14 +510,15 @@ static njs_int_t njs_crypto_create_hmac(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_str_t key; - njs_uint_t i; - njs_hmac_t *ctx; - njs_hash_alg_t *alg; - njs_typed_array_t *array; - const njs_value_t *value; - njs_array_buffer_t *buffer; - u_char digest[32], key_buf[64]; + njs_int_t ret; + njs_str_t key; + njs_uint_t i; + njs_hmac_t *ctx; + njs_value_t *value; + njs_hash_alg_t *alg; + njs_opaque_value_t result; + const njs_buffer_encoding_t *enc; + u_char digest[32], key_buf[64]; alg = njs_crypto_algorithm(vm, njs_arg(args, nargs, 1)); if (njs_slow_path(alg == NULL)) { @@ -532,34 +527,34 @@ njs_crypto_create_hmac(njs_vm_t *vm, njs value = njs_arg(args, nargs, 2); - switch (value->type) { - case NJS_STRING: - njs_string_get(value, &key); - break; + if (njs_value_is_string(value)) { + enc = njs_buffer_encoding(vm, njs_value_arg(&njs_value_undefined), 1); + if (njs_slow_path(enc == NULL)) { + return NJS_ERROR; + } - case NJS_TYPED_ARRAY: - case NJS_DATA_VIEW: - array = njs_typed_array(value); - buffer = array->buffer; - if (njs_slow_path(njs_is_detached_buffer(buffer))) { - njs_type_error(vm, "detached buffer"); + ret = njs_buffer_decode_string(vm, value, njs_value_arg(&result), enc); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - key.start = &buffer->u.u8[array->offset]; - key.length = array->byte_length; - break; + njs_value_string_get(njs_value_arg(&result), &key); - default: - njs_type_error(vm, "key argument \"%s\" is not a string " - "or Buffer-like object", njs_type_string(value->type)); + } else if (njs_value_is_buffer(value)) { + ret = njs_value_buffer_get(vm, value, &key); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + } else { + njs_vm_error(vm, "key is not a string or Buffer-like object"); return NJS_ERROR; } - ctx = njs_mp_alloc(vm->mem_pool, sizeof(njs_hmac_t)); + ctx = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_hmac_t)); if (njs_slow_path(ctx == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -596,17 +591,17 @@ njs_crypto_create_hmac(njs_vm_t *vm, njs static njs_hash_alg_t * -njs_crypto_algorithm(njs_vm_t *vm, const njs_value_t *value) +njs_crypto_algorithm(njs_vm_t *vm, njs_value_t *value) { njs_str_t name; njs_hash_alg_t *e; - if (njs_slow_path(!njs_is_string(value))) { - njs_type_error(vm, "algorithm must be a string"); + if (njs_slow_path(!njs_value_is_string(value))) { + njs_vm_error(vm, "algorithm must be a string"); return NULL; } - njs_string_get(value, &name); + njs_value_string_get(value, &name); for (e = &njs_hash_algorithms[0]; e->name.length != 0; e++) { if (njs_strstr_eq(&name, &e->name)) { @@ -614,28 +609,28 @@ njs_crypto_algorithm(njs_vm_t *vm, const } } - njs_type_error(vm, "not supported algorithm: \"%V\"", &name); + njs_vm_error(vm, "not supported algorithm: \"%V\"", &name); return NULL; } static njs_crypto_enc_t * -njs_crypto_encoding(njs_vm_t *vm, const njs_value_t *value) +njs_crypto_encoding(njs_vm_t *vm, njs_value_t *value) { njs_str_t name; njs_crypto_enc_t *e; - if (njs_slow_path(!njs_is_string(value))) { - if (njs_is_defined(value)) { - njs_type_error(vm, "encoding must be a string"); + if (njs_slow_path(!njs_value_is_string(value))) { + if (!njs_value_is_undefined(value)) { + njs_vm_error(vm, "encoding must be a string"); return NULL; } return &njs_encodings[0]; } - njs_string_get(value, &name); + njs_value_string_get(value, &name); for (e = &njs_encodings[1]; e->name.length != 0; e++) { if (njs_strstr_eq(&name, &e->name)) { @@ -643,7 +638,7 @@ njs_crypto_encoding(njs_vm_t *vm, const } } - njs_type_error(vm, "Unknown digest encoding: \"%V\"", &name); + njs_vm_error(vm, "Unknown digest encoding: \"%V\"", &name); return NULL; } diff -r a86328810249 -r b2cbf06ba017 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Tue May 02 20:50:46 2023 -0700 +++ b/src/test/njs_unit_test.c Tue May 02 20:50:52 2023 -0700 @@ -20436,25 +20436,25 @@ static njs_unit_test_t njs_crypto_modul "'d9f5aeb06abebb3be3f38adec9a2e3b94228d52193be923eb4e24c9b56ee0930']]") }, { njs_str("var h = require('crypto').createHash()"), - njs_str("TypeError: algorithm must be a string") }, + njs_str("Error: algorithm must be a string") }, { njs_str("var h = require('crypto').createHash([])"), - njs_str("TypeError: algorithm must be a string") }, + njs_str("Error: algorithm must be a string") }, { njs_str("var h = require('crypto').createHash('sha512')"), - njs_str("TypeError: not supported algorithm: \"sha512\"") }, + njs_str("Error: not supported algorithm: \"sha512\"") }, { njs_str("var h = require('crypto').createHash('sha1');" "h.update()"), - njs_str("TypeError: data argument \"undefined\" is not a string or Buffer-like object") }, + njs_str("Error: data is not a string or Buffer-like object") }, { njs_str("var h = require('crypto').createHash('sha1');" "h.update({})"), - njs_str("TypeError: data argument \"object\" is not a string or Buffer-like object") }, + njs_str("Error: data is not a string or Buffer-like object") }, { njs_str("var h = require('crypto').createHash('sha1');" "h.update('A').digest('latin1')"), - njs_str("TypeError: Unknown digest encoding: \"latin1\"") }, + njs_str("Error: Unknown digest encoding: \"latin1\"") }, { njs_str("require('crypto').createHash('sha1').digest() instanceof Buffer"), njs_str("true") }, @@ -20554,16 +20554,16 @@ static njs_unit_test_t njs_crypto_modul njs_str("5647b6c429701ff512f0f18232b4507065d2376ca8899a816a0a6e721bf8ddcc") }, { njs_str("var h = require('crypto').createHmac()"), - njs_str("TypeError: algorithm must be a string") }, + njs_str("Error: algorithm must be a string") }, { njs_str("var h = require('crypto').createHmac([])"), - njs_str("TypeError: algorithm must be a string") }, + njs_str("Error: algorithm must be a string") }, { njs_str("var h = require('crypto').createHmac('sha512', '')"), - njs_str("TypeError: not supported algorithm: \"sha512\"") }, + njs_str("Error: not supported algorithm: \"sha512\"") }, { njs_str("var h = require('crypto').createHmac('sha1', [])"), - njs_str("TypeError: key argument \"array\" is not a string or Buffer-like object") }, + njs_str("Error: key is not a string or Buffer-like object") }, { njs_str("var h = require('crypto').createHmac('sha1', 'secret key');" "h.update('A').digest('hex'); h.digest('hex')"), @@ -20578,7 +20578,7 @@ static njs_unit_test_t njs_crypto_modul { njs_str("var cr = require('crypto'); var h = cr.createHash('sha1');" "h.update.call(cr.createHmac('sha1', 's'), '')"), - njs_str("TypeError: \"this\" is not a hash object") }, + njs_str("Error: \"this\" is not a hash object") }, }; static njs_unit_test_t njs_querystring_module_test[] = @@ -23233,24 +23233,24 @@ static njs_unit_test_t njs_backtraces_t " at main (:1)\n") }, { njs_str("require('crypto').createHash('sha')"), - njs_str("TypeError: not supported algorithm: \"sha\"\n" + njs_str("Error: not supported algorithm: \"sha\"\n" " at crypto.createHash (native)\n" " at main (:1)\n") }, { njs_str("var h = require('crypto').createHash('sha1');" "h.update([])"), - njs_str("TypeError: data argument \"array\" is not a string or Buffer-like object\n" + njs_str("Error: data is not a string or Buffer-like object\n" " at Hash.update (native)\n" " at main (:1)\n") }, { njs_str("require('crypto').createHmac('sha1', [])"), - njs_str("TypeError: key argument \"array\" is not a string or Buffer-like object\n" + njs_str("Error: key is not a string or Buffer-like object\n" " at crypto.createHmac (native)\n" " at main (:1)\n") }, { njs_str("var h = require('crypto').createHmac('sha1', 'secret');" "h.update([])"), - njs_str("TypeError: data argument \"array\" is not a string or Buffer-like object\n" + njs_str("Error: data is not a string or Buffer-like object\n" " at Hmac.update (native)\n" " at main (:1)\n") }, From xeioex at nginx.com Wed May 3 04:13:31 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:31 +0000 Subject: [njs] QueryString: module is rewritten using public API. Message-ID: details: https://hg.nginx.org/njs/rev/fd956d2a25a3 branches: changeset: 2101:fd956d2a25a3 user: Dmitry Volyntsev date: Tue May 02 20:50:55 2023 -0700 description: QueryString: module is rewritten using public API. diffstat: external/njs_query_string_module.c | 561 +++++++++++++++--------------------- src/njs.h | 8 + src/njs_value.c | 17 + src/njs_value_conversion.h | 25 - src/njs_vm.c | 25 + src/test/njs_unit_test.c | 10 +- 6 files changed, 289 insertions(+), 357 deletions(-) diffs (truncated from 1114 to 1000 lines): diff -r b2cbf06ba017 -r fd956d2a25a3 external/njs_query_string_module.c --- a/external/njs_query_string_module.c Tue May 02 20:50:52 2023 -0700 +++ b/external/njs_query_string_module.c Tue May 02 20:50:55 2023 -0700 @@ -6,7 +6,8 @@ */ -#include +#include +#include static njs_int_t njs_query_string_parser(njs_vm_t *vm, u_char *query, @@ -108,48 +109,15 @@ njs_module_t njs_query_string_module = }; -static const njs_value_t njs_escape_str = njs_string("escape"); -static const njs_value_t njs_unescape_str = njs_string("unescape"); -static const njs_value_t njs_encode_uri_str = - njs_long_string("encodeURIComponent"); -static const njs_value_t njs_decode_uri_str = - njs_long_string("decodeURIComponent"); -static const njs_value_t njs_max_keys_str = njs_string("maxKeys"); +static const njs_str_t njs_escape_str = njs_str("escape"); +static const njs_str_t njs_unescape_str = njs_str("unescape"); +static const njs_str_t njs_encode_uri_str = njs_str("encodeURIComponent"); +static const njs_str_t njs_decode_uri_str = njs_str("decodeURIComponent"); +static const njs_str_t njs_max_keys_str = njs_str("maxKeys"); static const njs_str_t njs_sep_default = njs_str("&"); static const njs_str_t njs_eq_default = njs_str("="); -static const njs_value_t njs_unescape_default = - njs_native_function(njs_query_string_unescape, 1); - - -static njs_object_t * -njs_query_string_object_alloc(njs_vm_t *vm) -{ - njs_object_t *obj; - - obj = njs_mp_alloc(vm->mem_pool, sizeof(njs_object_t)); - - if (njs_fast_path(obj != NULL)) { - njs_lvlhsh_init(&obj->hash); - njs_lvlhsh_init(&obj->shared_hash); - obj->type = NJS_OBJECT; - obj->shared = 0; - obj->extensible = 1; - obj->error_data = 0; - obj->fast_array = 0; - - obj->__proto__ = NULL; - obj->slots = NULL; - - return obj; - } - - njs_memory_error(vm); - - return NULL; -} - static njs_int_t njs_query_string_decode(njs_vm_t *vm, njs_value_t *value, const u_char *start, @@ -157,7 +125,6 @@ njs_query_string_decode(njs_vm_t *vm, nj { u_char *dst; size_t length; - ssize_t str_size; uint32_t cp; njs_int_t ret; njs_chb_t chain; @@ -185,7 +152,7 @@ njs_query_string_decode(njs_vm_t *vm, nj -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; - njs_chb_init(&chain, vm->mem_pool); + njs_chb_init(&chain, njs_vm_memory_pool(vm)); njs_utf8_decode_init(&ctx); cp = 0; @@ -240,21 +207,7 @@ njs_query_string_decode(njs_vm_t *vm, nj length++; } - str_size = njs_chb_size(&chain); - if (njs_slow_path(str_size < 0)) { - goto failed; - } - - dst = njs_string_alloc(vm, value, str_size, length); - if (njs_slow_path(dst == NULL)) { - goto failed; - } - - njs_chb_join_to(&chain, dst); - - ret = NJS_OK; - -failed: + ret = njs_vm_value_string_create_chb(vm, value, &chain); njs_chb_destroy(&chain); @@ -265,7 +218,18 @@ failed: njs_inline njs_bool_t njs_query_string_is_native_decoder(njs_function_t *decoder) { - return decoder->native && decoder->u.native == njs_query_string_unescape; + njs_opaque_value_t function; + njs_function_native_t native; + + if (decoder == NULL) { + return 1; + } + + njs_value_function_set(njs_value_arg(&function), decoder); + + native = njs_value_native_function(njs_value_arg(&function)); + + return native == njs_query_string_unescape; } @@ -274,80 +238,107 @@ njs_query_string_append(njs_vm_t *vm, nj size_t key_size, const u_char *val, size_t val_size, njs_function_t *decoder) { - uint32_t key_length, val_length; - njs_int_t ret; - njs_array_t *array; - njs_value_t name, value, retval; + njs_int_t ret; + njs_value_t *push; + njs_opaque_value_t array, name, value, retval; if (njs_query_string_is_native_decoder(decoder)) { - ret = njs_query_string_decode(vm, &name, key, key_size); + ret = njs_query_string_decode(vm, njs_value_arg(&name), key, key_size); if (njs_slow_path(ret != NJS_OK)) { return ret; } - ret = njs_query_string_decode(vm, &value, val, val_size); + ret = njs_query_string_decode(vm, njs_value_arg(&value), val, val_size); if (njs_slow_path(ret != NJS_OK)) { return ret; } } else { - key_length = njs_max(njs_utf8_length(key, key_size), 0); - ret = njs_string_new(vm, &name, key, key_size, key_length); + ret = njs_vm_value_string_create(vm, njs_value_arg(&name), key, + key_size); if (njs_slow_path(ret != NJS_OK)) { return ret; } if (key_size > 0) { - ret = njs_function_call(vm, decoder, &njs_value_undefined, &name, 1, - &name); + ret = njs_vm_invoke(vm, decoder, njs_value_arg(&name), 1, + njs_value_arg(&name)); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (!njs_is_string(&name)) { - njs_value_to_string(vm, &name, &name); + if (!njs_value_is_string(njs_value_arg(&name))) { + ret = njs_value_to_string(vm, njs_value_arg(&name), + njs_value_arg(&name)); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } } - val_length = njs_max(njs_utf8_length(val, val_size), 0); - ret = njs_string_new(vm, &value, val, val_size, val_length); + ret = njs_vm_value_string_create(vm, njs_value_arg(&value), val, + val_size); if (njs_slow_path(ret != NJS_OK)) { return ret; } if (val_size > 0) { - ret = njs_function_call(vm, decoder, &njs_value_undefined, &value, - 1, &value); + ret = njs_vm_invoke(vm, decoder, njs_value_arg(&value), 1, + njs_value_arg(&value)); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (!njs_is_string(&value)) { - njs_value_to_string(vm, &value, &value); + if (!njs_value_is_string(njs_value_arg(&value))) { + ret = njs_value_to_string(vm, njs_value_arg(&value), + njs_value_arg(&value)); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } } } - ret = njs_value_property(vm, object, &name, &retval); + ret = njs_value_property(vm, object, njs_value_arg(&name), + njs_value_arg(&retval)); if (ret == NJS_OK) { - if (njs_is_array(&retval)) { - return njs_array_add(vm, njs_array(&retval), &value); + if (njs_value_is_array(njs_value_arg(&retval))) { + push = njs_vm_array_push(vm, njs_value_arg(&retval)); + if (njs_slow_path(push == NULL)) { + return NJS_ERROR; + } + + njs_value_assign(push, njs_value_arg(&value)); + + return NJS_OK; } - array = njs_array_alloc(vm, 1, 2, 0); - if (njs_slow_path(array == NULL)) { + ret = njs_vm_array_alloc(vm, njs_value_arg(&array), 2); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + push = njs_vm_array_push(vm, njs_value_arg(&array)); + if (njs_slow_path(push == NULL)) { return NJS_ERROR; } - array->start[0] = retval; - array->start[1] = value; + njs_value_assign(push, njs_value_arg(&retval)); - njs_set_array(&value, array); + push = njs_vm_array_push(vm, njs_value_arg(&array)); + if (njs_slow_path(push == NULL)) { + return NJS_ERROR; + } + + njs_value_assign(push, njs_value_arg(&value)); + + njs_value_assign(&value, &array); } - return njs_value_property_set(vm, object, &name, &value); + return njs_value_property_set(vm, object, njs_value_arg(&name), + njs_value_arg(&value)); } @@ -384,12 +375,12 @@ static njs_int_t njs_query_string_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - int64_t max_keys; - njs_int_t ret; - njs_str_t str, sep, eq; - njs_value_t value, *this, *string, *options, *arg; - njs_value_t val_sep, val_eq; - njs_function_t *decode; + int64_t max_keys; + njs_int_t ret; + njs_str_t str, sep, eq; + njs_value_t *this, *string, *options, *arg, *val; + njs_function_t *decode; + njs_opaque_value_t value, val_sep, val_eq; decode = NULL; max_keys = 1000; @@ -397,8 +388,8 @@ njs_query_string_parse(njs_vm_t *vm, njs this = njs_argument(args, 0); string = njs_arg(args, nargs, 1); - if (njs_is_string(string)) { - njs_string_get(string, &str); + if (njs_value_is_string(string)) { + njs_value_string_get(string, &str); } else { str = njs_str_value(""); @@ -408,71 +399,68 @@ njs_query_string_parse(njs_vm_t *vm, njs eq = njs_eq_default; arg = njs_arg(args, nargs, 2); - if (!njs_is_null_or_undefined(arg)) { - ret = njs_value_to_string(vm, &val_sep, arg); + if (!njs_value_is_null_or_undefined(arg)) { + ret = njs_value_to_string(vm, njs_value_arg(&val_sep), arg); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (njs_string_length(&val_sep) != 0) { - njs_string_get(&val_sep, &sep); + if (njs_string_length(njs_value_arg(&val_sep)) != 0) { + njs_value_string_get(njs_value_arg(&val_sep), &sep); } } arg = njs_arg(args, nargs, 3); - if (!njs_is_null_or_undefined(arg)) { - ret = njs_value_to_string(vm, &val_eq, arg); + if (!njs_value_is_null_or_undefined(arg)) { + ret = njs_value_to_string(vm, njs_value_arg(&val_eq), arg); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (njs_string_length(&val_eq) != 0) { - njs_string_get(&val_eq, &eq); + if (njs_string_length(njs_value_arg(&val_eq)) != 0) { + njs_value_string_get(njs_value_arg(&val_eq), &eq); } } options = njs_arg(args, nargs, 4); - if (njs_is_object(options)) { - ret = njs_value_property(vm, options, njs_value_arg(&njs_max_keys_str), - &value); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } - - ret = njs_value_to_integer(vm, &value, &max_keys); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + if (njs_value_is_object(options)) { + val = njs_vm_object_prop(vm, options, &njs_max_keys_str, &value); - if (max_keys == 0) { - max_keys = INT64_MAX; - } - - ret = njs_value_property(vm, options, - njs_value_arg(&njs_decode_uri_str), &value); - - if (ret == NJS_OK) { - if (njs_slow_path(!njs_is_function(&value))) { - njs_type_error(vm, - "option decodeURIComponent is not a function"); + if (val != NULL) { + if (!njs_value_is_valid_number(val)) { + njs_vm_error(vm, "is not a number"); return NJS_ERROR; } - decode = njs_function(&value); + max_keys = njs_value_number(val); + + if (max_keys == 0) { + max_keys = INT64_MAX; + } + } + + val = njs_vm_object_prop(vm, options, &njs_decode_uri_str, &value); + + if (val != NULL) { + if (njs_slow_path(!njs_value_is_function(val))) { + njs_vm_error(vm, "option decodeURIComponent is not a function"); + return NJS_ERROR; + } + + decode = njs_value_function(val); } } if (decode == NULL) { - ret = njs_value_property(vm, this, njs_value_arg(&njs_unescape_str), - &value); + val = njs_vm_object_prop(vm, this, &njs_unescape_str, &value); - if (ret != NJS_OK || !njs_is_function(&value)) { - njs_type_error(vm, "QueryString.unescape is not a function"); + if (val == NULL || !njs_value_is_function(val)) { + njs_vm_error(vm, "QueryString.unescape is not a function"); return NJS_ERROR; } - decode = njs_function(&value); + decode = njs_value_function(val); } return njs_query_string_parser(vm, str.start, str.start + str.length, @@ -485,9 +473,7 @@ njs_vm_query_string_parse(njs_vm_t *vm, njs_value_t *retval) { return njs_query_string_parser(vm, start, end, &njs_sep_default, - &njs_eq_default, - njs_function(&njs_unescape_default), - 1000, retval); + &njs_eq_default, NULL, 1000, retval); } @@ -496,20 +482,16 @@ njs_query_string_parser(njs_vm_t *vm, u_ const njs_str_t *sep, const njs_str_t *eq, njs_function_t *decode, njs_uint_t max_keys, njs_value_t *retval) { - size_t size; - u_char *part, *key, *val; - njs_int_t ret; - njs_uint_t count; - njs_value_t obj; - njs_object_t *object; + size_t size; + u_char *part, *key, *val; + njs_int_t ret; + njs_uint_t count; - object = njs_query_string_object_alloc(vm); - if (njs_slow_path(object == NULL)) { + ret = njs_vm_object_alloc(vm, retval, NULL); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - njs_set_object(&obj, object); - count = 0; key = query; @@ -533,7 +515,7 @@ njs_query_string_parser(njs_vm_t *vm, u_ val += eq->length; } - ret = njs_query_string_append(vm, &obj, key, size, val, part - val, + ret = njs_query_string_append(vm, retval, key, size, val, part - val, decode); if (njs_slow_path(ret != NJS_OK)) { return ret; @@ -545,8 +527,6 @@ njs_query_string_parser(njs_vm_t *vm, u_ } while (key < end); - njs_set_object(retval, object); - return NJS_OK; } @@ -580,7 +560,7 @@ njs_query_string_encode(njs_chb_t *chain } if (str->length == 0) { - return 0; + return NJS_OK; } p = str->start; @@ -601,21 +581,26 @@ njs_query_string_encode(njs_chb_t *chain if (size == str->length) { memcpy(start, str->start, str->length); njs_chb_written(chain, str->length); - return str->length; + return NJS_OK; } (void) njs_string_encode(escape, str->length, str->start, start); njs_chb_written(chain, size); - return size; + return NJS_OK; } njs_inline njs_bool_t njs_query_string_is_native_encoder(njs_function_t *encoder) { - return encoder->native && encoder->u.native == njs_query_string_escape; + njs_opaque_value_t function; + + njs_value_function_set(njs_value_arg(&function), encoder); + + return njs_value_native_function(njs_value_arg(&function)) + == njs_query_string_escape; } @@ -623,11 +608,11 @@ njs_inline njs_int_t njs_query_string_encoder_call(njs_vm_t *vm, njs_chb_t *chain, njs_function_t *encoder, njs_value_t *string) { - njs_str_t str; - njs_int_t ret; - njs_value_t retval; + njs_str_t str; + njs_int_t ret; + njs_opaque_value_t retval; - if (njs_slow_path(!njs_is_string(string))) { + if (njs_slow_path(!njs_value_is_string(string))) { ret = njs_value_to_string(vm, string, string); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; @@ -635,87 +620,62 @@ njs_query_string_encoder_call(njs_vm_t * } if (njs_fast_path(njs_query_string_is_native_encoder(encoder))) { - njs_string_get(string, &str); + njs_value_string_get(string, &str); return njs_query_string_encode(chain, &str); } - ret = njs_function_call(vm, encoder, &njs_value_undefined, string, 1, - &retval); + ret = njs_vm_invoke(vm, encoder, string, 1, njs_value_arg(&retval)); if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; + return ret; } - if (njs_slow_path(!njs_is_string(&retval))) { - ret = njs_value_to_string(vm, &retval, &retval); + if (njs_slow_path(!njs_value_is_string(njs_value_arg(&retval)))) { + ret = njs_value_to_string(vm, njs_value_arg(&retval), + njs_value_arg(&retval)); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } } - njs_string_get(&retval, &str); - - ret = njs_utf8_length(str.start, str.length); - if (ret < 0) { - njs_type_error(vm, "got non-UTF8 string from encoder"); - return NJS_ERROR; - } + njs_value_string_get(njs_value_arg(&retval), &str); njs_chb_append_str(chain, &str); - return ret; + return NJS_OK; } njs_inline njs_int_t njs_query_string_push(njs_vm_t *vm, njs_chb_t *chain, njs_value_t *key, - njs_value_t *value, njs_string_prop_t *eq, njs_function_t *encoder) + njs_value_t *value, njs_str_t *eq, njs_function_t *encoder) { - double num; - njs_int_t ret, length; - - length = 0; + njs_int_t ret; ret = njs_query_string_encoder_call(vm, chain, encoder, key); - if (njs_slow_path(ret < 0)) { + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - length += ret; - - njs_chb_append(chain, eq->start, eq->size); - length += eq->length; + njs_chb_append(chain, eq->start, eq->length); - switch (value->type) { - case NJS_NUMBER: - num = njs_number(value); - if (njs_slow_path(isnan(num) || isinf(num))) { - break; + if (njs_value_is_valid_number(value) + || njs_value_is_boolean(value) + || njs_value_is_string(value)) + { + if (!njs_value_is_string(value)) { + ret = njs_value_to_string(vm, value, value); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } } - /* Fall through. */ - - case NJS_BOOLEAN: - ret = njs_primitive_value_to_string(vm, value, value); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } - - /* Fall through. */ - - case NJS_STRING: ret = njs_query_string_encoder_call(vm, chain, encoder, value); if (njs_slow_path(ret < 0)) { return NJS_ERROR; } - - length += ret; - break; - - default: - break; } - return length; + return NJS_OK; } @@ -723,183 +683,137 @@ static njs_int_t njs_query_string_stringify(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - u_char *p; - int64_t len; - ssize_t size; - uint32_t n, i; - uint64_t length; - njs_int_t ret; - njs_chb_t chain; - njs_value_t value, result, *string, *this, *object, *arg, *options; - njs_array_t *keys, *array; - njs_function_t *encode; - njs_string_prop_t sep, eq; - - njs_value_t val_sep = njs_string("&"); - njs_value_t val_eq = njs_string("="); - - (void) njs_string_prop(&sep, &val_sep); - (void) njs_string_prop(&eq, &val_eq); + int64_t len, keys_length; + uint32_t n, i; + njs_int_t ret; + njs_str_t sep, eq; + njs_chb_t chain; + njs_value_t *this, *object, *arg, *options, *val, *keys; + njs_function_t *encode; + njs_opaque_value_t value, result, key, *string; encode = NULL; + sep = njs_sep_default; + eq = njs_eq_default; + this = njs_argument(args, 0); object = njs_arg(args, nargs, 1); - if (njs_slow_path(!njs_is_object(object))) { - njs_value_assign(retval, &njs_string_empty); + if (njs_slow_path(!njs_value_is_object(object))) { + njs_vm_value_string_set(vm, retval, (u_char *) "", 0); return NJS_OK; } arg = njs_arg(args, nargs, 2); - if (!njs_is_null_or_undefined(arg)) { + if (!njs_value_is_null_or_undefined(arg)) { ret = njs_value_to_string(vm, arg, arg); if (njs_slow_path(ret != NJS_OK)) { return ret; } if (njs_string_length(arg) > 0) { - (void) njs_string_prop(&sep, arg); + njs_value_string_get(arg, &sep); } } arg = njs_arg(args, nargs, 3); - if (!njs_is_null_or_undefined(arg)) { + if (!njs_value_is_null_or_undefined(arg)) { ret = njs_value_to_string(vm, arg, arg); if (njs_slow_path(ret != NJS_OK)) { return ret; } if (njs_string_length(arg) > 0) { - (void) njs_string_prop(&eq, arg); + njs_value_string_get(arg, &eq); } } options = njs_arg(args, nargs, 4); - if (njs_is_object(options)) { - ret = njs_value_property(vm, options, - njs_value_arg(&njs_encode_uri_str), &value); + if (njs_value_is_object(options)) { + val = njs_vm_object_prop(vm, options, &njs_encode_uri_str, &value); - if (ret == NJS_OK) { - if (njs_slow_path(!njs_is_function(&value))) { - njs_type_error(vm, - "option encodeURIComponent is not a function"); + if (val != NULL) { + if (njs_slow_path(!njs_value_is_function(val))) { + njs_vm_error(vm, "option encodeURIComponent is not a function"); return NJS_ERROR; } - encode = njs_function(&value); + encode = njs_value_function(val); } } if (encode == NULL) { - ret = njs_value_property(vm, this, njs_value_arg(&njs_escape_str), - &value); + val = njs_vm_object_prop(vm, this, &njs_escape_str, &value); - if (ret != NJS_OK || !njs_is_function(&value)) { - njs_type_error(vm, "QueryString.escape is not a function"); + if (val == NULL || !njs_value_is_function(val)) { + njs_vm_error(vm, "QueryString.escape is not a function"); return NJS_ERROR; } - encode = njs_function(&value); + encode = njs_value_function(val); } - njs_chb_init(&chain, vm->mem_pool); + njs_chb_init(&chain, njs_vm_memory_pool(vm)); - keys = njs_value_own_enumerate(vm, object, NJS_ENUM_KEYS, NJS_ENUM_STRING, - 0); + keys = njs_vm_object_keys(vm, object, njs_value_arg(&value)); if (njs_slow_path(keys == NULL)) { return NJS_ERROR; } - for (n = 0, length = 0; n < keys->length; n++) { - string = &keys->start[n]; + (void) njs_vm_array_length(vm, keys, &keys_length); - ret = njs_value_property(vm, object, string, &value); + string = (njs_opaque_value_t *) njs_vm_array_start(vm, keys); + if (njs_slow_path(string == NULL)) { + return NJS_ERROR; + } + + for (n = 0; n < keys_length; n++, string++) { + ret = njs_value_property(vm, object, njs_value_arg(string), + njs_value_arg(&value)); if (njs_slow_path(ret == NJS_ERROR)) { goto failed; } - if (njs_is_array(&value)) { - - if (njs_is_fast_array(&value)) { - array = njs_array(&value); - - for (i = 0; i < array->length; i++) { - if (chain.last != NULL) { - njs_chb_append(&chain, sep.start, sep.size); - length += sep.length; - } - - ret = njs_query_string_push(vm, &chain, string, - &array->start[i], &eq, encode); - if (njs_slow_path(ret < 0)) { - ret = NJS_ERROR; - goto failed; - } - - length += ret; - } - - continue; - } - - ret = njs_object_length(vm, &value, &len); - if (njs_slow_path(ret == NJS_ERROR)) { - goto failed; - } + if (njs_value_is_array(njs_value_arg(&value))) { + (void) njs_vm_array_length(vm, njs_value_arg(&value), &len); for (i = 0; i < len; i++) { - ret = njs_value_property_i64(vm, &value, i, &result); + njs_value_number_set(njs_value_arg(&key), i); + ret = njs_value_property(vm, njs_value_arg(&value), + njs_value_arg(&key), + njs_value_arg(&result)); if (njs_slow_path(ret == NJS_ERROR)) { goto failed; } if (chain.last != NULL) { - njs_chb_append(&chain, sep.start, sep.size); - length += sep.length; + njs_chb_append(&chain, sep.start, sep.length); } - ret = njs_query_string_push(vm, &chain, string, &result, &eq, + ret = njs_query_string_push(vm, &chain, njs_value_arg(string), + njs_value_arg(&result), &eq, encode); - if (njs_slow_path(ret < 0)) { - ret = NJS_ERROR; + if (njs_slow_path(ret != NJS_OK)) { goto failed; } - - length += ret; } continue; } if (n != 0) { - njs_chb_append(&chain, sep.start, sep.size); - length += sep.length; + njs_chb_append(&chain, sep.start, sep.length); } - ret = njs_query_string_push(vm, &chain, string, &value, &eq, encode); - if (njs_slow_path(ret < 0)) { - ret = NJS_ERROR; + ret = njs_query_string_push(vm, &chain, njs_value_arg(string), + njs_value_arg(&value), &eq, encode); + if (njs_slow_path(ret != NJS_OK)) { goto failed; } - - length += ret; } - size = njs_chb_size(&chain); - if (njs_slow_path(size < 0)) { - njs_memory_error(vm); - return NJS_ERROR; - } - - p = njs_string_alloc(vm, retval, size, length); - if (njs_slow_path(p == NULL)) { - return NJS_ERROR; - } - - njs_chb_join_to(&chain, p); - - ret = NJS_OK; + ret = njs_vm_value_string_create_chb(vm, retval, &chain); failed: @@ -913,45 +827,37 @@ static njs_int_t njs_query_string_escape(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - u_char *p; - ssize_t size, length; - njs_int_t ret; - njs_str_t str; - njs_chb_t chain; - njs_value_t *string, value; + njs_int_t ret; + njs_str_t str; + njs_chb_t chain; + njs_value_t *string; + njs_opaque_value_t value; string = njs_arg(args, nargs, 1); - if (!njs_is_string(string)) { - ret = njs_value_to_string(vm, &value, string); + if (!njs_value_is_string(string)) { + ret = njs_value_to_string(vm, njs_value_arg(&value), string); if (njs_slow_path(ret != NJS_OK)) { return ret; } - string = &value; + string = njs_value_arg(&value); } - njs_string_get(string, &str); + njs_value_string_get(string, &str); - njs_chb_init(&chain, vm->mem_pool); + njs_chb_init(&chain, njs_vm_memory_pool(vm)); - length = njs_query_string_encode(&chain, &str); - if (njs_slow_path(length < 0)) { + ret = njs_query_string_encode(&chain, &str); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - size = njs_chb_size(&chain); - - p = njs_string_alloc(vm, retval, size, length); - if (njs_slow_path(p == NULL)) { - return NJS_ERROR; - } - - njs_chb_join_to(&chain, p); + ret = njs_vm_value_string_create_chb(vm, retval, &chain); njs_chb_destroy(&chain); - return NJS_OK; + return ret; } @@ -959,22 +865,23 @@ static njs_int_t njs_query_string_unescape(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_int_t ret; - njs_str_t str; - njs_value_t *string, value; + njs_int_t ret; + njs_str_t str; + njs_value_t *string; + njs_opaque_value_t value; string = njs_arg(args, nargs, 1); - if (!njs_is_string(string)) { - ret = njs_value_to_string(vm, &value, string); + if (!njs_value_is_string(string)) { + ret = njs_value_to_string(vm, njs_value_arg(&value), string); if (njs_slow_path(ret != NJS_OK)) { return ret; } - string = &value; + string = njs_value_arg(&value); } - njs_string_get(string, &str); + njs_value_string_get(string, &str); return njs_query_string_decode(vm, retval, str.start, str.length); } diff -r b2cbf06ba017 -r fd956d2a25a3 src/njs.h --- a/src/njs.h Tue May 02 20:50:52 2023 -0700 +++ b/src/njs.h Tue May 02 20:50:55 2023 -0700 @@ -382,6 +382,10 @@ NJS_EXPORT njs_external_ptr_t njs_vm_ext NJS_EXPORT njs_int_t njs_external_property(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +NJS_EXPORT njs_int_t njs_value_property(njs_vm_t *vm, njs_value_t *value, + njs_value_t *key, njs_value_t *retval); +NJS_EXPORT njs_int_t njs_value_property_set(njs_vm_t *vm, njs_value_t *value, + njs_value_t *key, njs_value_t *setval); NJS_EXPORT uintptr_t njs_vm_meta(njs_vm_t *vm, njs_uint_t index); NJS_EXPORT njs_function_t *njs_vm_function_alloc(njs_vm_t *vm, @@ -432,6 +436,8 @@ NJS_EXPORT njs_int_t njs_value_buffer_ge NJS_EXPORT njs_int_t njs_vm_value_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size); +NJS_EXPORT njs_int_t njs_value_to_string(njs_vm_t *vm, njs_value_t *dst, + njs_value_t *value); /* * Converts a value to bytes. */ @@ -477,6 +483,8 @@ NJS_EXPORT void njs_value_function_set(n NJS_EXPORT uint8_t njs_value_bool(const njs_value_t *value); NJS_EXPORT double njs_value_number(const njs_value_t *value); NJS_EXPORT njs_function_t *njs_value_function(const njs_value_t *value); +NJS_EXPORT njs_function_native_t njs_value_native_function( + const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_external_tag(const njs_value_t *value); NJS_EXPORT uint16_t njs_vm_prop_magic16(njs_object_prop_t *prop); diff -r b2cbf06ba017 -r fd956d2a25a3 src/njs_value.c --- a/src/njs_value.c Tue May 02 20:50:52 2023 -0700 +++ b/src/njs_value.c Tue May 02 20:50:55 2023 -0700 @@ -487,6 +487,23 @@ njs_value_function(const njs_value_t *va } +njs_function_native_t +njs_value_native_function(const njs_value_t *value) +{ + njs_function_t *function; + + if (njs_is_function(value)) { + function = njs_function(value); + + if (function->native) { + return function->u.native; + } + } + + return NULL; +} From xeioex at nginx.com Wed May 3 04:13:33 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:33 +0000 Subject: [njs] FS: module is rewritten using public API. Message-ID: details: https://hg.nginx.org/njs/rev/18385a4a90ad branches: changeset: 2102:18385a4a90ad user: Dmitry Volyntsev date: Tue May 02 20:50:55 2023 -0700 description: FS: module is rewritten using public API. diffstat: external/njs_fs_module.c | 1485 ++++++++++++++++++++++--------------------- src/njs.h | 11 + src/njs_value.c | 31 + src/njs_value_conversion.h | 17 - src/njs_vm.c | 56 + src/test/njs_unit_test.c | 74 +- test/fs/methods.t.js | 16 +- test/fs/promises_07.t.js | 2 +- 8 files changed, 898 insertions(+), 794 deletions(-) diffs (truncated from 3111 to 1000 lines): diff -r fd956d2a25a3 -r 18385a4a90ad external/njs_fs_module.c --- a/external/njs_fs_module.c Tue May 02 20:50:55 2023 -0700 +++ b/external/njs_fs_module.c Tue May 02 20:50:55 2023 -0700 @@ -5,9 +5,11 @@ */ -#include - +#include +#include +#include #include +#include #if (NJS_SOLARIS) @@ -133,8 +135,8 @@ typedef struct { typedef struct { - njs_int_t bytes; - njs_value_t buffer; + njs_int_t bytes; + njs_opaque_value_t buffer; } njs_bytes_struct_t; @@ -173,10 +175,8 @@ static njs_int_t njs_fs_write(njs_vm_t * static njs_int_t njs_fs_write_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval); -static njs_int_t njs_fs_constants(njs_vm_t *vm, njs_object_prop_t *prop, - njs_value_t *value, njs_value_t *unused, njs_value_t *retval); -static njs_int_t njs_fs_promises(njs_vm_t *vm, njs_object_prop_t *prop, - njs_value_t *value, njs_value_t *unused, njs_value_t *retval); +static njs_int_t njs_fs_constant(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t njs_fs_dirent_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); @@ -195,18 +195,18 @@ static njs_int_t njs_fs_filehandle_close static njs_int_t njs_fs_filehandle_value_of(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_fs_filehandle_create(njs_vm_t *vm, int fd, - njs_bool_t shadow, njs_value_t *retval); + njs_bool_t shadow, njs_opaque_value_t *retval); static njs_int_t njs_fs_bytes_read_create(njs_vm_t *vm, int bytes, - njs_value_t *buffer, njs_value_t *retval); + njs_value_t *buffer, njs_opaque_value_t *retval); static njs_int_t njs_fs_bytes_written_create(njs_vm_t *vm, int bytes, - njs_value_t *buffer, njs_value_t *retval); + njs_value_t *buffer, njs_opaque_value_t *retval); static njs_int_t njs_fs_fd_read(njs_vm_t *vm, int fd, njs_str_t *data); static njs_int_t njs_fs_error(njs_vm_t *vm, const char *syscall, - const char *desc, const char *path, int errn, njs_value_t *result); -static njs_int_t njs_fs_result(njs_vm_t *vm, njs_value_t *result, + const char *desc, const char *path, int errn, njs_opaque_value_t *result); +static njs_int_t njs_fs_result(njs_vm_t *vm, njs_opaque_value_t *result, njs_index_t calltype, const njs_value_t* callback, njs_uint_t nargs, njs_value_t *retval); @@ -214,19 +214,16 @@ static njs_int_t njs_file_tree_walk(cons njs_file_tree_walk_cb_t cb, int fd_limit, njs_ftw_flags_t flags); static njs_int_t njs_fs_make_path(njs_vm_t *vm, char *path, mode_t md, - njs_bool_t recursive, njs_value_t *retval); + njs_bool_t recursive, njs_opaque_value_t *retval); static njs_int_t njs_fs_rmtree(njs_vm_t *vm, const char *path, - njs_bool_t recursive, njs_value_t *retval); + njs_bool_t recursive, njs_opaque_value_t *retval); static const char *njs_fs_path(njs_vm_t *vm, char storage[NJS_MAX_PATH + 1], - const njs_value_t *src, const char *prop_name); + njs_value_t *src, const char *prop_name); static int njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags); static mode_t njs_fs_mode(njs_vm_t *vm, njs_value_t *value, mode_t default_mode); -static njs_int_t njs_fs_add_event(njs_vm_t *vm, const njs_value_t *callback, - const njs_value_t *args, njs_uint_t nargs); - static njs_int_t njs_fs_dirent_create(njs_vm_t *vm, njs_value_t *name, njs_value_t *type, njs_value_t *retval); @@ -234,11 +231,11 @@ static njs_int_t njs_fs_dirent_create(nj static njs_int_t njs_fs_init(njs_vm_t *vm); -static const njs_value_t string_flag = njs_string("flag"); -static const njs_value_t string_mode = njs_string("mode"); -static const njs_value_t string_buffer = njs_string("buffer"); -static const njs_value_t string_encoding = njs_string("encoding"); -static const njs_value_t string_recursive = njs_string("recursive"); +static const njs_str_t string_flag = njs_str("flag"); +static const njs_str_t string_mode = njs_str("mode"); +static const njs_str_t string_buffer = njs_str("buffer"); +static const njs_str_t string_encoding = njs_str("encoding"); +static const njs_str_t string_recursive = njs_str("recursive"); static njs_fs_entry_t njs_flags_table[] = { @@ -259,6 +256,243 @@ static njs_fs_entry_t njs_flags_table[] }; +static njs_external_t njs_ext_fs_constants[] = { + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("F_OK"), + .enumerable = 1, + .u.property = { + .handler = njs_fs_constant, + .magic32 = F_OK, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("R_OK"), + .enumerable = 1, + .u.property = { + .handler = njs_fs_constant, + .magic32 = R_OK, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("W_OK"), + .enumerable = 1, + .u.property = { + .handler = njs_fs_constant, + .magic32 = W_OK, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("X_OK"), + .enumerable = 1, + .u.property = { + .handler = njs_fs_constant, + .magic32 = X_OK, + } + }, + +}; + + +static njs_external_t njs_ext_fs_promises[] = { + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("access"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_access, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("appendFile"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_write_file, + .magic8 = njs_fs_magic(NJS_FS_PROMISE, NJS_FS_APPEND), + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("close"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_close, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("fstat"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_stat, + .magic8 = njs_fs_magic(NJS_FS_PROMISE, NJS_FS_FSTAT), + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("mkdir"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_mkdir, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("lstat"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_stat, + .magic8 = njs_fs_magic(NJS_FS_PROMISE, NJS_FS_LSTAT), + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("open"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_open, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("readFile"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_read_file, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("readSync"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_read, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("readdir"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_readdir, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("realpath"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_realpath, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("rename"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_rename, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("rmdir"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_rmdir, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("stat"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_stat, + .magic8 = njs_fs_magic(NJS_FS_PROMISE, NJS_FS_STAT), + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("symlink"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_symlink, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("unlink"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_unlink, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("writeFile"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_write_file, + .magic8 = njs_fs_magic(NJS_FS_PROMISE, NJS_FS_TRUNC), + } + }, + +}; + + static njs_external_t njs_ext_fs[] = { { @@ -325,11 +559,14 @@ static njs_external_t njs_ext_fs[] = { }, { - .flags = NJS_EXTERN_PROPERTY, + .flags = NJS_EXTERN_OBJECT, .name.string = njs_str("constants"), + .writable = 1, .enumerable = 1, - .u.property = { - .handler = njs_fs_constants, + .configurable = 1, + .u.object = { + .properties = njs_ext_fs_constants, + .nproperties = njs_nitems(njs_ext_fs_constants), } }, @@ -411,11 +648,14 @@ static njs_external_t njs_ext_fs[] = { }, { - .flags = NJS_EXTERN_PROPERTY, + .flags = NJS_EXTERN_OBJECT, .name.string = njs_str("promises"), + .writable = 1, .enumerable = 1, - .u.property = { - .handler = njs_fs_promises, + .configurable = 1, + .u.object = { + .properties = njs_ext_fs_promises, + .nproperties = njs_nitems(njs_ext_fs_promises), } }, @@ -757,7 +997,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_ATIME, NJS_DATE), + .magic32 = njs_fs_magic2(NJS_FS_STAT_ATIME, 1), } }, @@ -767,7 +1007,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_ATIME, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_ATIME, 0), } }, @@ -777,7 +1017,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_BIRTHTIME, NJS_DATE), + .magic32 = njs_fs_magic2(NJS_FS_STAT_BIRTHTIME, 1), } }, @@ -787,7 +1027,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_BIRTHTIME, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_BIRTHTIME, 0), } }, @@ -797,7 +1037,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_CTIME, NJS_DATE), + .magic32 = njs_fs_magic2(NJS_FS_STAT_CTIME, 1), } }, @@ -807,7 +1047,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_CTIME, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_CTIME, 0), } }, @@ -817,7 +1057,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_BLKSIZE, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_BLKSIZE, 0), } }, @@ -827,7 +1067,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_BLOCKS, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_BLOCKS, 0), } }, @@ -837,7 +1077,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_DEV, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_DEV, 0), } }, @@ -847,7 +1087,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_GID, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_GID, 0), } }, @@ -857,7 +1097,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_INO, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_INO, 0), } }, @@ -867,7 +1107,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_MODE, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_MODE, 0), } }, @@ -877,7 +1117,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_MTIME, NJS_DATE), + .magic32 = njs_fs_magic2(NJS_FS_STAT_MTIME, 1), } }, @@ -887,7 +1127,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_MTIME, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_MTIME, 0), } }, @@ -897,7 +1137,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_NLINK, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_NLINK, 0), } }, @@ -907,7 +1147,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_RDEV, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_RDEV, 0), } }, @@ -917,7 +1157,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_SIZE, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_SIZE, 0), } }, @@ -927,7 +1167,7 @@ static njs_external_t njs_ext_stats[] = .enumerable = 1, .u.property = { .handler = njs_fs_stats_prop, - .magic32 = njs_fs_magic2(NJS_FS_STAT_UID, NJS_NUMBER), + .magic32 = njs_fs_magic2(NJS_FS_STAT_UID, 0), } }, @@ -1175,11 +1415,12 @@ static njs_int_t njs_fs_access(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval) { - int md; - njs_int_t ret; - const char *path; - njs_value_t result, *callback, *mode; - char path_buf[NJS_MAX_PATH + 1]; + int md; + njs_int_t ret; + const char *path; + njs_value_t *callback, *mode; + njs_opaque_value_t result; + char path_buf[NJS_MAX_PATH + 1]; path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); if (njs_slow_path(path == NULL)) { @@ -1191,8 +1432,8 @@ njs_fs_access(njs_vm_t *vm, njs_value_t if (calltype == NJS_FS_CALLBACK) { callback = njs_arg(args, nargs, njs_min(nargs - 1, 3)); - if (!njs_is_function(callback)) { - njs_type_error(vm, "\"callback\" must be a function"); + if (!njs_value_is_function(callback)) { + njs_vm_error(vm, "\"callback\" must be a function"); return NJS_ERROR; } @@ -1201,21 +1442,18 @@ njs_fs_access(njs_vm_t *vm, njs_value_t } } - switch (mode->type) { - case NJS_UNDEFINED: + if (njs_value_is_number(mode)) { + md = njs_value_number(mode); + + } else if (njs_value_is_undefined(mode)) { md = F_OK; - break; - - case NJS_NUMBER: - md = njs_number(mode); - break; - - default: - njs_type_error(vm, "\"mode\" must be a number"); + + } else { + njs_vm_error(vm, "\"mode\" must be a number"); return NJS_ERROR; } - njs_set_undefined(&result); + njs_value_undefined_set(njs_value_arg(&result)); ret = access(path, md); if (njs_slow_path(ret != 0)) { @@ -1234,12 +1472,13 @@ static njs_int_t njs_fs_open(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval) { - int fd, flags; - mode_t md; - njs_int_t ret; - const char *path; - njs_value_t result, *value; - char path_buf[NJS_MAX_PATH + 1]; + int fd, flags; + mode_t md; + njs_int_t ret; + const char *path; + njs_value_t *value; + njs_opaque_value_t result; + char path_buf[NJS_MAX_PATH + 1]; path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); if (njs_slow_path(path == NULL)) { @@ -1247,7 +1486,7 @@ njs_fs_open(njs_vm_t *vm, njs_value_t *a } value = njs_arg(args, nargs, 2); - if (njs_is_function(value)) { + if (njs_value_is_function(value)) { value = njs_value_arg(&njs_value_undefined); } @@ -1257,7 +1496,7 @@ njs_fs_open(njs_vm_t *vm, njs_value_t *a } value = njs_arg(args, nargs, 3); - if (njs_is_function(value)) { + if (njs_value_is_function(value)) { value = njs_value_arg(&njs_value_undefined); } @@ -1278,7 +1517,7 @@ njs_fs_open(njs_vm_t *vm, njs_value_t *a } if (calltype == NJS_FS_DIRECT) { - njs_value_number_set(&result, fd); + njs_value_number_set(njs_value_arg(&result), fd); } done: @@ -1299,9 +1538,10 @@ static njs_int_t njs_fs_close(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval) { - int64_t fd; - njs_int_t ret; - njs_value_t result, *fh; + int64_t fd; + njs_int_t ret; + njs_value_t *fh; + njs_opaque_value_t result; fh = njs_arg(args, nargs, 1); @@ -1310,7 +1550,7 @@ njs_fs_close(njs_vm_t *vm, njs_value_t * return ret; } - njs_set_undefined(&result); + njs_value_undefined_set(njs_value_arg(&result)); ret = close((int) fd); if (njs_slow_path(ret != 0)) { @@ -1329,11 +1569,12 @@ static njs_int_t njs_fs_mkdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval) { - char *path; - mode_t md; - njs_int_t ret; - njs_value_t mode, recursive, result, *callback, *options; - char path_buf[NJS_MAX_PATH + 1]; + char *path; + mode_t md; + njs_int_t ret; + njs_value_t *callback, *options; + njs_opaque_value_t mode, recursive, result; + char path_buf[NJS_MAX_PATH + 1]; path = (char *) njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); if (njs_slow_path(path == NULL)) { @@ -1345,53 +1586,41 @@ njs_fs_mkdir(njs_vm_t *vm, njs_value_t * if (njs_slow_path(calltype == NJS_FS_CALLBACK)) { callback = njs_arg(args, nargs, njs_min(nargs - 1, 3)); - if (!njs_is_function(callback)) { - njs_type_error(vm, "\"callback\" must be a function"); + if (!njs_value_is_function(callback)) { + njs_vm_error(vm, "\"callback\" must be a function"); return NJS_ERROR; } + if (options == callback) { options = njs_value_arg(&njs_value_undefined); } } - njs_set_undefined(&mode); - njs_set_false(&recursive); - - switch (options->type) { - case NJS_NUMBER: - mode = *options; - break; - - case NJS_UNDEFINED: - break; - - default: - if (!njs_is_object(options)) { - njs_type_error(vm, "Unknown options type: \"%s\" " - "(a number or object required)", - njs_type_string(options->type)); + njs_value_undefined_set(njs_value_arg(&mode)); + njs_value_boolean_set(njs_value_arg(&recursive), 0); + + if (njs_value_is_number(options)) { + njs_value_assign(&mode, options); + + } else if (!njs_value_is_undefined(options)) { + if (!njs_value_is_object(options)) { + njs_vm_error(vm, "Unknown options type" + "(a number or object required)"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_mode), - &mode); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } - - ret = njs_value_property(vm, options, njs_value_arg(&string_recursive), - &recursive); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } + (void) njs_vm_object_prop(vm, options, &string_recursive, &recursive); + + (void) njs_vm_object_prop(vm, options, &string_mode, &mode); } - md = njs_fs_mode(vm, &mode, 0777); + md = njs_fs_mode(vm, njs_value_arg(&mode), 0777); if (njs_slow_path(md == (mode_t) -1)) { return NJS_ERROR; } - ret = njs_fs_make_path(vm, path, md, njs_is_true(&recursive), &result); + ret = njs_fs_make_path(vm, path, md, + njs_value_bool(njs_value_arg(&recursive)), &result); if (ret == NJS_OK) { return njs_fs_result(vm, &result, calltype, callback, 1, retval); @@ -1405,12 +1634,13 @@ static njs_int_t njs_fs_read(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval) { - int64_t fd, length, pos, offset; - ssize_t n; - njs_int_t ret; - njs_str_t data; - njs_uint_t fd_offset; - njs_value_t result, *buffer, *value; + int64_t fd, length, pos, offset; + ssize_t n; + njs_int_t ret; + njs_str_t data; + njs_uint_t fd_offset; + njs_value_t *buffer, *value; + njs_opaque_value_t result; fd_offset = !!(calltype == NJS_FS_DIRECT); @@ -1439,8 +1669,7 @@ njs_fs_read(njs_vm_t *vm, njs_value_t *a } if (njs_slow_path(offset < 0 || (size_t) offset > data.length)) { - njs_range_error(vm, "offset is out of range (must be <= %z)", - data.length); + njs_vm_error(vm, "offset is out of range (must be <= %z)", data.length); return NJS_ERROR; } @@ -1449,15 +1678,15 @@ njs_fs_read(njs_vm_t *vm, njs_value_t *a value = njs_arg(args, nargs, fd_offset + 3); - if (njs_is_defined(value)) { + if (!njs_value_is_undefined(value)) { ret = njs_value_to_integer(vm, value, &length); if (njs_slow_path(ret != NJS_OK)) { return ret; } if (njs_slow_path(length < 0 || (size_t) length > data.length)) { - njs_range_error(vm, "length is out of range (must be <= %z)", - data.length); + njs_vm_error(vm, "length is out of range (must be <= %z)", + data.length); return NJS_ERROR; } @@ -1466,7 +1695,7 @@ njs_fs_read(njs_vm_t *vm, njs_value_t *a value = njs_arg(args, nargs, fd_offset + 4); - if (!njs_is_null_or_undefined(value)) { + if (!njs_value_is_null_or_undefined(value)) { ret = njs_value_to_integer(vm, value, &pos); if (njs_slow_path(ret != NJS_OK)) { return ret; @@ -1492,7 +1721,7 @@ njs_fs_read(njs_vm_t *vm, njs_value_t *a } } else { - njs_value_number_set(&result, n); + njs_value_number_set(njs_value_arg(&result), n); } done: @@ -1513,8 +1742,9 @@ njs_fs_read_file(njs_vm_t *vm, njs_value njs_str_t data; njs_int_t ret; const char *path; - njs_value_t flag, encode, result, *callback, *options; + njs_value_t *callback, *options; struct stat sb; + njs_opaque_value_t flag, result, encode; const njs_buffer_encoding_t *encoding; char path_buf[NJS_MAX_PATH + 1]; @@ -1529,8 +1759,8 @@ njs_fs_read_file(njs_vm_t *vm, njs_value if (calltype == NJS_FS_CALLBACK) { callback = njs_arg(args, nargs, njs_min(nargs - 1, 3)); - if (!njs_is_function(callback)) { - njs_type_error(vm, "\"callback\" must be a function"); + if (!njs_value_is_function(callback)) { + njs_vm_error(vm, "\"callback\" must be a function"); return NJS_ERROR; } @@ -1539,46 +1769,32 @@ njs_fs_read_file(njs_vm_t *vm, njs_value } } - njs_set_undefined(&flag); - njs_set_undefined(&encode); - - switch (options->type) { - case NJS_STRING: - encode = *options; - break; - - case NJS_UNDEFINED: - break; - - default: - if (!njs_is_object(options)) { - njs_type_error(vm, "Unknown options type: \"%s\" " - "(a string or object required)", - njs_type_string(options->type)); + njs_value_undefined_set(njs_value_arg(&flag)); + njs_value_undefined_set(njs_value_arg(&encode)); + + if (njs_value_is_string(options)) { + njs_value_assign(&encode, options); + + } else if (!njs_value_is_undefined(options)) { + if (!njs_value_is_object(options)) { + njs_vm_error(vm, "Unknown options type " + "(a string or object required)"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_flag), - &flag); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } - - ret = njs_value_property(vm, options, njs_value_arg(&string_encoding), - &encode); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } + (void) njs_vm_object_prop(vm, options, &string_flag, &flag); + + (void) njs_vm_object_prop(vm, options, &string_encoding, &encode); } - flags = njs_fs_flags(vm, &flag, O_RDONLY); + flags = njs_fs_flags(vm, njs_value_arg(&flag), O_RDONLY); if (njs_slow_path(flags == -1)) { return NJS_ERROR; } encoding = NULL; - if (njs_is_defined(&encode)) { - encoding = njs_buffer_encoding(vm, &encode, 1); + if (!njs_value_is_undefined(njs_value_arg(&encode))) { + encoding = njs_buffer_encoding(vm, njs_value_arg(&encode), 1); if (njs_slow_path(encoding == NULL)) { return NJS_ERROR; } @@ -1615,11 +1831,12 @@ njs_fs_read_file(njs_vm_t *vm, njs_value } if (encoding == NULL) { - ret = njs_buffer_set(vm, &result, data.start, data.length); + ret = njs_buffer_set(vm, njs_value_arg(&result), data.start, + data.length); } else { - ret = encoding->encode(vm, &result, &data); - njs_mp_free(vm->mem_pool, data.start); + ret = encoding->encode(vm, njs_value_arg(&result), &data); + njs_mp_free(njs_vm_memory_pool(vm), data.start); } done: @@ -1644,14 +1861,13 @@ njs_fs_readdir(njs_vm_t *vm, njs_value_t njs_str_t s; njs_int_t ret; const char *path; - njs_value_t encode, types, ename, etype, result, - *callback, *options, *value; - njs_array_t *results; + njs_value_t *callback, *options, *value; struct dirent *entry; + njs_opaque_value_t encode, types, ename, etype, result; const njs_buffer_encoding_t *encoding; char path_buf[NJS_MAX_PATH + 1]; - static const njs_value_t string_types = njs_string("withFileTypes"); + static const njs_str_t string_types = njs_str("withFileTypes"); path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); if (njs_slow_path(path == NULL)) { @@ -1663,8 +1879,8 @@ njs_fs_readdir(njs_vm_t *vm, njs_value_t if (njs_slow_path(calltype == NJS_FS_CALLBACK)) { callback = njs_arg(args, nargs, njs_min(nargs - 1, 3)); - if (!njs_is_function(callback)) { - njs_type_error(vm, "\"callback\" must be a function"); + if (!njs_value_is_function(callback)) { + njs_vm_error(vm, "\"callback\" must be a function"); return NJS_ERROR; } if (options == callback) { @@ -1672,53 +1888,42 @@ njs_fs_readdir(njs_vm_t *vm, njs_value_t } } - njs_set_false(&types); - njs_set_undefined(&encode); - - switch (options->type) { - case NJS_STRING: - encode = *options; - break; - - case NJS_UNDEFINED: - break; - - default: - if (!njs_is_object(options)) { - njs_type_error(vm, "Unknown options type: \"%s\" " - "(a string or object required)", - njs_type_string(options->type)); + njs_value_boolean_set(njs_value_arg(&types), 0); + njs_value_undefined_set(njs_value_arg(&encode)); + + if (njs_value_is_string(options)) { + njs_value_assign(&encode, options); + + } else if (!njs_value_is_undefined(options)) { + if (!njs_value_is_object(options)) { + njs_vm_error(vm, "Unknown options type " + "(a string or object required)"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_encoding), - &encode); - if (njs_slow_path(ret == NJS_ERROR)) { From xeioex at nginx.com Wed May 3 04:13:35 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 03 May 2023 04:13:35 +0000 Subject: [njs] WebCrypto: module is rewritten using public API. Message-ID: details: https://hg.nginx.org/njs/rev/f1432043a6a4 branches: changeset: 2103:f1432043a6a4 user: Dmitry Volyntsev date: Tue May 02 20:50:57 2023 -0700 description: WebCrypto: module is rewritten using public API. diffstat: external/njs_webcrypto_module.c | 1227 +++++++++++++++++++------------------- src/njs.h | 20 + src/njs_iterator.h | 17 - src/njs_vm.c | 8 + test/harness/runTsuite.js | 2 +- 5 files changed, 647 insertions(+), 627 deletions(-) diffs (truncated from 2564 to 1000 lines): diff -r 18385a4a90ad -r f1432043a6a4 external/njs_webcrypto_module.c --- a/external/njs_webcrypto_module.c Tue May 02 20:50:55 2023 -0700 +++ b/external/njs_webcrypto_module.c Tue May 02 20:50:57 2023 -0700 @@ -5,7 +5,9 @@ */ -#include +#include +#include +#include #include "njs_openssl.h" typedef enum { @@ -126,7 +128,8 @@ static njs_webcrypto_key_format_t njs_ke static njs_str_t *njs_format_string(njs_webcrypto_key_format_t fmt); static njs_int_t njs_key_usage(njs_vm_t *vm, njs_value_t *value, unsigned *mask); -static njs_int_t njs_key_ops(njs_vm_t *vm, njs_value_t *retval, unsigned mask); +static njs_int_t njs_key_ops(njs_vm_t *vm, njs_opaque_value_t *retval, + unsigned mask); static njs_webcrypto_algorithm_t *njs_key_algorithm(njs_vm_t *vm, njs_value_t *value); static njs_str_t *njs_algorithm_string(njs_webcrypto_algorithm_t *algorithm); @@ -136,7 +139,7 @@ static const EVP_MD *njs_algorithm_hash_ static njs_int_t njs_algorithm_curve(njs_vm_t *vm, njs_value_t *value, int *curve); -static njs_int_t njs_webcrypto_result(njs_vm_t *vm, njs_value_t *result, +static njs_int_t njs_webcrypto_result(njs_vm_t *vm, njs_opaque_value_t *result, njs_int_t rc, njs_value_t *retval); static njs_int_t njs_webcrypto_array_buffer(njs_vm_t *vm, njs_value_t *retval, u_char *start, size_t length); @@ -593,23 +596,23 @@ njs_module_t njs_webcrypto_module = { }; -static const njs_value_t string_alg = njs_string("alg"); -static const njs_value_t string_d = njs_string("d"); -static const njs_value_t string_dp = njs_string("dp"); -static const njs_value_t string_dq = njs_string("dq"); -static const njs_value_t string_e = njs_string("e"); -static const njs_value_t string_k = njs_string("k"); -static const njs_value_t string_n = njs_string("n"); -static const njs_value_t string_p = njs_string("p"); -static const njs_value_t string_q = njs_string("q"); -static const njs_value_t string_qi = njs_string("qi"); -static const njs_value_t string_x = njs_string("x"); -static const njs_value_t string_y = njs_string("y"); -static const njs_value_t string_ext = njs_string("ext"); -static const njs_value_t string_crv = njs_string("crv"); -static const njs_value_t string_kty = njs_string("kty"); -static const njs_value_t key_ops = njs_string("key_ops"); -static const njs_value_t string_length = njs_string("length"); +static const njs_str_t string_alg = njs_str("alg"); +static const njs_str_t string_d = njs_str("d"); +static const njs_str_t string_dp = njs_str("dp"); +static const njs_str_t string_dq = njs_str("dq"); +static const njs_str_t string_e = njs_str("e"); +static const njs_str_t string_k = njs_str("k"); +static const njs_str_t string_n = njs_str("n"); +static const njs_str_t string_p = njs_str("p"); +static const njs_str_t string_q = njs_str("q"); +static const njs_str_t string_qi = njs_str("qi"); +static const njs_str_t string_x = njs_str("x"); +static const njs_str_t string_y = njs_str("y"); +static const njs_str_t string_ext = njs_str("ext"); +static const njs_str_t string_crv = njs_str("crv"); +static const njs_str_t string_kty = njs_str("kty"); +static const njs_str_t key_ops = njs_str("key_ops"); +static const njs_str_t string_length = njs_str("length"); static njs_int_t njs_webcrypto_crypto_key_proto_id; @@ -622,7 +625,8 @@ njs_ext_cipher(njs_vm_t *vm, njs_value_t unsigned mask; njs_int_t ret; njs_str_t data; - njs_value_t *options, value; + njs_value_t *options; + njs_opaque_value_t result; njs_webcrypto_key_t *key; njs_webcrypto_algorithm_t *alg; @@ -635,22 +639,22 @@ njs_ext_cipher(njs_vm_t *vm, njs_value_t key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, njs_arg(args, nargs, 2)); if (njs_slow_path(key == NULL)) { - njs_type_error(vm, "\"key\" is not a CryptoKey object"); + njs_vm_error(vm, "\"key\" is not a CryptoKey object"); goto fail; } mask = encrypt ? NJS_KEY_USAGE_ENCRYPT : NJS_KEY_USAGE_DECRYPT; if (njs_slow_path(!(key->usage & mask))) { - njs_type_error(vm, "provide key does not support %s operation", - encrypt ? "encrypt" : "decrypt"); + njs_vm_error(vm, "provide key does not support %s operation", + encrypt ? "encrypt" : "decrypt"); goto fail; } if (njs_slow_path(key->alg != alg)) { - njs_type_error(vm, "cannot %s using \"%V\" with \"%V\" key", - encrypt ? "encrypt" : "decrypt", - njs_algorithm_string(key->alg), - njs_algorithm_string(alg)); + njs_vm_error(vm, "cannot %s using \"%V\" with \"%V\" key", + encrypt ? "encrypt" : "decrypt", + njs_algorithm_string(key->alg), + njs_algorithm_string(alg)); goto fail; } @@ -661,23 +665,26 @@ njs_ext_cipher(njs_vm_t *vm, njs_value_t switch (alg->type) { case NJS_ALGORITHM_RSA_OAEP: - ret = njs_cipher_pkey(vm, &data, key, encrypt, &value); + ret = njs_cipher_pkey(vm, &data, key, encrypt, njs_value_arg(&result)); break; case NJS_ALGORITHM_AES_GCM: - ret = njs_cipher_aes_gcm(vm, &data, key, options, encrypt, &value); + ret = njs_cipher_aes_gcm(vm, &data, key, options, encrypt, + njs_value_arg(&result)); break; case NJS_ALGORITHM_AES_CTR: - ret = njs_cipher_aes_ctr(vm, &data, key, options, encrypt, &value); + ret = njs_cipher_aes_ctr(vm, &data, key, options, encrypt, + njs_value_arg(&result)); break; case NJS_ALGORITHM_AES_CBC: default: - ret = njs_cipher_aes_cbc(vm, &data, key, options, encrypt, &value); - } - - return njs_webcrypto_result(vm, &value, ret, retval); + ret = njs_cipher_aes_cbc(vm, &data, key, options, encrypt, + njs_value_arg(&result)); + } + + return njs_webcrypto_result(vm, &result, ret, retval); fail: @@ -736,7 +743,7 @@ njs_cipher_pkey(njs_vm_t *vm, njs_str_t dst = njs_mp_alloc(njs_vm_memory_pool(vm), outlen); if (njs_slow_path(dst == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); ret = NJS_ERROR; goto fail; } @@ -763,18 +770,19 @@ static njs_int_t njs_cipher_aes_gcm(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt, njs_value_t *retval) { - int len, outlen, dstlen; - u_char *dst, *p; - int64_t taglen; - njs_str_t iv, aad; - njs_int_t ret; - njs_value_t value; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - - static const njs_value_t string_iv = njs_string("iv"); - static const njs_value_t string_ad = njs_string("additionalData"); - static const njs_value_t string_tl = njs_string("tagLength"); + int len, outlen, dstlen; + u_char *dst, *p; + int64_t taglen; + njs_str_t iv, aad; + njs_int_t ret; + njs_value_t *value; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + njs_opaque_value_t lvalue; + + static const njs_str_t string_iv = njs_str("iv"); + static const njs_str_t string_ad = njs_str("additionalData"); + static const njs_str_t string_tl = njs_str("tagLength"); switch (key->raw.length) { case 16: @@ -790,33 +798,26 @@ njs_cipher_aes_gcm(njs_vm_t *vm, njs_str break; default: - njs_type_error(vm, "AES-GCM Invalid key length"); + njs_vm_error(vm, "AES-GCM Invalid key length"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_iv), &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "AES-GCM algorithm.iv is not provided"); - } - + value = njs_vm_object_prop(vm, options, &string_iv, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "AES-GCM algorithm.iv is not provided"); return NJS_ERROR; } - ret = njs_vm_value_to_bytes(vm, &iv, &value); + ret = njs_vm_value_to_bytes(vm, &iv, njs_value_arg(&lvalue)); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } taglen = 128; - ret = njs_value_property(vm, options, njs_value_arg(&string_tl), &value); - if (njs_slow_path(ret == NJS_ERROR)) { - return NJS_ERROR; - } - - if (njs_is_defined(&value)) { - ret = njs_value_to_integer(vm, &value, &taglen); + value = njs_vm_object_prop(vm, options, &string_tl, &lvalue); + if (value != NULL && !njs_value_is_undefined(value)) { + ret = njs_value_to_integer(vm, value, &taglen); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -830,14 +831,14 @@ njs_cipher_aes_gcm(njs_vm_t *vm, njs_str && taglen != 120 && taglen != 128)) { - njs_type_error(vm, "AES-GCM Invalid tagLength"); + njs_vm_error(vm, "AES-GCM Invalid tagLength"); return NJS_ERROR; } taglen /= 8; if (njs_slow_path(!encrypt && (data->length < (size_t) taglen))) { - njs_type_error(vm, "AES-GCM data is too short"); + njs_vm_error(vm, "AES-GCM data is too short"); return NJS_ERROR; } @@ -881,15 +882,11 @@ njs_cipher_aes_gcm(njs_vm_t *vm, njs_str } } - ret = njs_value_property(vm, options, njs_value_arg(&string_ad), &value); - if (njs_slow_path(ret == NJS_ERROR)) { - return NJS_ERROR; - } - aad.length = 0; - if (njs_is_defined(&value)) { - ret = njs_vm_value_to_bytes(vm, &aad, &value); + value = njs_vm_object_prop(vm, options, &string_ad, &lvalue); + if (value != NULL && !njs_value_is_undefined(value)) { + ret = njs_vm_value_to_bytes(vm, &aad, value); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -908,7 +905,7 @@ njs_cipher_aes_gcm(njs_vm_t *vm, njs_str dstlen = data->length + EVP_CIPHER_CTX_block_size(ctx) + taglen; dst = njs_mp_alloc(njs_vm_memory_pool(vm), dstlen); if (njs_slow_path(dst == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -1064,18 +1061,19 @@ static njs_int_t njs_cipher_aes_ctr(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt, njs_value_t *retval) { - int len, len2; - u_char *dst; - int64_t length; - BIGNUM *total, *blocks, *left, *ctr; - njs_int_t ret; - njs_str_t iv; - njs_uint_t size1; - njs_value_t value; - const EVP_CIPHER *cipher; - u_char iv2[16]; - - static const njs_value_t string_counter = njs_string("counter"); + int len, len2; + u_char *dst; + int64_t length; + BIGNUM *total, *blocks, *left, *ctr; + njs_int_t ret; + njs_str_t iv; + njs_uint_t size1; + njs_value_t *value; + const EVP_CIPHER *cipher; + njs_opaque_value_t lvalue; + u_char iv2[16]; + + static const njs_str_t string_counter = njs_str("counter"); switch (key->raw.length) { case 16: @@ -1091,48 +1089,39 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str break; default: - njs_type_error(vm, "AES-CTR Invalid key length"); + njs_vm_error(vm, "AES-CTR Invalid key length"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_counter), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "AES-CTR algorithm.counter is not provided"); - } - + value = njs_vm_object_prop(vm, options, &string_counter, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "AES-CTR algorithm.counter is not provided"); return NJS_ERROR; } - ret = njs_vm_value_to_bytes(vm, &iv, &value); + ret = njs_vm_value_to_bytes(vm, &iv, value); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } if (njs_slow_path(iv.length != 16)) { - njs_type_error(vm, "AES-CTR algorithm.counter must be 16 bytes long"); + njs_vm_error(vm, "AES-CTR algorithm.counter must be 16 bytes long"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_length), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "AES-CTR algorithm.length is not provided"); - } - + value = njs_vm_object_prop(vm, options, &string_length, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "AES-CTR algorithm.length is not provided"); return NJS_ERROR; } - ret = njs_value_to_integer(vm, &value, &length); + ret = njs_value_to_integer(vm, value, &length); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } if (njs_slow_path(length == 0 || length > 128)) { - njs_type_error(vm, "AES-CTR algorithm.length " - "must be between 1 and 128"); + njs_vm_error(vm, "AES-CTR algorithm.length must be between 1 and 128"); return NJS_ERROR; } @@ -1175,7 +1164,7 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str ret = BN_cmp(blocks, total); if (njs_slow_path(ret > 0)) { - njs_type_error(vm, "AES-CTR repeated counter"); + njs_vm_error(vm, "AES-CTR repeated counter"); ret = NJS_ERROR; goto fail; } @@ -1196,7 +1185,7 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str dst = njs_mp_alloc(njs_vm_memory_pool(vm), data->length + EVP_MAX_BLOCK_LENGTH); if (njs_slow_path(dst == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -1271,16 +1260,17 @@ static njs_int_t njs_cipher_aes_cbc(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt, njs_value_t *retval) { - int olen_max, olen, olen2; - u_char *dst; - unsigned remainder; - njs_str_t iv; - njs_int_t ret; - njs_value_t value; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - - static const njs_value_t string_iv = njs_string("iv"); + int olen_max, olen, olen2; + u_char *dst; + unsigned remainder; + njs_str_t iv; + njs_int_t ret; + njs_value_t *value; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + njs_opaque_value_t lvalue; + + static const njs_str_t string_iv = njs_str("iv"); switch (key->raw.length) { case 16: @@ -1296,26 +1286,23 @@ njs_cipher_aes_cbc(njs_vm_t *vm, njs_str break; default: - njs_type_error(vm, "AES-CBC Invalid key length"); + njs_vm_error(vm, "AES-CBC Invalid key length"); return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_iv), &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "AES-CBC algorithm.iv is not provided"); - } - + value = njs_vm_object_prop(vm, options, &string_iv, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "AES-CBC algorithm.iv is not provided"); return NJS_ERROR; } - ret = njs_vm_value_to_bytes(vm, &iv, &value); + ret = njs_vm_value_to_bytes(vm, &iv, value); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } if (njs_slow_path(iv.length != 16)) { - njs_type_error(vm, "AES-CBC algorithm.iv must be 16 bytes long"); + njs_vm_error(vm, "AES-CBC algorithm.iv must be 16 bytes long"); return NJS_ERROR; } @@ -1343,7 +1330,7 @@ njs_cipher_aes_cbc(njs_vm_t *vm, njs_str dst = njs_mp_alloc(njs_vm_memory_pool(vm), olen_max); if (njs_slow_path(dst == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); ret = NJS_ERROR; goto fail; } @@ -1386,16 +1373,17 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t unsigned usage, mask; njs_int_t ret; njs_str_t salt, info; - njs_value_t value, *aobject, *dobject; + njs_value_t *value, *aobject, *dobject; const EVP_MD *md; EVP_PKEY_CTX *pctx; njs_webcrypto_key_t *key, *dkey; + njs_opaque_value_t lvalue; njs_webcrypto_hash_t hash; njs_webcrypto_algorithm_t *alg, *dalg; - static const njs_value_t string_info = njs_string("info"); - static const njs_value_t string_salt = njs_string("salt"); - static const njs_value_t string_iterations = njs_string("iterations"); + static const njs_str_t string_info = njs_str("info"); + static const njs_str_t string_salt = njs_str("salt"); + static const njs_str_t string_iterations = njs_str("iterations"); aobject = njs_arg(args, nargs, 1); alg = njs_key_algorithm(vm, aobject); @@ -1406,22 +1394,22 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, njs_arg(args, nargs, 2)); if (njs_slow_path(key == NULL)) { - njs_type_error(vm, "\"baseKey\" is not a CryptoKey object"); + njs_vm_error(vm, "\"baseKey\" is not a CryptoKey object"); goto fail; } mask = derive_key ? NJS_KEY_USAGE_DERIVE_KEY : NJS_KEY_USAGE_DERIVE_BITS; if (njs_slow_path(!(key->usage & mask))) { - njs_type_error(vm, "provide key does not support \"%s\" operation", - derive_key ? "deriveKey" : "deriveBits"); + njs_vm_error(vm, "provide key does not support \"%s\" operation", + derive_key ? "deriveKey" : "deriveBits"); goto fail; } if (njs_slow_path(key->alg != alg)) { - njs_type_error(vm, "cannot derive %s using \"%V\" with \"%V\" key", - derive_key ? "key" : "bits", - njs_algorithm_string(key->alg), - njs_algorithm_string(alg)); + njs_vm_error(vm, "cannot derive %s using \"%V\" with \"%V\" key", + derive_key ? "key" : "bits", + njs_algorithm_string(key->alg), + njs_algorithm_string(alg)); goto fail; } @@ -1433,22 +1421,18 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t goto fail; } - ret = njs_value_property(vm, dobject, njs_value_arg(&string_length), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "derivedKeyAlgorithm.length " - "is not provided"); - goto fail; - } + value = njs_vm_object_prop(vm, dobject, &string_length, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "derivedKeyAlgorithm.length is not provided"); + goto fail; } } else { dalg = NULL; - njs_value_assign(&value, dobject); - } - - ret = njs_value_to_integer(vm, &value, &length); + value = dobject; + } + + ret = njs_value_to_integer(vm, value, &length); if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -1463,16 +1447,16 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t case NJS_ALGORITHM_AES_CBC: if (length != 16 && length != 32) { - njs_type_error(vm, "deriveKey \"%V\" length must be 128 or 256", - njs_algorithm_string(dalg)); + njs_vm_error(vm, "deriveKey \"%V\" length must be 128 or 256", + njs_algorithm_string(dalg)); goto fail; } break; default: - njs_internal_error(vm, "not implemented deriveKey: \"%V\"", - njs_algorithm_string(dalg)); + njs_vm_error(vm, "not implemented deriveKey: \"%V\"", + njs_algorithm_string(dalg)); goto fail; } @@ -1482,15 +1466,15 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t } if (njs_slow_path(usage & ~dalg->usage)) { - njs_type_error(vm, "unsupported key usage for \"%V\" key", - njs_algorithm_string(alg)); + njs_vm_error(vm, "unsupported key usage for \"%V\" key", + njs_algorithm_string(alg)); goto fail; } dkey = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_webcrypto_key_t)); if (njs_slow_path(dkey == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); goto fail; } @@ -1500,7 +1484,7 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t k = njs_mp_zalloc(njs_vm_memory_pool(vm), length); if (njs_slow_path(k == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); goto fail; } @@ -1511,39 +1495,30 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t goto fail; } - ret = njs_value_property(vm, aobject, njs_value_arg(&string_salt), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "PBKDF2 algorithm.salt is not provided"); - } - + value = njs_vm_object_prop(vm, aobject, &string_salt, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "PBKDF2 algorithm.salt is not provided"); goto fail; } - ret = njs_vm_value_to_bytes(vm, &salt, &value); + ret = njs_vm_value_to_bytes(vm, &salt, value); if (njs_slow_path(ret != NJS_OK)) { goto fail; } if (njs_slow_path(salt.length < 16)) { - njs_type_error(vm, "PBKDF2 algorithm.salt must be " - "at least 16 bytes long"); + njs_vm_error(vm, "PBKDF2 algorithm.salt must be " + "at least 16 bytes long"); goto fail; } - ret = njs_value_property(vm, aobject, njs_value_arg(&string_iterations), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "PBKDF2 algorithm.iterations " - "is not provided"); - } - + value = njs_vm_object_prop(vm, aobject, &string_iterations, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "PBKDF2 algorithm.iterations is not provided"); goto fail; } - ret = njs_value_to_integer(vm, &value, &iterations); + ret = njs_value_to_integer(vm, value, &iterations); if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -1566,32 +1541,24 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t goto fail; } - ret = njs_value_property(vm, aobject, njs_value_arg(&string_salt), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "HKDF algorithm.salt is not provided"); - } - + value = njs_vm_object_prop(vm, aobject, &string_salt, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "HKDF algorithm.salt is not provided"); goto fail; } - ret = njs_vm_value_to_bytes(vm, &salt, &value); + ret = njs_vm_value_to_bytes(vm, &salt, value); if (njs_slow_path(ret != NJS_OK)) { goto fail; } - ret = njs_value_property(vm, aobject, njs_value_arg(&string_info), - &value); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - njs_type_error(vm, "HKDF algorithm.info is not provided"); - } - + value = njs_vm_object_prop(vm, aobject, &string_info, &lvalue); + if (value == NULL) { + njs_vm_error(vm, "HKDF algorithm.info is not provided"); goto fail; } - ret = njs_vm_value_to_bytes(vm, &info, &value); + ret = njs_vm_value_to_bytes(vm, &info, value); if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -1659,8 +1626,8 @@ free: case NJS_ALGORITHM_ECDH: default: - njs_internal_error(vm, "not implemented deriveKey " - "algorithm: \"%V\"", njs_algorithm_string(alg)); + njs_vm_error(vm, "not implemented deriveKey " + "algorithm: \"%V\"", njs_algorithm_string(alg)); goto fail; } @@ -1675,18 +1642,19 @@ free: dkey->raw.start = k; dkey->raw.length = length; - ret = njs_vm_external_create(vm, &value, + ret = njs_vm_external_create(vm, njs_value_arg(&lvalue), njs_webcrypto_crypto_key_proto_id, dkey, 0); } else { - ret = njs_vm_value_array_buffer_set(vm, &value, k, length); + ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&lvalue), k, + length); } if (njs_slow_path(ret != NJS_OK)) { goto fail; } - return njs_webcrypto_result(vm, &value, NJS_OK, retval); + return njs_webcrypto_result(vm, &lvalue, NJS_OK, retval); fail: @@ -1702,8 +1670,8 @@ njs_ext_digest(njs_vm_t *vm, njs_value_t u_char *dst; njs_str_t data; njs_int_t ret; - njs_value_t value; const EVP_MD *md; + njs_opaque_value_t result; njs_webcrypto_hash_t hash; ret = njs_algorithm_hash(vm, njs_arg(args, nargs, 1), &hash); @@ -1721,7 +1689,7 @@ njs_ext_digest(njs_vm_t *vm, njs_value_t dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen); if (njs_slow_path(dst == NULL)) { - njs_memory_error(vm); + njs_vm_memory_error(vm); goto fail; } @@ -1731,12 +1699,12 @@ njs_ext_digest(njs_vm_t *vm, njs_value_t goto fail; } - ret = njs_vm_value_array_buffer_set(vm, &value, dst, olen); + ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&result), dst, olen); if (njs_slow_path(ret != NJS_OK)) { goto fail; } - return njs_webcrypto_result(vm, &value, NJS_OK, retval); + return njs_webcrypto_result(vm, &result, NJS_OK, retval); fail: @@ -1745,8 +1713,8 @@ fail: static njs_int_t -njs_export_base64url_bignum(njs_vm_t *vm, njs_value_t *retval, const BIGNUM *v, - size_t size) +njs_export_base64url_bignum(njs_vm_t *vm, njs_opaque_value_t *retval, + const BIGNUM *v, size_t size) { njs_str_t src; u_char buf[512]; @@ -1762,36 +1730,35 @@ njs_export_base64url_bignum(njs_vm_t *vm src.start = buf; src.length = size; - return njs_string_base64url(vm, retval, &src); + return njs_string_base64url(vm, njs_value_arg(retval), &src); } static njs_int_t -njs_base64url_bignum_set(njs_vm_t *vm, njs_value_t *jwk, njs_value_t *key, +njs_base64url_bignum_set(njs_vm_t *vm, njs_value_t *jwk, const njs_str_t *key, const BIGNUM *v, size_t size) { - njs_int_t ret; - njs_value_t value; + njs_int_t ret; + njs_opaque_value_t value; ret = njs_export_base64url_bignum(vm, &value, v, size); if (ret != NJS_OK) { return NJS_ERROR; } - return njs_value_property_set(vm, jwk, key, &value); + return njs_vm_object_prop_set(vm, jwk, key, &value); } static njs_int_t njs_export_jwk_rsa(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval) { - njs_int_t ret; - const RSA *rsa; - njs_str_t *nm; - njs_value_t nvalue, evalue, alg; - const BIGNUM *n_bn, *e_bn, *d_bn, *p_bn, *q_bn, *dp_bn, *dq_bn, *qi_bn; - - static const njs_value_t rsa_str = njs_string("RSA"); + njs_int_t ret; + const RSA *rsa; + njs_str_t *nm; + const BIGNUM *n_bn, *e_bn, *d_bn, *p_bn, *q_bn, *dp_bn, *dq_bn, + *qi_bn; + njs_opaque_value_t nvalue, evalue, alg, rsa_s; rsa = njs_pkey_get_rsa_key(key->pkey); @@ -1807,8 +1774,24 @@ njs_export_jwk_rsa(njs_vm_t *vm, njs_web return NJS_ERROR; } - ret = njs_vm_object_alloc(vm, retval, &string_kty, &rsa_str, &string_n, - &nvalue, &string_e, &evalue, NULL); + ret = njs_vm_object_alloc(vm, retval, NULL); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + njs_vm_value_string_set(vm, njs_value_arg(&rsa_s), (u_char *) "RSA", 3); + + ret = njs_vm_object_prop_set(vm, retval, &string_kty, &rsa_s); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_n, &nvalue); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_e, &evalue); if (ret != NJS_OK) { return NJS_ERROR; } @@ -1817,38 +1800,32 @@ njs_export_jwk_rsa(njs_vm_t *vm, njs_web njs_rsa_get0_factors(rsa, &p_bn, &q_bn); njs_rsa_get0_ctr_params(rsa, &dp_bn, &dq_bn, &qi_bn); - ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_d), - d_bn, 0); + ret = njs_base64url_bignum_set(vm, retval, &string_d, d_bn, 0); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_p), - p_bn, 0); + ret = njs_base64url_bignum_set(vm, retval, &string_p, p_bn, 0); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_q), - q_bn, 0); + ret = njs_base64url_bignum_set(vm, retval, &string_q, q_bn, 0); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_dp), - dp_bn, 0); + ret = njs_base64url_bignum_set(vm, retval, &string_dp, dp_bn, 0); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_dq), - dq_bn, 0); + ret = njs_base64url_bignum_set(vm, retval, &string_dq, dq_bn, 0); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_base64url_bignum_set(vm, retval, njs_value_arg(&string_qi), - qi_bn, 0); + ret = njs_base64url_bignum_set(vm, retval, &string_qi, qi_bn, 0); if (ret != NJS_OK) { return NJS_ERROR; } @@ -1856,9 +1833,10 @@ njs_export_jwk_rsa(njs_vm_t *vm, njs_web nm = &njs_webcrypto_alg_name[key->alg->type][key->hash]; - (void) njs_vm_value_string_set(vm, &alg, nm->start, nm->length); - - return njs_value_property_set(vm, retval, njs_value_arg(&string_alg), &alg); + (void) njs_vm_value_string_set(vm, njs_value_arg(&alg), nm->start, + nm->length); + + return njs_vm_object_prop_set(vm, retval, &string_alg, &alg); } @@ -1868,15 +1846,13 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc int nid, group_bits, group_bytes; BIGNUM *x_bn, *y_bn; njs_int_t ret; - njs_value_t xvalue, yvalue, dvalue, name; const EC_KEY *ec; const BIGNUM *d_bn; const EC_POINT *pub; const EC_GROUP *group; + njs_opaque_value_t xvalue, yvalue, dvalue, name, ec_s; njs_webcrypto_entry_t *e; - static const njs_value_t ec_str = njs_string("EC"); - x_bn = NULL; y_bn = NULL; d_bn = NULL; @@ -1924,24 +1900,44 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc for (e = &njs_webcrypto_curve[0]; e->name.length != 0; e++) { if ((uintptr_t) nid == e->value) { - (void) njs_vm_value_string_set(vm, &name, e->name.start, - e->name.length); + (void) njs_vm_value_string_set(vm, njs_value_arg(&name), + e->name.start, e->name.length); break; } } if (e->name.length == 0) { - njs_type_error(vm, "Unsupported JWK EC curve: %s", OBJ_nid2sn(nid)); + njs_vm_error(vm, "Unsupported JWK EC curve: %s", OBJ_nid2sn(nid)); goto fail; } - ret = njs_vm_object_alloc(vm, retval, &string_kty, &ec_str, &string_x, - &xvalue, &string_y, &yvalue, &string_crv, &name, - NULL); + ret = njs_vm_object_alloc(vm, retval, NULL); if (ret != NJS_OK) { goto fail; } + njs_vm_value_string_set(vm, njs_value_arg(&ec_s), (u_char *) "EC", 2); + + ret = njs_vm_object_prop_set(vm, retval, &string_kty, &ec_s); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_x, &xvalue); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_y, &yvalue); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_crv, &name); + if (ret != NJS_OK) { + return NJS_ERROR; + } + if (key->privat) { d_bn = EC_KEY_get0_private_key(ec); @@ -1950,8 +1946,7 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc goto fail; } - ret = njs_value_property_set(vm, retval, njs_value_arg(&string_d), - &dvalue); + ret = njs_vm_object_prop_set(vm, retval, &string_d, &dvalue); if (ret != NJS_OK) { goto fail; } @@ -1986,8 +1981,8 @@ njs_export_raw_ec(njs_vm_t *vm, njs_webc njs_assert(key->pkey != NULL); if (key->privat) { - njs_type_error(vm, "private key of \"%V\" cannot be exported " - "in \"raw\" format", njs_algorithm_string(key->alg)); + njs_vm_error(vm, "private key of \"%V\" cannot be exported " + "in \"raw\" format", njs_algorithm_string(key->alg)); return NJS_ERROR; } @@ -2022,8 +2017,8 @@ static njs_int_t njs_export_jwk_asymmetric(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval) { - njs_int_t ret; - njs_value_t ops, extractable; + njs_int_t ret; + njs_opaque_value_t ops, extractable; njs_assert(key->pkey != NULL); @@ -2048,7 +2043,7 @@ njs_export_jwk_asymmetric(njs_vm_t *vm, break; default: - njs_type_error(vm, "provided key cannot be exported as JWK"); + njs_vm_error(vm, "provided key cannot be exported as JWK"); return NJS_ERROR; } From arut at nginx.com Wed May 3 13:00:44 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 3 May 2023 17:00:44 +0400 Subject: [PATCH 1 of 3] QUIC: changed path validation timeout In-Reply-To: <121AA5DA-1F59-4544-A3CF-089CAC9B3CA8@nginx.com> References: <121AA5DA-1F59-4544-A3CF-089CAC9B3CA8@nginx.com> Message-ID: <20230503130044.m7l73z52qgxv6aym@N00W24XTQX> Hi, On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote: > > > > On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1679925333 -14400 > > # Mon Mar 27 17:55:33 2023 +0400 > > # Branch quic > > # Node ID f76e83412133085a6c82fce2c3e15b2c34a6e959 > > # Parent 5fd628b89bb7fb5c95afa1dc914385f7ab79f6a3 > > QUIC: changed path validation timeout. > > > > Path validation packets containing PATH_CHALLENGE frames are sent separately > > from regular frame queue, because of the need to use a decicated path and > > pad the packets. The packets are also resent separately from the regular > > probe/lost detection mechanism. A path validation packet is resent 3 times, > > each time after PTO expiration. Assuming constant PTO, the overall maximum > > waiting time is 3 * PTO. According to RFC 9000, 8.2.4. Failed Path Validation, > > the following value is recommended as a validation timeout: > > > > A value of three times the larger of the current PTO > > or the PTO for the new path (using kInitialRtt, as > > defined in [QUIC-RECOVERY]) is RECOMMENDED. > > > > The change adds PTO of the new path to the equation as the lower bound. > > Also, max_ack_delay is now always accounted for, unlike previously, when > > it was only used when there are packets in flight. As mentioned before, > > PACH_CHALLENGE is not considered in-flight by nginx since it's processed > > separately, but technically it is. > > I don't like an idea to make a separate function to calculate > time for path validation retransmits. It looks like an existing > function could be reused. > > I tend to think checking for inflight packets in ngx_quic_pto() > isn't correct at the first place. The condition comes from > the GetPtoTimeAndSpace example in 9002, A.8: > > : GetPtoTimeAndSpace(): > : duration = (smoothed_rtt + max(4 * rttvar, kGranularity)) > : * (2 ^ pto_count) > : // Anti-deadlock PTO starts from the current time > : if (no ack-eliciting packets in flight): > : assert(!PeerCompletedAddressValidation()) > : if (has handshake keys): > : return (now() + duration), Handshake > : else: > : return (now() + duration), Initial > : <..> > : return pto_timeout, pto_space > > But PeerCompletedAddressValidation is always true for the server. > The above anti-deadlock measure seems to only make sense for a client > when it has no new data to send, but forced to send something to rise > an anti-amplification limit for the server. This thought is supported > by commentaries in places of GetPtoTimeAndSpace use. > > Removing the condition from ngx_quic_pto() makes possible to unify > the function to use it for both regular PTO and path validation. > > Next is to make retransmits similar to a new connection establishment. > Per RFC 9000, 8.2.1: > : An endpoint SHOULD NOT probe a new path with packets containing a > : PATH_CHALLENGE frame more frequently than it would send an Initial packet. > > I think we can improve path validation to use a separate backoff, > path->tries can be used to base a backoff upon it. > > Since PATH_CHALLENGE are resent separately from the regular probe/lost > detection mechanism, this needs to be moved out from ngx_quic_pto(). > > This makes the following series based on your patch. > We could set an overall maximum waiting time of 3 * PTO and test it > in pv handler in addition to the check for NGX_QUIC_PATH_RETRIES. Jftr, discussed all of the above in person. Agreed to implement that. > # HG changeset patch > # User Sergey Kandaurov > # Date 1682332923 -14400 > # Mon Apr 24 14:42:03 2023 +0400 > # Branch quic > # Node ID f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 > # Parent d6861ecf8a9cf4e98d9ed6f4435054d106b29f48 > QUIC: removed check for in-flight packets in computing PTO. > > The check is needed for clients in order to unblock a server due to > anti-amplification limits, and it seems to make no sense for servers. > See RFC 9002, A.6 and A.8 for a further explanation. > > This makes max_ack_delay to now always account, notably including > PATH_CHALLENGE timers as noted in the last paragraph of 9000, 9.4, > unlike when it was only used when there are packets in flight. > > While here, fixed nearby style. > > 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 > @@ -782,15 +782,11 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu > qc = ngx_quic_get_connection(c); > > /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ > + > duration = qc->avg_rtt; > - > duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); > duration <<= qc->pto_count; > > - if (qc->congestion.in_flight == 0) { /* no in-flight packets */ > - return duration; > - } > - > if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { > duration += qc->ctp.max_ack_delay << qc->pto_count; > } Looks ok. > # HG changeset patch > # User Sergey Kandaurov > # Date 1682338151 -14400 > # Mon Apr 24 16:09:11 2023 +0400 > # Branch quic > # Node ID 808fe808e276496a9b026690c141201720744ab3 > # Parent f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 > QUIC: separated path validation retransmit backoff. > > Path validation packets containing PATH_CHALLENGE frames are sent separately > from regular frame queue, because of the need to use a decicated path and pad > the packets. The packets are sent periodically, separately from the regular > probe/lost detection mechanism. A path validation packet is resent up to 3 > times, each time after PTO expiration, with increasing per-path PTO backoff. > > 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 > @@ -736,7 +736,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) - now); > + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) > + - now); > > if (w < 0) { > w = 0; > @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu > > duration = qc->avg_rtt; > duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); > - duration <<= qc->pto_count; > > if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { > - duration += qc->ctp.max_ack_delay << qc->pto_count; > + duration += qc->ctp.max_ack_delay; > } > > return duration; > @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) > continue; > } > > - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { > + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) > + - now) > 0) > + { > continue; > } > > 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 > @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t > "quic initiated validation of path seq:%uL", path->seqnum); > > path->validating = 1; > + path->tries = 0; > > if (RAND_bytes(path->challenge1, 8) != 1) { > return NGX_ERROR; > @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t > pto = ngx_quic_pto(c, ctx); > > path->expires = ngx_current_msec + pto; > - path->tries = NGX_QUIC_PATH_RETRIES; > > if (!qc->path_validation.timer_set) { > ngx_add_timer(&qc->path_validation, pto); > @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve > qc = ngx_quic_get_connection(c); > > ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); > - pto = ngx_quic_pto(c, ctx); > > next = -1; > now = ngx_current_msec; > @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve > continue; > } > > - if (--path->tries) { > + if (++path->tries < NGX_QUIC_PATH_RETRIES) { > + pto = ngx_quic_pto(c, ctx) << path->tries; Here we schedule a timer for 2 * PTO or 4 * PTO. Technically, our path validation code allows several paths to be validated at the same time. If that happens, 2 * PTO and 4 * PTO will be too much for the first attempt for the new path, where the timeout is just PTO. As a result, the last path may wait 2x or 4x longer than needed. Luckily, this is not possible, because when we start validation of a path, the validation for the old path is stopped. This allows us to simplify the PATH_RESPONSE and timeout handlers to only handle qc->path. I suggest to add a patch for this. It will also affect PMTUD, since the same handler will be responsible for MTU packets. Also, it's probably a good idea to suspend PMTUD for the old path when we switch to a new one. This will happend naturally if we support only one path in those handlers. > path->expires = ngx_current_msec + pto; > > if (next == -1 || pto < next) { > # HG changeset patch > # User Sergey Kandaurov > # Date 1682338293 -14400 > # Mon Apr 24 16:11:33 2023 +0400 > # Branch quic > # Node ID 760ee5baed4d1370a92f5d3a2b82d4a28ac8bae5 > # Parent 808fe808e276496a9b026690c141201720744ab3 > QUIC: lower bound path validation PTO. > > According to RFC 9000, 8.2.4. Failed Path Validation, > the following value is recommended as a validation timeout: > > A value of three times the larger of the current PTO > or the PTO for the new path (using kInitialRtt, as > defined in [QUIC-RECOVERY]) is RECOMMENDED. > > The change adds PTO of the new path to the equation as the lower bound. > > 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 > @@ -511,7 +511,7 @@ ngx_quic_validate_path(ngx_connection_t > } > > ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); > - pto = ngx_quic_pto(c, ctx); > + pto = ngx_max(ngx_quic_pto(c, ctx), 1000); > > path->expires = ngx_current_msec + pto; > > @@ -605,7 +605,7 @@ ngx_quic_path_validation_handler(ngx_eve > } > > if (++path->tries < NGX_QUIC_PATH_RETRIES) { > - pto = ngx_quic_pto(c, ctx) << path->tries; > + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; > > path->expires = ngx_current_msec + pto; Looks ok. > > > > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From pluknet at nginx.com Wed May 3 15:14:35 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 3 May 2023 19:14:35 +0400 Subject: [PATCH 02 of 11] Tests: removed unneeded require from proxy_ssl_keepalive.t In-Reply-To: References: <6f0148ef1991d92a003c.1681702286@vm-bsd.mdounin.ru> <44FD297A-6B6A-4625-A959-8265A51D7480@nginx.com> Message-ID: <6EC17BEC-C663-4045-8A49-192B90223FC1@nginx.com> > On 3 May 2023, at 05:26, Maxim Dounin wrote: > > Hello! > > On Tue, May 02, 2023 at 05:49:23PM +0400, Sergey Kandaurov wrote: > >> >>> On 17 Apr 2023, at 07:31, Maxim Dounin wrote: >>> >>> # HG changeset patch >>> # User Maxim Dounin >>> # Date 1681702250 -10800 >>> # Mon Apr 17 06:30:50 2023 +0300 >>> # Node ID 6f0148ef1991d92a003c8529c8cce9a8dd49e706 >>> # Parent a01b7d84f4355073a00f43760fc512e03b4452c3 >>> Tests: removed unneeded require from proxy_ssl_keepalive.t. >>> >>> diff --git a/proxy_ssl_keepalive.t b/proxy_ssl_keepalive.t >>> --- a/proxy_ssl_keepalive.t >>> +++ b/proxy_ssl_keepalive.t >>> @@ -22,9 +22,6 @@ use Test::Nginx; >>> select STDERR; $| = 1; >>> select STDOUT; $| = 1; >>> >>> -eval { require IO::Socket::SSL; }; >>> -plan(skip_all => 'IO::Socket::SSL not installed') if $@; >>> - >>> my $t = Test::Nginx->new()->has(qw/http http_ssl proxy upstream_keepalive/) >>> ->has_daemon('openssl')->plan(3) >>> ->write_file_expand('nginx.conf', <<'EOF'); >> >> We can as well remove it from h2_ssl_proxy_cache.t and h2_ssl_variables.t >> after 45c80276d691, HTTP2 package handles that for us. >> (Same approach used for the crypto layer of HTTP/3 tests.) > > Both h2_ssl_proxy_cache.t and h2_ssl_variables.t actually use > IO::Socket::SSL, even if the actual use is within > Test::Nginx::HTTP2, and will behave incorrectly if IO::Socket::SSL > is not available (as currently written, they won't fail, but will > skip individual tests with incorrect reasoning). As such, they do > need these requires (and the corresponding plan(skip_all) call if > IO::Socket::SSL is not available). > I tend to disagree about reasoning: it's most specific, not incorrect. Your mileage may vary. HTTP/2 implementation requires to be negotiated via ALPN (NPN was a fallback, before it was decommissioned in 1.21.4), otherwise it continues as if it were HTTP/1.1. So we have to check in tests that HTTP/2 was negotiated, and as such - we can provide most specific skip reasoning. General checking for IO::Socket:SSL, if not installed, provides generic skip reasoning, which in fact doesn't advise that installing an arbitrary (old enough) IO::Socket:SSL may not be enough: $ perl h2_ssl_variables.t 1..0 # SKIP IO::Socket::SSL not installed After removing require from h2_ssl_variables.t: $ perl h2_ssl_variables.t 1..10 ok 1 # skip OpenSSL NPN support required ok 2 # skip OpenSSL ALPN support required ok 3 # skip OpenSSL NPN support required ok 4 # skip OpenSSL ALPN support required ok 5 # skip OpenSSL NPN support required ok 6 # skip OpenSSL ALPN support required ok 7 # skip OpenSSL NPN support required ok 8 # skip OpenSSL ALPN support required ok 9 - no alerts ok 10 - no sanitizer errors This is somewhat similar to IO::Uncompress::Gunzip handling. The difference is that gunzip test is run/skipped right here, whereas IO::Socket::SSL check and skip are moved apart. For comparison, SSL tests in mail_imap_ssl.t indeed require IO::Socket::SSL directly in mail_imap_ssl.t, as otherwise it won't resolve IO::Socket::SSL::SSL_VERIFY_NONE. BTW, it's probably time to remove NPN from tests. Removing NPN is a good reason to review the SSL flag of HTTP2::new_socket(). It has received a limited use (cf. h2_ssl.t, h2_ssl_verify_client.t), it may have sense to just remove it. > In contrast, proxy_ssl_keepalive.t does not use IO::Socket::SSL at > all. All client connections initiated by the test are plain text, > and SSL is only used within nginx itself. As such, > IO::Socket::SSL is not required to run the test (and hence the > patch). > > -- > Maxim Dounin > http://mdounin.ru/ > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Sergey Kandaurov From pluknet at nginx.com Wed May 3 16:20:02 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 3 May 2023 20:20:02 +0400 Subject: [PATCH 03 of 11] Tests: added has_feature() tests for IO::Socket::SSL In-Reply-To: References: Message-ID: <48161DDE-6D84-4F21-AEC1-157FB555C683@nginx.com> > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > # HG changeset patch > # User Maxim Dounin > # Date 1681702252 -10800 > # Mon Apr 17 06:30:52 2023 +0300 > # Node ID f704912ed09f3494a815709710c3744b0adca50b > # Parent 6f0148ef1991d92a003c8529c8cce9a8dd49e706 > Tests: added has_feature() tests for IO::Socket::SSL. > > The following distinct features supported: > > - "socket_ssl", which requires IO::Socket::SSL and also implies > existance of the IO::Socket::SSL::SSL_VERIFY_NONE() symbol. > It is used by most of the tests. > SSL_VERIFY_NONE was added in IO::Socket::SSL 1.31 (2009.09.25). The check was added primarily for then supported CentOS 5. Now CentOS 5 is long obsolete, SSL_VERIFY_NONE can be dropped. Most popular modern distributions have something of IO::Socket::SSL 2.0xx, the oldest still supported CentOS 7 has IO::Socket::SSL 1.94. > - "socket_ssl_sni", which requires IO::Socket::SSL with the can_client_sni() > function (1.84), and SNI support available in Net::SSLeay and the OpenSSL > library being used. Used by ssl_sni.t, ssl_sni_sessions.t, > stream_ssl_preread.t. Additional Net::SSLeay testing is believed to be > unneeded and was removed. > I agree that Net::SSLeay is believed to be redundant: properly implemented IO::Socket::SSL should detect Net::SSLeay is too old and behave accordingly. Note that you removed can_client_sni() from h2_ssl_verify_client.t. In fact, it is replaced with "socket_ssl_alpn". ALPN support was added to IO::Socket::SSL after SNI, so it should work in practice, although not evident. Formally, the "socket_ssl_sni" prerequisite needs to be added. On the other way, can_client_sni() was added in 1.83, while SNI support factually was added noticeably earlier - in version 1.56, via SSL_hostname. Using IO::Socket::SSL 1.82 allowed me to run the following tests that would be otherwise skipped due to socket_ssl_sni: ssl_sni.t - replacing with "socket_ssl" is enough stream_ssl_preread.t - replacing with "socket_ssl" is enough ssl_sni_sessions.t - also needs SSL_VERIFY_NONE in get_ssl_socket() to stop whining on old IO::Socket::SSL that presumably doesn't get it from $ctx. It won't run anyway though due to IO::Socket::SSL version is too old to support TLSv1.3 sessions. If we tend to drop anything older than 1.94 (CentOS 7), then we can freely drop "socket_ssl_sni" from the patch. SSL_hostname is already there. diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm --- a/lib/Test/Nginx.pm +++ b/lib/Test/Nginx.pm @@ -244,15 +244,9 @@ sub has_feature($) { if ($feature =~ /^socket_ssl/) { eval { require IO::Socket::SSL; }; return 0 if $@; - eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; - return 0 if $@; if ($feature eq 'socket_ssl') { return 1; } - if ($feature eq 'socket_ssl_sni') { - eval { IO::Socket::SSL->can_client_sni() or die; }; - return !$@; - } if ($feature eq 'socket_ssl_alpn') { eval { IO::Socket::SSL->can_alpn() or die; }; return !$@; diff --git a/ssl_sni.t b/ssl_sni.t --- a/ssl_sni.t +++ b/ssl_sni.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_ssl sni rewrite socket_ssl_sni/) +my $t = Test::Nginx->new()->has(qw/http http_ssl sni rewrite socket_ssl/) ->has_daemon('openssl')->plan(8) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/ssl_sni_sessions.t b/ssl_sni_sessions.t --- a/ssl_sni_sessions.t +++ b/ssl_sni_sessions.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_ssl sni rewrite socket_ssl_sni/) +my $t = Test::Nginx->new()->has(qw/http http_ssl sni rewrite socket_ssl/) ->has_daemon('openssl') ->write_file_expand('nginx.conf', <<'EOF'); @@ -177,6 +177,7 @@ sub get_ssl_socket { PeerPort => $port, SSL_hostname => $host, SSL_reuse_ctx => $ctx, + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), SSL_error_trap => sub { die $_[1] } ); alarm(0); diff --git a/stream_ssl_preread.t b/stream_ssl_preread.t --- a/stream_ssl_preread.t +++ b/stream_ssl_preread.t @@ -24,7 +24,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/stream stream_map stream_ssl_preread/) - ->has(qw/stream_ssl stream_return socket_ssl_sni/) + ->has(qw/stream_ssl stream_return socket_ssl/) ->has_daemon('openssl')->plan(13) ->write_file_expand('nginx.conf', <<'EOF'); > - "socket_ssl_alpn", which requires IO::Socket::SSL with ALPN support (2.009), > and ALPN support in Net::SSLeay and the OpenSSL library being used. > Used by h2_ssl.t, h2_ssl_verify_client.t, stream_ssl_alpn.t, > stream_ssl_preread_alpn.t. > > - "socket_ssl_sslversion", which requires IO::Socket::SSL with > the get_sslversion() and get_sslversion_int() methods (1.964). > Used by mail_imap_ssl.t. I don't like that the whole mail_imap_ssl.t is skipped on < 1.964 (even though it is not quite new and most distributions have it now), because of a minor optional feature that could not be tested. In principle, we can rewrite the test similar to $ssl_protocol to avoid dependency on 1.964: diff --git a/mail_imap_ssl.t b/mail_imap_ssl.t --- a/mail_imap_ssl.t +++ b/mail_imap_ssl.t @@ -29,7 +29,7 @@ select STDOUT; $| = 1; local $SIG{PIPE} = 'IGNORE'; my $t = Test::Nginx->new() - ->has(qw/mail mail_ssl imap http rewrite socket_ssl_sslversion/) + ->has(qw/mail mail_ssl imap http rewrite socket_ssl/) ->has_daemon('openssl')->plan(13) ->write_file_expand('nginx.conf', <<'EOF'); @@ -209,12 +209,9 @@ my $s = Test::Nginx::IMAP->new(PeerAddr # Auth-SSL-Protocol and Auth-SSL-Cipher headers -my ($cipher, $sslversion); +$s = get_ssl_socket(8143); -$s = get_ssl_socket(8143); -$cipher = $s->get_cipher(); -$sslversion = $s->get_sslversion(); -$sslversion =~ s/_/./; +my $cipher = $s->get_cipher(); undef $s; @@ -237,7 +234,7 @@ TODO: { local $TODO = 'not yet' unless $t->has_version('1.21.2'); $f = $t->read_file('auth2.log'); -like($f, qr|^$cipher:$sslversion$|m, 'log - cipher sslversion'); +like($f, qr/^$cipher:(TLS|SSL)v(\d|\.)+$/m, 'log - cipher sslversion'); } > > - "socket_ssl_reused", which requires IO::Socket::SSL with > the get_session_reused() method (2.057). To be used in the following > patches. > > This makes it possible to simplify and unify various SSL tests. [..] -- Sergey Kandaurov From mdounin at mdounin.ru Thu May 4 02:26:01 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 4 May 2023 05:26:01 +0300 Subject: [PATCH 02 of 11] Tests: removed unneeded require from proxy_ssl_keepalive.t In-Reply-To: <6EC17BEC-C663-4045-8A49-192B90223FC1@nginx.com> References: <6f0148ef1991d92a003c.1681702286@vm-bsd.mdounin.ru> <44FD297A-6B6A-4625-A959-8265A51D7480@nginx.com> <6EC17BEC-C663-4045-8A49-192B90223FC1@nginx.com> Message-ID: Hello! On Wed, May 03, 2023 at 07:14:35PM +0400, Sergey Kandaurov wrote: > > On 3 May 2023, at 05:26, Maxim Dounin wrote: > > > > Hello! > > > > On Tue, May 02, 2023 at 05:49:23PM +0400, Sergey Kandaurov wrote: > > > >> > >>> On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > >>> > >>> # HG changeset patch > >>> # User Maxim Dounin > >>> # Date 1681702250 -10800 > >>> # Mon Apr 17 06:30:50 2023 +0300 > >>> # Node ID 6f0148ef1991d92a003c8529c8cce9a8dd49e706 > >>> # Parent a01b7d84f4355073a00f43760fc512e03b4452c3 > >>> Tests: removed unneeded require from proxy_ssl_keepalive.t. > >>> > >>> diff --git a/proxy_ssl_keepalive.t b/proxy_ssl_keepalive.t > >>> --- a/proxy_ssl_keepalive.t > >>> +++ b/proxy_ssl_keepalive.t > >>> @@ -22,9 +22,6 @@ use Test::Nginx; > >>> select STDERR; $| = 1; > >>> select STDOUT; $| = 1; > >>> > >>> -eval { require IO::Socket::SSL; }; > >>> -plan(skip_all => 'IO::Socket::SSL not installed') if $@; > >>> - > >>> my $t = Test::Nginx->new()->has(qw/http http_ssl proxy upstream_keepalive/) > >>> ->has_daemon('openssl')->plan(3) > >>> ->write_file_expand('nginx.conf', <<'EOF'); > >> > >> We can as well remove it from h2_ssl_proxy_cache.t and h2_ssl_variables.t > >> after 45c80276d691, HTTP2 package handles that for us. > >> (Same approach used for the crypto layer of HTTP/3 tests.) > > > > Both h2_ssl_proxy_cache.t and h2_ssl_variables.t actually use > > IO::Socket::SSL, even if the actual use is within > > Test::Nginx::HTTP2, and will behave incorrectly if IO::Socket::SSL > > is not available (as currently written, they won't fail, but will > > skip individual tests with incorrect reasoning). As such, they do > > need these requires (and the corresponding plan(skip_all) call if > > IO::Socket::SSL is not available). > > > > I tend to disagree about reasoning: it's most specific, not incorrect. > Your mileage may vary. > > HTTP/2 implementation requires to be negotiated via ALPN > (NPN was a fallback, before it was decommissioned in 1.21.4), > otherwise it continues as if it were HTTP/1.1. > So we have to check in tests that HTTP/2 was negotiated, > and as such - we can provide most specific skip reasoning. > > General checking for IO::Socket:SSL, if not installed, provides > generic skip reasoning, which in fact doesn't advise that installing > an arbitrary (old enough) IO::Socket:SSL may not be enough: > > $ perl h2_ssl_variables.t > 1..0 # SKIP IO::Socket::SSL not installed > > After removing require from h2_ssl_variables.t: > > $ perl h2_ssl_variables.t > 1..10 > ok 1 # skip OpenSSL NPN support required > ok 2 # skip OpenSSL ALPN support required > ok 3 # skip OpenSSL NPN support required > ok 4 # skip OpenSSL ALPN support required > ok 5 # skip OpenSSL NPN support required > ok 6 # skip OpenSSL ALPN support required > ok 7 # skip OpenSSL NPN support required > ok 8 # skip OpenSSL ALPN support required > ok 9 - no alerts > ok 10 - no sanitizer errors There are two basic issues here as I see it: - The reasoning suggests that there is no "OpenSSL NPN support" and/or "OpenSSL ALPN support", while both are most likely available on the particular machine. Further, trying to fix this skip reason will always fail - because updating OpenSSL cannot help here. I don't see how it can be "most specific" reason, it's simply an incorrect one, hardly different from saying something like "TCP/IP support required". - This will break nicely as long as any of the tests is slightly modified and won't do ALPN/NPN checks on each request - for example, when checking SSL protocols or ciphers being negotiated. Existing code with IO::Socket::SSL checks is perfectly correct, and I see no reasons to remove them. Especially given that such checks are trivial for the tests after the patch series in question. > This is somewhat similar to IO::Uncompress::Gunzip handling. > The difference is that gunzip test is run/skipped right here, > whereas IO::Socket::SSL check and skip are moved apart. The difference is that gunzip tests are provide correct skip reasoning if IO::Uncompress::Gunzip is not available, and will never fail unexpectedly on any modifications since IO::Uncompress::Gunzip require and skip are in the same function. [...] > BTW, it's probably time to remove NPN from tests. No objections, but it's clearly outside of the scope of this patch series. > Removing NPN > is a good reason to review the SSL flag of HTTP2::new_socket(). > It has received a limited use (cf. h2_ssl.t, h2_ssl_verify_client.t), > it may have sense to just remove it. I don't think it's a good idea. Rather, it should be extended to be in par with functionality provided by http() and mail test modules with this patch series, that is, make it possible to provide additional SSL options. This will make it possible to remove custom socket creation code from h2_ssl.t and h2_ssl_verify_client.t as well. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu May 4 03:57:24 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 4 May 2023 06:57:24 +0300 Subject: [PATCH 03 of 11] Tests: added has_feature() tests for IO::Socket::SSL In-Reply-To: <48161DDE-6D84-4F21-AEC1-157FB555C683@nginx.com> References: <48161DDE-6D84-4F21-AEC1-157FB555C683@nginx.com> Message-ID: Hello! On Wed, May 03, 2023 at 08:20:02PM +0400, Sergey Kandaurov wrote: > > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > > > # HG changeset patch > > # User Maxim Dounin > > # Date 1681702252 -10800 > > # Mon Apr 17 06:30:52 2023 +0300 > > # Node ID f704912ed09f3494a815709710c3744b0adca50b > > # Parent 6f0148ef1991d92a003c8529c8cce9a8dd49e706 > > Tests: added has_feature() tests for IO::Socket::SSL. > > > > The following distinct features supported: > > > > - "socket_ssl", which requires IO::Socket::SSL and also implies > > existance of the IO::Socket::SSL::SSL_VERIFY_NONE() symbol. > > It is used by most of the tests. > > > > SSL_VERIFY_NONE was added in IO::Socket::SSL 1.31 (2009.09.25). > The check was added primarily for then supported CentOS 5. > Now CentOS 5 is long obsolete, SSL_VERIFY_NONE can be dropped. As implemented, with has(socket_ssl) at least version 1.31 of IO::Socket::SSL is required to run SSL tests. It still works correctly with older version though, and just skips SSL tests, so the test suite can be run on old platforms if needed. > Most popular modern distributions have something of IO::Socket::SSL 2.0xx, > the oldest still supported CentOS 7 has IO::Socket::SSL 1.94. > > > - "socket_ssl_sni", which requires IO::Socket::SSL with the can_client_sni() > > function (1.84), and SNI support available in Net::SSLeay and the OpenSSL > > library being used. Used by ssl_sni.t, ssl_sni_sessions.t, > > stream_ssl_preread.t. Additional Net::SSLeay testing is believed to be > > unneeded and was removed. > > > > I agree that Net::SSLeay is believed to be redundant: properly implemented > IO::Socket::SSL should detect Net::SSLeay is too old and behave accordingly. > > Note that you removed can_client_sni() from h2_ssl_verify_client.t. > In fact, it is replaced with "socket_ssl_alpn". ALPN support was added > to IO::Socket::SSL after SNI, so it should work in practice, although not > evident. Formally, the "socket_ssl_sni" prerequisite needs to be added. It was dropped intentionally, since ALPN support basically implies SNI support. I don't think trying to catch clients with ALPN but without SNI support worth the effort - there are no such clients. > On the other way, can_client_sni() was added in 1.83, while SNI support > factually was added noticeably earlier - in version 1.56, via SSL_hostname. > Using IO::Socket::SSL 1.82 allowed me to run the following tests > that would be otherwise skipped due to socket_ssl_sni: > > ssl_sni.t - replacing with "socket_ssl" is enough > stream_ssl_preread.t - replacing with "socket_ssl" is enough > ssl_sni_sessions.t - also needs SSL_VERIFY_NONE in get_ssl_socket() > to stop whining on old IO::Socket::SSL that presumably doesn't get it > from $ctx. It won't run anyway though due to IO::Socket::SSL version > is too old to support TLSv1.3 sessions. > > If we tend to drop anything older than 1.94 (CentOS 7), then we can freely > drop "socket_ssl_sni" from the patch. SSL_hostname is already there. I'm fine with skipping tests with older versions, but failing tests without clear reasoning looks wrong to me. That is, we can probably merge "socket_ssl_sni" requirements into "socket_ssl", but simply dropping them will result in failures on systems with old IO::Socket::SSL versions and/or systems with old OpenSSL. And this is something I would like to avoid - even if that's not something tested on a regular basis. Either way, current distinction with separate SNI tests looks good enough for me, and I would rather keep it as is. Especially given that it's basically effortless with this patch series. [...] > > - "socket_ssl_alpn", which requires IO::Socket::SSL with ALPN support (2.009), > > and ALPN support in Net::SSLeay and the OpenSSL library being used. > > Used by h2_ssl.t, h2_ssl_verify_client.t, stream_ssl_alpn.t, > > stream_ssl_preread_alpn.t. > > > > - "socket_ssl_sslversion", which requires IO::Socket::SSL with > > the get_sslversion() and get_sslversion_int() methods (1.964). > > Used by mail_imap_ssl.t. > > I don't like that the whole mail_imap_ssl.t is skipped on < 1.964 > (even though it is not quite new and most distributions have it now), > because of a minor optional feature that could not be tested. > In principle, we can rewrite the test similar to $ssl_protocol > to avoid dependency on 1.964: In no particular order: - It's a skip, not a failure. - IO::Socket::SSL 1.964 is rather old, and if only an older version of IO::Socket::SSL is available - this likely means that SSL isn't a specific feature being considered. - That's not only IMAP SSL test we have, and skipping it shouldn't be a problem: we still test at least some IMAP SSL functionality even on platforms with such outdated IO::Socket::SSL. - We still need "socket_ssl_sslversion" for other tests, notably mail_ssl_session_reuse.t and stream_ssl_session_reuse.t, see the following patches. Overall, I don't think it worth the effort. If you think it worth it, consider submitting a patch once this patch series settles. -- Maxim Dounin http://mdounin.ru/ From arut at nginx.com Thu May 4 15:04:55 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 04 May 2023 19:04:55 +0400 Subject: [PATCH] QUIC: fixed encryption level in ngx_quic_frame_sendto() Message-ID: <99591a6dd409e18adb6b.1683212695@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1683212627 -14400 # Thu May 04 19:03:47 2023 +0400 # Branch quic # Node ID 99591a6dd409e18adb6bd2a895d940f7c0415080 # Parent 2610c54070bec4e55b6c9d31c16e8526fc8ee1ae QUIC: fixed encryption level in ngx_quic_frame_sendto(). Previously, ssl_encryption_application was hardcoded. Before 9553eea74f2a ngx_quic_frame_sendto() was used only for PATH_CHALLENGE/PATH_RESPONSE, which are only sent at the application level. Since 9553eea74f2a, ngx_quic_frame_sendto() is also used for sending CONNECTION_CLOSE, which can be sent at initial level after SSL handshake error or rejection. This resulted in packet encryption error. Now level is copied from frame, which fixes the 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 @@ -1223,7 +1223,7 @@ ngx_quic_frame_sendto(ngx_connection_t * static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + ctx = ngx_quic_get_send_ctx(qc, frame->level); ngx_quic_init_packet(c, ctx, &pkt, path); From pluknet at nginx.com Thu May 4 15:32:00 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 4 May 2023 19:32:00 +0400 Subject: [PATCH] QUIC: fixed encryption level in ngx_quic_frame_sendto() In-Reply-To: <99591a6dd409e18adb6b.1683212695@arut-laptop> References: <99591a6dd409e18adb6b.1683212695@arut-laptop> Message-ID: > On 4 May 2023, at 19:04, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1683212627 -14400 > # Thu May 04 19:03:47 2023 +0400 > # Branch quic > # Node ID 99591a6dd409e18adb6bd2a895d940f7c0415080 > # Parent 2610c54070bec4e55b6c9d31c16e8526fc8ee1ae > QUIC: fixed encryption level in ngx_quic_frame_sendto(). > > Previously, ssl_encryption_application was hardcoded. Before 9553eea74f2a > ngx_quic_frame_sendto() was used only for PATH_CHALLENGE/PATH_RESPONSE, which > are only sent at the application level. Since 9553eea74f2a, > ngx_quic_frame_sendto() is also used for sending CONNECTION_CLOSE, which can > be sent at initial level after SSL handshake error or rejection. This > resulted in packet encryption error. Now level is copied from frame, which > fixes the 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 > @@ -1223,7 +1223,7 @@ ngx_quic_frame_sendto(ngx_connection_t * > static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; > > qc = ngx_quic_get_connection(c); > - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); > + ctx = ngx_quic_get_send_ctx(qc, frame->level); > > ngx_quic_init_packet(c, ctx, &pkt, path); > Proposed text: Previously, ssl_encryption_application was hardcoded. Before 9553eea74f2a, ngx_quic_frame_sendto() was used only for PATH_CHALLENGE/PATH_RESPONSE sent at the application level only. Since 9553eea74f2a, ngx_quic_frame_sendto() is also used for CONNECTION_CLOSE, which can be sent at initial level after SSL handshake error or rejection. This resulted in packet encryption error. Now level is copied from frame, which fixes the error. Otherwise, looks fine. -- Sergey Kandaurov From xeioex at nginx.com Fri May 5 05:17:07 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 05 May 2023 05:17:07 +0000 Subject: [njs] WebCrypto: fixed building with OpenSSL 1.1.0. Message-ID: details: https://hg.nginx.org/njs/rev/4c4e5b60c766 branches: changeset: 2104:4c4e5b60c766 user: Dmitry Volyntsev date: Thu May 04 22:15:46 2023 -0700 description: WebCrypto: fixed building with OpenSSL 1.1.0. The issue was introduced in 0681bf662222 (0.7.10). This closes #636 issue on Github. diffstat: external/njs_openssl.h | 4 +--- external/njs_webcrypto_module.c | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diffs (79 lines): diff -r f1432043a6a4 -r 4c4e5b60c766 external/njs_openssl.h --- a/external/njs_openssl.h Tue May 02 20:50:57 2023 -0700 +++ b/external/njs_openssl.h Thu May 04 22:15:46 2023 -0700 @@ -43,8 +43,6 @@ #else #define njs_evp_md_ctx_new() EVP_MD_CTX_create() #define njs_evp_md_ctx_free(_ctx) EVP_MD_CTX_destroy(_ctx) -#define ECDSA_SIG_get0_s(sig) (sig)->s -#define ECDSA_SIG_get0_r(sig) (sig)->r #endif @@ -303,7 +301,7 @@ njs_inline int njs_ec_point_get_affine_coordinates(const EC_GROUP *group, const EC_POINT *p, BIGNUM *x, BIGNUM *y) { -#if (OPENSSL_VERSION_NUMBER >= 0x10100001L) +#if (OPENSSL_VERSION_NUMBER >= 0x10101001L) return EC_POINT_get_affine_coordinates(group, p, x, y, NULL); #else return EC_POINT_get_affine_coordinates_GFp(group, p, x, y, NULL); diff -r f1432043a6a4 -r 4c4e5b60c766 external/njs_webcrypto_module.c --- a/external/njs_webcrypto_module.c Tue May 02 20:50:57 2023 -0700 +++ b/external/njs_webcrypto_module.c Thu May 04 22:15:46 2023 -0700 @@ -1863,7 +1863,7 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc group = EC_KEY_get0_group(ec); group_bits = EC_GROUP_get_degree(group); - group_bytes = (group_bits / CHAR_BIT) + (7 + (group_bits % CHAR_BIT)) / 8; + group_bytes = (group_bits / 8) + (7 + (group_bits % 8)) / 8; x_bn = BN_new(); if (x_bn == NULL) { @@ -2024,7 +2024,7 @@ njs_export_jwk_asymmetric(njs_vm_t *vm, switch (EVP_PKEY_id(key->pkey)) { case EVP_PKEY_RSA: -#if (OPENSSL_VERSION_NUMBER >= 0x10100001L) +#if (OPENSSL_VERSION_NUMBER >= 0x10101001L) case EVP_PKEY_RSA_PSS: #endif ret = njs_export_jwk_rsa(vm, key, retval); @@ -3636,10 +3636,11 @@ static njs_int_t njs_convert_der_to_p1363(njs_vm_t *vm, EVP_PKEY *pkey, const u_char *der, size_t der_len, u_char **pout, size_t *out_len) { - u_char *data; - unsigned n; - njs_int_t ret; - ECDSA_SIG *ec_sig; + u_char *data; + unsigned n; + njs_int_t ret; + ECDSA_SIG *ec_sig; + const BIGNUM *r, *s; ret = NJS_OK; ec_sig = NULL; @@ -3659,11 +3660,18 @@ njs_convert_der_to_p1363(njs_vm_t *vm, E goto fail; } - if (njs_bn_bn2binpad(ECDSA_SIG_get0_r(ec_sig), data, n) <= 0) { +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + ECDSA_SIG_get0(ec_sig, &r, &s); +#else + r = ec_sig->r; + s = ec_sig->s; +#endif + + if (njs_bn_bn2binpad(r, data, n) <= 0) { goto fail; } - if (njs_bn_bn2binpad(ECDSA_SIG_get0_s(ec_sig), &data[n], n) <= 0) { + if (njs_bn_bn2binpad(s, &data[n], n) <= 0) { goto fail; } From xeioex at nginx.com Sat May 6 03:11:02 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 06 May 2023 03:11:02 +0000 Subject: [njs] Refactored $262 object as external. Message-ID: details: https://hg.nginx.org/njs/rev/314a2a9fe9c7 branches: changeset: 2105:314a2a9fe9c7 user: Dmitry Volyntsev date: Fri May 05 20:08:55 2023 -0700 description: Refactored $262 object as external. This allows to decouple $262 object which is only needed for tests from the njs core. diffstat: auto/cc | 4 -- auto/help | 2 - auto/options | 2 - src/njs_builtin.c | 52 ---------------------------- src/njs_vm.h | 3 - src/test/njs_externals_test.c | 78 +++++++++++++++++++++++++++++++++++++++++++ src/test/njs_externals_test.h | 1 + src/test/njs_unit_test.c | 22 ++++++------ 8 files changed, 90 insertions(+), 74 deletions(-) diffs (311 lines): diff -r 4c4e5b60c766 -r 314a2a9fe9c7 auto/cc --- a/auto/cc Thu May 04 22:15:46 2023 -0700 +++ b/auto/cc Fri May 05 20:08:55 2023 -0700 @@ -185,9 +185,5 @@ if [ "$NJS_DEBUG_GENERATOR" = "YES" ]; t njs_define=NJS_DEBUG_GENERATOR . auto/define fi -if [ "$NJS_TEST262" = "YES" ]; then - njs_define=NJS_TEST262 . auto/define -fi - # Stop on error exit status again. set -e diff -r 4c4e5b60c766 -r 314a2a9fe9c7 auto/help --- a/auto/help Thu May 04 22:15:46 2023 -0700 +++ b/auto/help Fri May 05 20:08:55 2023 -0700 @@ -55,6 +55,4 @@ default: "$NJS_DEBUG_MEMORY" default: "$NJS_DEBUG_OPCODE" --debug-generator=YES enables generator debug, \ default: "$NJS_DEBUG_GENERATOR" - --test262=YES enables test262 extentions, \ -default: "$NJS_TEST262" END diff -r 4c4e5b60c766 -r 314a2a9fe9c7 auto/options --- a/auto/options Thu May 04 22:15:46 2023 -0700 +++ b/auto/options Fri May 05 20:08:55 2023 -0700 @@ -13,7 +13,6 @@ NJS_DEBUG_GENERATOR=NO NJS_ADDRESS_SANITIZER=NO NJS_ADDR2LINE=NO -NJS_TEST262=YES NJS_OPENSSL=YES NJS_LIBXML2=YES @@ -47,7 +46,6 @@ do --debug-memory=*) NJS_DEBUG_MEMORY="$value" ;; --debug-opcode=*) NJS_DEBUG_OPCODE="$value" ;; --debug-generator=*) NJS_DEBUG_GENERATOR="$value" ;; - --test262=*) NJS_TEST262="$value" ;; --no-openssl) NJS_OPENSSL=NO ;; --no-libxml2) NJS_LIBXML2=NO ;; diff -r 4c4e5b60c766 -r 314a2a9fe9c7 src/njs_builtin.c --- a/src/njs_builtin.c Thu May 04 22:15:46 2023 -0700 +++ b/src/njs_builtin.c Fri May 05 20:08:55 2023 -0700 @@ -35,9 +35,6 @@ static njs_int_t njs_env_hash_init(njs_v static const njs_object_init_t njs_global_this_init; static const njs_object_init_t njs_njs_object_init; static const njs_object_init_t njs_process_object_init; -#ifdef NJS_TEST262 -static const njs_object_init_t njs_262_object_init; -#endif static const njs_object_init_t *njs_object_init[] = { @@ -46,9 +43,6 @@ static const njs_object_init_t *njs_obj &njs_process_object_init, &njs_math_object_init, &njs_json_object_init, -#ifdef NJS_TEST262 - &njs_262_object_init, -#endif NULL }; @@ -1690,49 +1684,3 @@ static const njs_object_init_t njs_proc njs_process_object_properties, njs_nitems(njs_process_object_properties), }; - - -#if (NJS_TEST262) - -static njs_int_t -njs_262_detach_array_buffer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - njs_value_t *value; - njs_array_buffer_t *buffer; - - value = njs_arg(args, nargs, 1); - if (njs_slow_path(!njs_is_array_buffer(value))) { - njs_type_error(vm, "\"this\" is not an ArrayBuffer"); - return NJS_ERROR; - } - - buffer = njs_array_buffer(value); - buffer->u.data = NULL; - buffer->size = 0; - - njs_set_null(retval); - - return NJS_OK; -} - -static const njs_object_prop_t njs_262_object_properties[] = -{ - { - .type = NJS_PROPERTY, - .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), - .u.value = njs_string("$262"), - .configurable = 1, - }, - - NJS_DECLARE_PROP_LNATIVE("detachArrayBuffer", njs_262_detach_array_buffer, - 2, 0), -}; - - -static const njs_object_init_t njs_262_object_init = { - njs_262_object_properties, - njs_nitems(njs_262_object_properties), -}; - -#endif diff -r 4c4e5b60c766 -r 314a2a9fe9c7 src/njs_vm.h --- a/src/njs_vm.h Thu May 04 22:15:46 2023 -0700 +++ b/src/njs_vm.h Fri May 05 20:08:55 2023 -0700 @@ -95,9 +95,6 @@ enum njs_object_e { NJS_OBJECT_PROCESS, NJS_OBJECT_MATH, NJS_OBJECT_JSON, -#ifdef NJS_TEST262 - NJS_OBJECT_262, -#endif NJS_OBJECT_MAX }; diff -r 4c4e5b60c766 -r 314a2a9fe9c7 src/test/njs_externals_test.c --- a/src/test/njs_externals_test.c Thu May 04 22:15:46 2023 -0700 +++ b/src/test/njs_externals_test.c Fri May 05 20:08:55 2023 -0700 @@ -547,6 +547,53 @@ njs_unit_test_constructor(njs_vm_t *vm, } +static njs_int_t +njs_262_detach_array_buffer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_value_t *value; + njs_array_buffer_t *buffer; + + value = njs_arg(args, nargs, 1); + if (njs_slow_path(!njs_is_array_buffer(value))) { + njs_type_error(vm, "\"this\" is not an ArrayBuffer"); + return NJS_ERROR; + } + + buffer = njs_array_buffer(value); + buffer->u.data = NULL; + buffer->size = 0; + + njs_set_null(retval); + + return NJS_OK; +} + + +static njs_external_t njs_unit_test_262_external[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "$262", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("detachArrayBuffer"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_262_detach_array_buffer, + } + }, + +}; + + static njs_external_t njs_unit_test_r_c[] = { { @@ -931,6 +978,37 @@ njs_externals_init_internal(njs_vm_t *vm njs_int_t +njs_externals_262_init(njs_vm_t *vm) +{ + njs_int_t ret, proto_id; + njs_opaque_value_t value; + + static const njs_str_t dollar_262 = njs_str("$262"); + + proto_id = njs_vm_external_prototype(vm, njs_unit_test_262_external, + njs_nitems(njs_unit_test_262_external)); + if (njs_slow_path(proto_id < 0)) { + njs_printf("njs_vm_external_prototype() failed\n"); + return NJS_ERROR; + } + + ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1); + if (njs_slow_path(ret != NJS_OK)) { + njs_printf("njs_vm_external_create() failed\n"); + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &dollar_262, njs_value_arg(&value), 1); + if (njs_slow_path(ret != NJS_OK)) { + njs_printf("njs_vm_bind() failed\n"); + return NJS_ERROR; + } + + return NJS_OK; +} + + +njs_int_t njs_externals_shared_init(njs_vm_t *vm) { return njs_externals_init_internal(vm, njs_test_requests, 1, 1); diff -r 4c4e5b60c766 -r 314a2a9fe9c7 src/test/njs_externals_test.h --- a/src/test/njs_externals_test.h Thu May 04 22:15:46 2023 -0700 +++ b/src/test/njs_externals_test.h Fri May 05 20:08:55 2023 -0700 @@ -25,6 +25,7 @@ typedef struct { njs_int_t njs_externals_shared_init(njs_vm_t *vm); +njs_int_t njs_externals_262_init(njs_vm_t *vm); njs_int_t njs_externals_init(njs_vm_t *vm); njs_int_t njs_external_env_init(njs_external_env_t *env); njs_int_t njs_external_call(njs_vm_t *vm, const njs_str_t *fname, diff -r 4c4e5b60c766 -r 314a2a9fe9c7 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Thu May 04 22:15:46 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 05 20:08:55 2023 -0700 @@ -6559,12 +6559,10 @@ static njs_unit_test_t njs_test[] = " return a.map(q=>q/2).join('|') === '3|2|1'})"), njs_str("true") }, -#ifdef NJS_TEST262 { njs_str("const arr = new Uint8Array([1,2,3]);" "const sep = {toString(){$262.detachArrayBuffer(arr.buffer); return ','}};" "arr.join(sep)"), njs_str("TypeError: detached buffer") }, -#endif { njs_str("Uint8Array.prototype.reduce.call(1)"), njs_str("TypeError: this is not a typed array") }, @@ -22519,12 +22517,6 @@ static njs_unit_test_t njs_externals_te { njs_str("$r2.uri == 'αβγ' && $r2.uri === 'αβγ'"), njs_str("true") }, -#if (NJS_TEST262) -#define N262 "$262," -#else -#define N262 "" -#endif - #if (NJS_HAVE_OPENSSL) #define NCRYPTO "crypto," #else @@ -22532,7 +22524,7 @@ static njs_unit_test_t njs_externals_te #endif { njs_str("Object.keys(this).sort()"), - njs_str(N262 "$r,$r2,$r3,$shared,ExternalConstructor," NCRYPTO "global,njs,process") }, + njs_str("$262,$r,$r2,$r3,$shared,ExternalConstructor," NCRYPTO "global,njs,process") }, { njs_str("Object.getOwnPropertySymbols($r2)[0] == Symbol.toStringTag"), njs_str("true") }, @@ -23195,14 +23187,12 @@ static njs_unit_test_t njs_backtraces_t " at Math.max (native)\n" " at main (:1)\n") }, -#ifdef NJS_TEST262 { njs_str("var ab = new ArrayBuffer(1);" "$262.detachArrayBuffer(ab);" "ab.byteLength"), njs_str("TypeError: detached buffer\n" " at ArrayBuffer.prototype.byteLength (native)\n" " at main (:1)\n") }, -#endif { njs_str("Object.prototype()"), njs_str("TypeError: (intermediate value)[\"prototype\"] is not a function\n" @@ -23674,6 +23664,11 @@ njs_unit_test(njs_unit_test_t tests[], s goto done; } + ret = njs_externals_262_init(vm); + if (ret != NJS_OK) { + goto done; + } + if (opts->externals) { ret = njs_externals_shared_init(vm); if (ret != NJS_OK) { @@ -23808,6 +23803,11 @@ njs_interactive_test(njs_unit_test_t tes goto done; } + ret = njs_externals_262_init(vm); + if (ret != NJS_OK) { + goto done; + } + if (opts->externals) { ret = njs_externals_shared_init(vm); if (ret != NJS_OK) { From xeioex at nginx.com Sat May 6 03:11:04 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 06 May 2023 03:11:04 +0000 Subject: [njs] Change: non-compliant deprecated String methods were removed. Message-ID: details: https://hg.nginx.org/njs/rev/4df790f42ce7 branches: changeset: 2106:4df790f42ce7 user: Dmitry Volyntsev date: Fri May 05 20:08:56 2023 -0700 description: Change: non-compliant deprecated String methods were removed. The following methods were removed: String.bytesFrom(), String.prototype.fromBytes(), String.prototype.fromUTF8(), String.prototype.toBytes(), String.prototype.toUTF8(), String.prototype.toString(encoding). Because String.bytesFrom() was used to test the existing code which works with byte strings it was replaced with $262.bytesString() which is only available in unit tests. diffstat: src/njs_string.c | 445 +----------------------------------------- src/test/njs_externals_test.c | 173 ++++++++++++++++ src/test/njs_unit_test.c | 369 ++++++++-------------------------- 3 files changed, 265 insertions(+), 722 deletions(-) diffs (truncated from 1311 to 1000 lines): diff -r 314a2a9fe9c7 -r 4df790f42ce7 src/njs_string.c --- a/src/njs_string.c Fri May 05 20:08:55 2023 -0700 +++ b/src/njs_string.c Fri May 05 20:08:56 2023 -0700 @@ -66,13 +66,6 @@ static njs_int_t njs_string_slice_args(n njs_value_t *args, njs_uint_t nargs); static njs_int_t njs_string_from_char_code(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t is_point, njs_value_t *retval); -static njs_int_t njs_string_bytes_from(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_string_bytes_from_array_like(njs_vm_t *vm, - njs_value_t *value, njs_value_t *retval); -static njs_int_t njs_string_bytes_from_string(njs_vm_t *vm, - const njs_value_t *string, const njs_value_t *encoding, - njs_value_t *retval); static njs_int_t njs_string_match_multiple(njs_vm_t *vm, njs_value_t *args, njs_regexp_pattern_t *pattern, njs_value_t *retval); @@ -660,8 +653,6 @@ static const njs_object_prop_t njs_stri NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), - NJS_DECLARE_PROP_NATIVE("bytesFrom", njs_string_bytes_from, 0, 0), - NJS_DECLARE_PROP_NATIVE("fromCharCode", njs_string_from_char_code, 1, 0), NJS_DECLARE_PROP_NATIVE("fromCodePoint", njs_string_from_char_code, 1, 1), @@ -837,58 +828,11 @@ njs_string_prototype_value_of(njs_vm_t * } -/* - * String.prototype.toString([encoding]). - * Returns the string as is if no additional argument is provided, - * otherwise converts a string into an encoded string: hex, base64, - * base64url. - */ - static njs_int_t njs_string_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_int_t ret; - njs_str_t enc, str; - njs_value_t value; - njs_string_prop_t string; - - ret = njs_string_prototype_value_of(vm, args, nargs, unused, retval); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - if (nargs < 2) { - return NJS_OK; - } - - if (njs_slow_path(!njs_is_string(&args[1]))) { - njs_type_error(vm, "encoding must be a string"); - return NJS_ERROR; - } - - njs_value_assign(&value, retval); - - (void) njs_string_prop(&string, &value); - - njs_string_get(&args[1], &enc); - - str.length = string.size; - str.start = string.start; - - if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) { - return njs_string_hex(vm, retval, &str); - - } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) { - return njs_string_base64(vm, retval, &str); - - } else if (enc.length == 9 && memcmp(enc.start, "base64url", 9) == 0) { - return njs_string_base64url(vm, retval, &str); - } - - njs_type_error(vm, "Unknown encoding: \"%V\"", &enc); - - return NJS_ERROR; + return njs_string_prototype_value_of(vm, args, nargs, unused, retval); } @@ -977,226 +921,6 @@ njs_string_object_validate(njs_vm_t *vm, } -/* - * String.fromUTF8(start[, end]). - * The method converts an UTF-8 encoded byte string to an Unicode string. - */ - -static njs_int_t -njs_string_prototype_from_utf8(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) -{ - ssize_t length; - njs_int_t ret; - njs_slice_prop_t slice; - njs_string_prop_t string; - - njs_deprecated(vm, "String.prototype.fromUTF8()"); - - ret = njs_string_object_validate(vm, njs_argument(args, 0)); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - if (string.length != 0) { - /* ASCII or UTF8 string. */ - return njs_string_slice(vm, retval, &string, &slice); - } - - string.start += slice.start; - - length = njs_utf8_length(string.start, slice.length); - - if (length >= 0) { - return njs_string_new(vm, retval, string.start, slice.length, - length); - } - - njs_set_null(retval); - - return NJS_OK; -} - - -/* - * String.toUTF8(start[, end]). - * The method serializes Unicode string to an UTF-8 encoded byte string. - */ - -static njs_int_t -njs_string_prototype_to_utf8(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - njs_int_t ret; - njs_slice_prop_t slice; - njs_string_prop_t string; - - njs_deprecated(vm, "String.prototype.toUTF8()"); - - ret = njs_string_object_validate(vm, njs_argument(args, 0)); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - (void) njs_string_prop(&string, njs_argument(args, 0)); - - string.length = 0; - slice.string_length = string.size; - - ret = njs_string_slice_args(vm, &slice, args, nargs); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - return njs_string_slice(vm, retval, &string, &slice); -} - - -/* - * String.fromBytes(start[, end]). - * The method converts a byte string to an Unicode string. - */ - -static njs_int_t -njs_string_prototype_from_bytes(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) -{ - u_char *p, *s, *start, *end; - size_t size; - njs_int_t ret; - njs_slice_prop_t slice; - njs_string_prop_t string; - - njs_deprecated(vm, "String.prototype.fromBytes()"); - - ret = njs_string_object_validate(vm, njs_argument(args, 0)); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - if (string.length != 0) { - /* ASCII or UTF8 string. */ - return njs_string_slice(vm, retval, &string, &slice); - } - - size = 0; - string.start += slice.start; - end = string.start + slice.length; - - for (p = string.start; p < end; p++) { - size += (*p < 0x80) ? 1 : 2; - } - - start = njs_string_alloc(vm, retval, size, slice.length); - - if (njs_fast_path(start != NULL)) { - - if (size == slice.length) { - memcpy(start, string.start, size); - - } else { - s = start; - end = string.start + slice.length; - - for (p = string.start; p < end; p++) { - s = njs_utf8_encode(s, *p); - } - } - - return NJS_OK; - } - - return NJS_ERROR; -} - - -/* - * String.toBytes(start[, end]). - * The method serializes an Unicode string to a byte string. - * The method returns null if a character larger than 255 is - * encountered in the Unicode string. - */ - -static njs_int_t -njs_string_prototype_to_bytes(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - u_char *p; - size_t length; - uint32_t byte; - njs_int_t ret; - const u_char *s, *end; - njs_slice_prop_t slice; - njs_string_prop_t string; - njs_unicode_decode_t ctx; - - njs_deprecated(vm, "String.prototype.toBytes()"); - - ret = njs_string_object_validate(vm, njs_argument(args, 0)); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - ret = njs_string_slice_prop(vm, &string, &slice, args, nargs); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - if (string.length == 0) { - /* Byte string. */ - return njs_string_slice(vm, retval, &string, &slice); - } - - p = njs_string_alloc(vm, retval, slice.length, 0); - - if (njs_fast_path(p != NULL)) { - - if (string.length != string.size) { - /* UTF-8 string. */ - end = string.start + string.size; - - s = njs_string_utf8_offset(string.start, end, slice.start); - - length = slice.length; - - njs_utf8_decode_init(&ctx); - - while (length != 0 && s < end) { - byte = njs_utf8_decode(&ctx, &s, end); - - if (njs_slow_path(byte > 0xFF)) { - njs_release(vm, retval); - njs_set_null(retval); - - return NJS_OK; - } - - *p++ = (u_char) byte; - length--; - } - - } else { - /* ASCII string. */ - memcpy(p, string.start + slice.start, slice.length); - } - - return NJS_OK; - } - - return NJS_ERROR; -} - - static njs_int_t njs_string_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) @@ -1618,165 +1342,6 @@ done: } -/* - * String.bytesFrom(array-like). - * Converts an array-like object containing octets into a byte string. - * - * String.bytesFrom(string[, encoding]). - * Converts a string using provided encoding: hex, base64, base64url to - * a byte string. - */ - -static njs_int_t -njs_string_bytes_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - njs_value_t *value; - - njs_deprecated(vm, "String.bytesFrom()"); - - value = njs_arg(args, nargs, 1); - - if (njs_is_string(value)) { - return njs_string_bytes_from_string(vm, value, njs_arg(args, nargs, 2), - retval); - - } else if (njs_is_object(value)) { - - if (njs_is_object_string(value)) { - value = njs_object_value(value); - return njs_string_bytes_from_string(vm, value, - njs_arg(args, nargs, 2), - retval); - } - - return njs_string_bytes_from_array_like(vm, value, retval); - } - - njs_type_error(vm, "value must be a string or array-like object"); - - return NJS_ERROR; -} - - -static njs_int_t -njs_string_bytes_from_array_like(njs_vm_t *vm, njs_value_t *value, - njs_value_t *retval) -{ - u_char *p; - int64_t length; - uint32_t u32; - njs_int_t ret; - njs_array_t *array; - njs_value_t *octet, index, prop; - njs_array_buffer_t *buffer; - - array = NULL; - buffer = NULL; - - switch (value->type) { - case NJS_ARRAY: - array = njs_array(value); - length = array->length; - break; - - case NJS_ARRAY_BUFFER: - case NJS_TYPED_ARRAY: - - if (njs_is_typed_array(value)) { - buffer = njs_typed_array(value)->buffer; - - } else { - buffer = njs_array_buffer(value); - } - - length = buffer->size; - break; - - default: - ret = njs_object_length(vm, value, &length); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } - } - - p = njs_string_alloc(vm, retval, length, 0); - if (njs_slow_path(p == NULL)) { - return NJS_ERROR; - } - - if (array != NULL) { - octet = array->start; - - while (length != 0) { - ret = njs_value_to_uint32(vm, octet, &u32); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - *p++ = (u_char) u32; - octet++; - length--; - } - - } else if (buffer != NULL) { - memcpy(p, buffer->u.u8, length); - - } else { - p += length - 1; - - while (length != 0) { - njs_set_number(&index, length - 1); - - ret = njs_value_property(vm, value, &index, &prop); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; - } - - ret = njs_value_to_uint32(vm, &prop, &u32); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - *p-- = (u_char) u32; - length--; - } - } - - return NJS_OK; -} - - -static njs_int_t -njs_string_bytes_from_string(njs_vm_t *vm, const njs_value_t *string, - const njs_value_t *encoding, njs_value_t *retval) -{ - njs_str_t enc, str; - - if (!njs_is_string(encoding)) { - njs_type_error(vm, "\"encoding\" must be a string"); - return NJS_ERROR; - } - - njs_string_get(encoding, &enc); - njs_string_get(string, &str); - - if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) { - return njs_string_decode_hex(vm, retval, &str); - - } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) { - return njs_string_decode_base64(vm, retval, &str); - - } else if (enc.length == 9 && memcmp(enc.start, "base64url", 9) == 0) { - return njs_string_decode_base64url(vm, retval, &str); - } - - njs_type_error(vm, "Unknown encoding: \"%V\"", &enc); - - return NJS_ERROR; -} - - size_t njs_decode_hex_length(const njs_str_t *src, size_t *out_size) { @@ -4085,14 +3650,6 @@ static const njs_object_prop_t njs_stri NJS_DECLARE_PROP_NATIVE("concat", njs_string_prototype_concat, 1, 0), - NJS_DECLARE_PROP_NATIVE("fromUTF8", njs_string_prototype_from_utf8, 0, 0), - - NJS_DECLARE_PROP_NATIVE("toUTF8", njs_string_prototype_to_utf8, 0, 0), - - NJS_DECLARE_PROP_NATIVE("fromBytes", njs_string_prototype_from_bytes, 0, 0), - - NJS_DECLARE_PROP_NATIVE("toBytes", njs_string_prototype_to_bytes, 0, 0), - NJS_DECLARE_PROP_NATIVE("slice", njs_string_prototype_slice, 2, 0), NJS_DECLARE_PROP_NATIVE("substring", njs_string_prototype_substring, 2, 0), diff -r 314a2a9fe9c7 -r 4df790f42ce7 src/test/njs_externals_test.c --- a/src/test/njs_externals_test.c Fri May 05 20:08:55 2023 -0700 +++ b/src/test/njs_externals_test.c Fri May 05 20:08:56 2023 -0700 @@ -570,6 +570,168 @@ njs_262_detach_array_buffer(njs_vm_t *vm } +static njs_int_t +njs_262_bytes_from_array_like(njs_vm_t *vm, njs_value_t *value, + njs_value_t *retval) +{ + u_char *p; + int64_t length; + uint32_t u32; + njs_int_t ret; + njs_array_t *array; + njs_value_t *octet, index, prop; + njs_array_buffer_t *buffer; + + array = NULL; + buffer = NULL; + + switch (value->type) { + case NJS_ARRAY: + array = njs_array(value); + length = array->length; + break; + + case NJS_ARRAY_BUFFER: + case NJS_TYPED_ARRAY: + + if (njs_is_typed_array(value)) { + buffer = njs_typed_array(value)->buffer; + + } else { + buffer = njs_array_buffer(value); + } + + length = buffer->size; + break; + + default: + ret = njs_object_length(vm, value, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + p = njs_string_alloc(vm, retval, length, 0); + if (njs_slow_path(p == NULL)) { + return NJS_ERROR; + } + + if (array != NULL) { + octet = array->start; + + while (length != 0) { + ret = njs_value_to_uint32(vm, octet, &u32); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + *p++ = (u_char) u32; + octet++; + length--; + } + + } else if (buffer != NULL) { + memcpy(p, buffer->u.u8, length); + + } else { + p += length - 1; + + while (length != 0) { + njs_set_number(&index, length - 1); + + ret = njs_value_property(vm, value, &index, &prop); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + ret = njs_value_to_uint32(vm, &prop, &u32); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + *p-- = (u_char) u32; + length--; + } + } + + return NJS_OK; +} + + +static njs_int_t +njs_262_bytes_from_string(njs_vm_t *vm, const njs_value_t *string, + const njs_value_t *encoding, njs_value_t *retval) +{ + njs_str_t enc, str; + + if (!njs_is_string(encoding)) { + njs_type_error(vm, "\"encoding\" must be a string"); + return NJS_ERROR; + } + + njs_string_get(encoding, &enc); + njs_string_get(string, &str); + + if (enc.length == 3 && memcmp(enc.start, "hex", 3) == 0) { + return njs_string_decode_hex(vm, retval, &str); + + } else if (enc.length == 6 && memcmp(enc.start, "base64", 6) == 0) { + return njs_string_decode_base64(vm, retval, &str); + + } else if (enc.length == 9 && memcmp(enc.start, "base64url", 9) == 0) { + return njs_string_decode_base64url(vm, retval, &str); + } + + njs_type_error(vm, "Unknown encoding: \"%V\"", &enc); + + return NJS_ERROR; +} + + +/* + * $262.byteString(array-like). + * Converts an array-like object containing octets into a byte string. + * + * $262.byteString(string[, encoding]). + * Converts a string using provided encoding: hex, base64, base64url to + * a byte string. + * + * Note: the function produces a byte string, and byte strings are deprecated. + * The function is provided for testing of existing code which works with + * byte strings. When code working with byte strings is removed + * the function will be removed as well. + */ + +static njs_int_t +njs_262_byte_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_value_t *value; + + value = njs_arg(args, nargs, 1); + + if (njs_is_string(value)) { + return njs_262_bytes_from_string(vm, value, njs_arg(args, nargs, 2), + retval); + + } else if (njs_is_object(value)) { + + if (njs_is_object_string(value)) { + value = njs_object_value(value); + return njs_262_bytes_from_string(vm, value, + njs_arg(args, nargs, 2), + retval); + } + + return njs_262_bytes_from_array_like(vm, value, retval); + } + + njs_type_error(vm, "value must be a string or array-like object"); + + return NJS_ERROR; +} + + static njs_external_t njs_unit_test_262_external[] = { { @@ -591,6 +753,17 @@ static njs_external_t njs_unit_test_262 } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("byteString"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_262_byte_string, + } + }, + }; diff -r 314a2a9fe9c7 -r 4df790f42ce7 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 05 20:08:55 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 05 20:08:56 2023 -0700 @@ -1047,56 +1047,9 @@ static njs_unit_test_t njs_test[] = { njs_str("undefined - undefined"), njs_str("NaN") }, - /* String.toString() method. */ - { njs_str("'A'.toString()"), njs_str("A") }, - { njs_str("'A'.toBytes().toString('latin1')"), - njs_str("TypeError: Unknown encoding: \"latin1\"") }, - - { njs_str("'ABCD'.toBytes().toString('hex')"), - njs_str("41424344") }, - - { njs_str("'\\x00\\xAA\\xBB\\xFF'.toBytes().toString('hex')"), - njs_str("00aabbff") }, - - { njs_str("'\\x00\\xAA\\xBB\\xFF'.toBytes().toString('base64')"), - njs_str("AKq7/w==") }, - - { njs_str("'ABCD'.toBytes().toString('base64')"), - njs_str("QUJDRA==") }, - - { njs_str("'ABC'.toBytes().toString('base64')"), - njs_str("QUJD") }, - - { njs_str("'AB'.toBytes().toString('base64')"), - njs_str("QUI=") }, - - { njs_str("'A'.toBytes().toString('base64')"), - njs_str("QQ==") }, - - { njs_str("''.toBytes().toString('base64')"), - njs_str("") }, - - { njs_str("'\\x00\\xAA\\xBB\\xFF'.toBytes().toString('base64url')"), - njs_str("AKq7_w") }, - - { njs_str("'ABCD'.toBytes().toString('base64url')"), - njs_str("QUJDRA") }, - - { njs_str("'ABC'.toBytes().toString('base64url')"), - njs_str("QUJD") }, - - { njs_str("'AB'.toBytes().toString('base64url')"), - njs_str("QUI") }, - - { njs_str("'A'.toBytes().toString('base64url')"), - njs_str("QQ") }, - - { njs_str("''.toBytes().toString('base64url')"), - njs_str("") }, - /* Assignment. */ { njs_str("var a, b = (a = [2]) * (3 * 4); a +' '+ b"), @@ -4348,7 +4301,7 @@ static njs_unit_test_t njs_test[] = { njs_str("[" " 'α'.repeat(33)," - " String.bytesFrom(Array(16).fill(0x9d))," + " $262.byteString(Array(16).fill(0x9d))," "]" ".map(v=>{var out = ['β', 'γ'].join(v); return out.length})"), njs_str("35,20") }, @@ -4369,7 +4322,7 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = ['β','γ']; a.join('').length"), njs_str("2") }, - { njs_str("var a = ['β', String.bytesFrom([0x9d]),'γ']; a.join('').length"), + { njs_str("var a = ['β', $262.byteString([0x9d]),'γ']; a.join('').length"), njs_str("5") }, { njs_str("var a = []; a[5] = 5; a.join()"), @@ -4708,7 +4661,7 @@ static njs_unit_test_t njs_test[] = { njs_str("Array.prototype.slice.call('αβZγ')"), njs_str("α,β,Z,γ") }, - { njs_str("Array.prototype.slice.call(String.bytesFrom(Array(16).fill(0x9d)))[0].charCodeAt(0)"), + { njs_str("Array.prototype.slice.call($262.byteString(Array(16).fill(0x9d)))[0].charCodeAt(0)"), njs_str("157") }, { njs_str("Array.prototype.slice.call('αβZγ', 1)"), @@ -7809,37 +7762,34 @@ static njs_unit_test_t njs_test[] = { njs_str("(new String('abc')).hasOwnProperty('length')"), njs_str("true") }, - { njs_str("'abc'.toUTF8().length"), - njs_str("3") }, - { njs_str("'абв'.length"), njs_str("3") }, - { njs_str("'абв'.toUTF8().length"), + { njs_str("(new TextEncoder()).encode('абв').length"), njs_str("6") }, { njs_str("'αβγ'.length"), njs_str("3") }, - { njs_str("'αβγ'.toUTF8().length"), + { njs_str("(new TextEncoder()).encode('αβγ').length"), njs_str("6") }, { njs_str("'絵文字'.length"), njs_str("3") }, - { njs_str("'絵文字'.toUTF8().length"), + { njs_str("(new TextEncoder()).encode('絵文字').length"), njs_str("9") }, { njs_str("'えもじ'.length"), njs_str("3") }, - { njs_str("'えもじ'.toUTF8().length"), + { njs_str("(new TextEncoder()).encode('えもじ').length"), njs_str("9") }, { njs_str("'囲碁織'.length"), njs_str("3") }, - { njs_str("'囲碁織'.toUTF8().length"), + { njs_str("(new TextEncoder()).encode('囲碁織').length"), njs_str("9") }, { njs_str("var a = 'abc'; a.length"), @@ -7995,76 +7945,12 @@ static njs_unit_test_t njs_test[] = "var a = 'abc'; a.concat('абв', s)"), njs_str("abcабв123") }, - { njs_str("'\\u00CE\\u00B1'.toBytes() == 'α'"), - njs_str("true") }, - - { njs_str("'\\u00CE\\u00B1'.toBytes() === 'α'"), - njs_str("true") }, - - { njs_str("var b = '\\u00C2\\u00B6'.toBytes(), u = b.fromUTF8();" - "b.length +' '+ b +' '+ u.length +' '+ u"), - njs_str("2 ¶ 1 ¶") }, - - { njs_str("'α'.toBytes()"), - njs_str("null") }, - - { njs_str("'α'.toUTF8()[0]"), - njs_str("\xCE") }, - { njs_str("var r = /^\\x80$/; r.source + r.source.length"), njs_str("^\\x80$6") }, { njs_str("var r = /^\\\\x80$/; r.source + r.source.length"), njs_str("^\\\\x80$7") }, - { njs_str("/^\\x80$/.test('\\x80'.toBytes())"), - njs_str("true") }, - - { njs_str("/^\\xC2\\x80$/.test('\\x80'.toUTF8())"), - njs_str("true") }, - - { njs_str("'α'.toUTF8().toBytes()"), - njs_str("α") }, - - { njs_str("var a = 'a'.toBytes() + 'α'; a + a.length"), - njs_str("aα3") }, - - { njs_str("var a = 'µ§±®'.toBytes(); a"), - njs_str("\xB5\xA7\xB1\xAE") }, - - { njs_str("var a = 'µ§±®'.toBytes(2); a"), - njs_str("\xB1\xAE") }, - - { njs_str("var a = 'µ§±®'.toBytes(1,3); a"), - njs_str("\xA7\xB1") }, - - { njs_str("var a = '\\xB5\\xA7\\xB1\\xAE'.toBytes(); a.fromBytes()"), - njs_str("µ§±®") }, - - { njs_str("var a = '\\xB5\\xA7\\xB1\\xAE'.toBytes(); a.fromBytes(2)"), - njs_str("±®") }, - - { njs_str("var a = '\\xB5\\xA7\\xB1\\xAE'.toBytes(); a.fromBytes(1, 3)"), - njs_str("§±") }, - - { njs_str("'A'.repeat(8).toBytes() === 'A'.repeat(8)"), - njs_str("true") }, - - { njs_str("'A'.repeat(16).toBytes() === 'A'.repeat(16)"), - njs_str("true") }, - - { njs_str("'A'.repeat(38).toBytes(-5) === 'AAAAA'"), - njs_str("true") }, - - { njs_str("('α' + 'A'.repeat(32)).toBytes()"), - njs_str("null") }, - - { njs_str("('α' + 'A'.repeat(32)).toBytes(1) === 'A'.repeat(32)"), - njs_str("true") }, - - { njs_str("('α' + 'A'.repeat(40)).toBytes(-3,-1)"), - njs_str("AA") }, - { njs_str("var s = 'x'.repeat(2**10).repeat(2**14);" "var a = Array(200).fill(s);" "String.prototype.concat.apply(s, a.slice(1))"), @@ -8160,9 +8046,6 @@ static njs_unit_test_t njs_test[] = { njs_str("String.prototype.slice(1, 5)"), njs_str("") }, - { njs_str("String.prototype.toBytes(1, 5)"), - njs_str("") }, - { njs_str("'abc'.charAt(1 + 1)"), njs_str("c") }, @@ -8461,9 +8344,6 @@ static njs_unit_test_t njs_test[] = { njs_str("var r = new String('undefined').indexOf(x); var x; r"), njs_str("0") }, - { njs_str("'a a'.toUTF8().indexOf('a', 1)"), - njs_str("2") }, - { njs_str("'aaa'.lastIndexOf()"), njs_str("-1") }, @@ -8658,9 +8538,6 @@ static njs_unit_test_t njs_test[] = { njs_str("'\x00абвгдеёжз'.toUpperCase().length"), njs_str("10") }, - { njs_str("['ȿ', 'Ȿ', 'ȿ'.toUpperCase(), 'Ȿ'.toLowerCase()].map((v)=>v.toUTF8().length)"), - njs_str("2,3,3,2") }, - #if (!NJS_HAVE_MEMORY_SANITIZER) /* very long tests under MSAN */ { njs_str("var a = [], code;" "for (code = 0; code <= 1114111; code++) {" @@ -8863,16 +8740,6 @@ static njs_unit_test_t njs_test[] = { njs_str("var r = 'αβγ'.replaceAll('', 'X'); [r, r.length]"), njs_str("XαXβXγX,7") }, - { njs_str("var s = 'αz'.toUTF8();" - "var r = s.replace('z', 'β');" - "r.length"), - njs_str("4") }, - - { njs_str("var s = 'αzz'.toUTF8();" - "var r = s.replaceAll('z', 'β');" - "r.length"), - njs_str("3") }, - { njs_str("'abc'.replace('b', (m, o, s) => `|${s}|${o}|${m}|`)"), njs_str("a|abc|1|b|c") }, @@ -9648,10 +9515,6 @@ static njs_unit_test_t njs_test[] = { njs_str("('β' + 'α'.repeat(33) +'β').match(/α+/g)[0][32]"), njs_str("α") }, - { njs_str("var a = '\\u00CE\\u00B1'.toBytes().match(/α/g)[0] + 'α';" - "a +' '+ a.length"), - njs_str("αα 4") }, - { njs_str("'abc'.split()"), njs_str("abc") }, @@ -9915,92 +9778,8 @@ static njs_unit_test_t njs_test[] = njs_str("TypeError: Cannot convert a Symbol value to a string") }, { njs_str("[undefined, null, Symbol()]" - ".every(v=> { try {String.bytesFrom(v);} catch(e) {return e.name == 'TypeError'} })"), - njs_str("true") }, - - { njs_str("String.bytesFrom({}).length"), - njs_str("0") }, - - { njs_str("String.bytesFrom({length:5, 0:'A'.charCodeAt(0), 2:'X', 3:NaN,4:0xfd}).toString('hex')"), - njs_str("41000000fd") }, - - { njs_str("String.bytesFrom([1, 2, 0.23, '5', 'A']).toString('hex')"), - njs_str("0102000500") }, - - { njs_str("String.bytesFrom([NaN, Infinity]).toString('hex')"), - njs_str("0000") }, - - { njs_str("String.bytesFrom(new Uint8Array([0xff,0xde,0xba])).toString('hex')"), - njs_str("ffdeba") }, - - { njs_str("String.bytesFrom((new Uint8Array([0xff,0xde,0xba])).buffer).toString('hex')"), - njs_str("ffdeba") }, - - { njs_str("String.bytesFrom('', 'hex')"), - njs_str("") }, - - { njs_str("String.bytesFrom('00aabbcc', 'hex').toString('hex')"), - njs_str("00aabbcc") }, - - { njs_str("String.bytesFrom(new String('00aabbcc'), 'hex').toString('hex')"), - njs_str("00aabbcc") }, - - { njs_str("String.bytesFrom('deadBEEF##', 'hex').toString('hex')"), - njs_str("deadbeef") }, - - { njs_str("String.bytesFrom('aa0', 'hex').toString('hex')"), - njs_str("aa") }, - - { njs_str("String.bytesFrom('', 'base64')"), - njs_str("") }, - - { njs_str("String.bytesFrom('#', 'base64')"), - njs_str("") }, - - { njs_str("String.bytesFrom('QQ==', 'base64')"), - njs_str("A") }, - - { njs_str("String.bytesFrom('QQ=', 'base64')"), - njs_str("A") }, From xeioex at nginx.com Sat May 6 03:11:06 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 06 May 2023 03:11:06 +0000 Subject: [njs] Tests: unit tests are rewritten using public API. Message-ID: details: https://hg.nginx.org/njs/rev/40af42ad829b branches: changeset: 2107:40af42ad829b user: Dmitry Volyntsev date: Fri May 05 20:08:57 2023 -0700 description: Tests: unit tests are rewritten using public API. njs_to_int32_test is replaced with ordinary script tests. diffstat: src/test/njs_externals_test.c | 9 +- src/test/njs_externals_test.h | 6 +- src/test/njs_unit_test.c | 348 +++++++++++++++++++---------------------- 3 files changed, 168 insertions(+), 195 deletions(-) diffs (644 lines): diff -r 4df790f42ce7 -r 40af42ad829b src/test/njs_externals_test.c --- a/src/test/njs_externals_test.c Fri May 05 20:08:56 2023 -0700 +++ b/src/test/njs_externals_test.c Fri May 05 20:08:57 2023 -0700 @@ -405,13 +405,13 @@ njs_unit_test_r_subrequest(njs_vm_t *vm, return NJS_ERROR; } - ev = njs_mp_alloc(vm->mem_pool, sizeof(njs_external_ev_t)); + ev = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_external_ev_t)); if (ev == NULL) { njs_memory_error(vm); return NJS_ERROR; } - ret = njs_vm_promise_create(vm, &value, &ev->callbacks[0]); + ret = njs_vm_promise_create(vm, &value, njs_value_arg(&ev->callbacks[0])); if (ret != NJS_OK) { return NJS_ERROR; } @@ -1199,7 +1199,7 @@ njs_int_t njs_external_env_init(njs_external_env_t *env) { if (env != NULL) { - njs_value_invalid_set(&env->retval); + njs_value_invalid_set(njs_value_arg(&env->retval)); njs_queue_init(&env->events); } @@ -1229,7 +1229,8 @@ njs_external_process_events(njs_vm_t *vm ev->link.prev = NULL; ev->link.next = NULL; - njs_vm_post_event(vm, ev->vm_event, &ev->args[0], ev->nargs); + njs_vm_post_event(vm, ev->vm_event, njs_value_arg(&ev->args[0]), + ev->nargs); } return NJS_OK; diff -r 4df790f42ce7 -r 40af42ad829b src/test/njs_externals_test.h --- a/src/test/njs_externals_test.h Fri May 05 20:08:56 2023 -0700 +++ b/src/test/njs_externals_test.h Fri May 05 20:08:57 2023 -0700 @@ -9,7 +9,7 @@ typedef struct { - njs_value_t retval; + njs_opaque_value_t retval; njs_queue_t events; /* of njs_external_ev_t */ } njs_external_env_t; @@ -18,8 +18,8 @@ typedef struct { njs_vm_event_t vm_event; void *data; njs_uint_t nargs; - njs_value_t args[3]; - njs_value_t callbacks[2]; + njs_opaque_value_t args[3]; + njs_opaque_value_t callbacks[2]; njs_queue_link_t link; } njs_external_ev_t; diff -r 4df790f42ce7 -r 40af42ad829b src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 05 20:08:56 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 05 20:08:57 2023 -0700 @@ -4,7 +4,14 @@ */ -#include +#include +#include +#include +#include +#include +#include + +#include #ifndef NJS_HAVE_PCRE2 #include @@ -1075,6 +1082,38 @@ static njs_unit_test_t njs_test[] = { njs_str("var x; x in (x = 1, [1, 2, 3])"), njs_str("false") }, + /* ToInt32(). */ + + { njs_str("-1.0 | 0"), + njs_str("-1") }, + + { njs_str("0.0 | 0"), + njs_str("0") }, + + { njs_str("0.001 | 0"), + njs_str("0") }, + + { njs_str("1.0 | 0"), + njs_str("1") }, + + { njs_str("2147483647.0 | 0"), + njs_str("2147483647") }, + + { njs_str("2147483648.0 | 0"), + njs_str("-2147483648") }, + + { njs_str("2147483649.0 | 0"), + njs_str("-2147483647") }, + + { njs_str("-1844674406941458432.0 | 0"), + njs_str("-2147483648") }, + + { njs_str("4.835703278458518e+24 /* 2**(53+29) + 2**30 */ | 0"), + njs_str("1073741824") }, + + { njs_str("9.671406556917036e+24 /* 2**(53+30) + 2**31 */ | 0"), + njs_str("-2147483648") }, + /* Exponentiation. */ { njs_str("2 ** 3 ** 2"), @@ -23179,7 +23218,7 @@ typedef struct { typedef struct { njs_vm_t *vm; - njs_value_t retval; + njs_opaque_value_t retval; njs_external_env_t *env; njs_external_env_t env0; @@ -23257,12 +23296,13 @@ njs_external_retval(njs_external_state_t { if (state->env != NULL && ret == NJS_OK - && njs_value_is_valid(&state->env->retval)) + && njs_value_is_valid(njs_value_arg(&state->env->retval))) { - return njs_vm_value_string(state->vm, s, &state->env->retval); - } - - return njs_vm_value_string(state->vm, s, &state->retval); + return njs_vm_value_string(state->vm, s, + njs_value_arg(&state->env->retval)); + } + + return njs_vm_value_string(state->vm, s, njs_value_arg(&state->retval)); } @@ -23273,13 +23313,13 @@ njs_runtime_init(njs_vm_t *vm, njs_opts_ njs_uint_t i; njs_runtime_t *rt; - rt = njs_mp_alloc(vm->mem_pool, sizeof(njs_runtime_t)); + rt = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_runtime_t)); if (rt == NULL) { return NULL; } rt->size = opts->repeat; - rt->states = njs_mp_alloc(vm->mem_pool, + rt->states = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_external_state_t) * rt->size); if (rt->states == NULL) { return NULL; @@ -23340,10 +23380,10 @@ static njs_int_t njs_process_test(njs_external_state_t *state, njs_opts_t *opts, njs_unit_test_t *expected) { - njs_int_t ret; - njs_str_t s; - njs_bool_t success; - njs_value_t request; + njs_int_t ret; + njs_str_t s; + njs_bool_t success; + njs_opaque_value_t request; static const njs_str_t handler_str = njs_str("main.handler"); static const njs_str_t request_str = njs_str("$r"); @@ -23354,7 +23394,7 @@ njs_process_test(njs_external_state_t *s case sw_start: state->state = sw_handler; - ret = njs_vm_start(state->vm, &state->retval); + ret = njs_vm_start(state->vm, njs_value_arg(&state->retval)); if (ret != NJS_OK) { goto done; } @@ -23368,13 +23408,15 @@ njs_process_test(njs_external_state_t *s state->state = sw_loop; if (opts->handler) { - ret = njs_vm_value(state->vm, &request_str, &request); + ret = njs_vm_value(state->vm, &request_str, + njs_value_arg(&request)); if (ret != NJS_OK) { njs_stderror("njs_vm_value(\"%V\") failed\n", &request_str); return NJS_ERROR; } - ret = njs_external_call(state->vm, &handler_str, &request, 1); + ret = njs_external_call(state->vm, &handler_str, + njs_value_arg(&request), 1); if (ret == NJS_ERROR) { goto done; } @@ -23582,15 +23624,15 @@ static njs_int_t njs_interactive_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_opts_t *opts, njs_stat_t *stat) { - u_char *start, *last, *end; - njs_vm_t *vm; - njs_int_t ret; - njs_str_t s; - njs_uint_t i; - njs_stat_t prev; - njs_bool_t success; - njs_value_t retval; - njs_vm_opt_t options; + u_char *start, *last, *end; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t s; + njs_uint_t i; + njs_stat_t prev; + njs_bool_t success; + njs_vm_opt_t options; + njs_opaque_value_t retval; vm = NULL; @@ -23651,11 +23693,11 @@ njs_interactive_test(njs_unit_test_t tes njs_disassembler(vm); } - ret = njs_vm_start(vm, &retval); + ret = njs_vm_start(vm, njs_value_arg(&retval)); } } - if (njs_vm_value_dump(vm, &s, &retval, 0, 1) != NJS_OK) { + if (njs_vm_value_dump(vm, &s, njs_value_arg(&retval), 0, 1) != NJS_OK) { njs_printf("njs_vm_value_dump() failed\n"); goto done; } @@ -23784,14 +23826,14 @@ static njs_int_t njs_vm_json_test(njs_unit_test_t unused[], size_t num, njs_str_t *name, njs_opts_t *opts, njs_stat_t *stat) { - njs_vm_t *vm; - njs_int_t ret; - njs_str_t s, *script; - njs_uint_t i; - njs_bool_t success; - njs_stat_t prev; - njs_value_t args[3], retval; - njs_vm_opt_t options; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t s, *script; + njs_uint_t i; + njs_bool_t success; + njs_stat_t prev; + njs_vm_opt_t options; + njs_opaque_value_t args[3], retval; static const njs_str_t fname = njs_str("replacer"); static const njs_str_t iname = njs_str("indent"); @@ -23840,29 +23882,31 @@ njs_vm_json_test(njs_unit_test_t unused[ goto done; } - ret = njs_vm_start(vm, &args[0]); + ret = njs_vm_start(vm, njs_value_arg(&args[0])); if (ret != NJS_OK) { njs_printf("njs_vm_start() failed\n"); goto done; } - ret = njs_vm_json_parse(vm, args, 1, &retval); + ret = njs_vm_json_parse(vm, njs_value_arg(args), 1, + njs_value_arg(&retval)); if (ret != NJS_OK) { njs_printf("njs_vm_json_parse() failed\n"); goto done; } njs_value_assign(&args[0], &retval); - njs_vm_value(vm, &fname, &args[1]); - njs_vm_value(vm, &iname, &args[2]); - - ret = njs_vm_json_stringify(vm, args, 3, &retval); + njs_vm_value(vm, &fname, njs_value_arg(&args[1])); + njs_vm_value(vm, &iname, njs_value_arg(&args[2])); + + ret = njs_vm_json_stringify(vm, njs_value_arg(args), 3, + njs_value_arg(&retval)); if (ret != NJS_OK) { njs_printf("njs_vm_json_stringify() failed\n"); goto done; } - if (njs_vm_value_string(vm, &s, &retval) != NJS_OK) { + if (njs_vm_value_string(vm, &s, njs_value_arg(&retval)) != NJS_OK) { njs_printf("njs_vm_value_string() failed\n"); goto done; } @@ -23912,14 +23956,14 @@ static njs_int_t njs_vm_value_test(njs_unit_test_t unused[], size_t num, njs_str_t *name, njs_opts_t *opts, njs_stat_t *stat) { - njs_vm_t *vm; - njs_int_t ret; - njs_str_t s, *script, path; - njs_uint_t i; - njs_bool_t success; - njs_stat_t prev; - njs_value_t retval; - njs_vm_opt_t options; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t s, *script, path; + njs_uint_t i; + njs_bool_t success; + njs_stat_t prev; + njs_vm_opt_t options; + njs_opaque_value_t retval; static struct { njs_str_t script; @@ -24001,7 +24045,7 @@ njs_vm_value_test(njs_unit_test_t unused goto done; } - ret = njs_vm_start(vm, &retval); + ret = njs_vm_start(vm, njs_value_arg(&retval)); if (ret != NJS_OK) { njs_printf("njs_vm_run() failed\n"); goto done; @@ -24009,7 +24053,7 @@ njs_vm_value_test(njs_unit_test_t unused path = tests[i].path; - path.start = njs_mp_alloc(vm->mem_pool, path.length); + path.start = njs_mp_alloc(njs_vm_memory_pool(vm), path.length); if (path.start == NULL) { njs_printf("njs_mp_alloc() failed\n"); goto done; @@ -24017,10 +24061,12 @@ njs_vm_value_test(njs_unit_test_t unused memcpy(path.start, tests[i].path.start, path.length); - ret = njs_vm_value(vm, &path, &retval); + ret = njs_vm_value(vm, &path, njs_value_arg(&retval)); if (ret == NJS_OK) { - if (njs_vm_value_string(vm, &s, &retval) != NJS_OK) { + if (njs_vm_value_string(vm, &s, njs_value_arg(&retval)) + != NJS_OK) + { njs_printf("njs_vm_value_string() failed\n"); goto done; } @@ -24076,32 +24122,37 @@ done: static njs_int_t njs_vm_object_alloc_test(njs_vm_t *vm, njs_opts_t *opts, njs_stat_t *stat) { - njs_int_t ret; - njs_value_t args[2], obj; - - static const njs_value_t num_key = njs_string("num"); - static const njs_value_t bool_key = njs_string("bool"); - - njs_value_number_set(njs_argument(&args, 0), 1); - njs_value_boolean_set(njs_argument(&args, 1), 0); - - ret = njs_vm_object_alloc(vm, &obj, NULL); + njs_int_t ret; + njs_opaque_value_t args[2], obj, num_key, bool_key; + + njs_value_number_set(njs_value_arg(&args[0]), 1); + njs_value_boolean_set(njs_value_arg(&args[0]), 0); + + (void) njs_vm_value_string_set(vm, njs_value_arg(&num_key), + (u_char *) "num", 3); + (void) njs_vm_value_string_set(vm, njs_value_arg(&bool_key), + (u_char *) "bool", 4); + + ret = njs_vm_object_alloc(vm, njs_value_arg(&obj), NULL); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_vm_object_alloc(vm, &obj, &num_key, NULL); + ret = njs_vm_object_alloc(vm, njs_value_arg(&obj), njs_value_arg(&num_key), + NULL); if (ret == NJS_OK) { return NJS_ERROR; } - ret = njs_vm_object_alloc(vm, &obj, &num_key, &args[0], NULL); + ret = njs_vm_object_alloc(vm, njs_value_arg(&obj), njs_value_arg(&num_key), + njs_value_arg(&args[0]), NULL); if (ret != NJS_OK) { return NJS_ERROR; } - ret = njs_vm_object_alloc(vm, &obj, &num_key, &args[0], &bool_key, - &args[1], NULL); + ret = njs_vm_object_alloc(vm, njs_value_arg(&obj), njs_value_arg(&num_key), + njs_value_arg(&args[0]), njs_value_arg(&bool_key), + njs_value_arg(&args[1]), NULL); if (ret != NJS_OK) { stat->failed++; return NJS_OK; @@ -24216,7 +24267,7 @@ njs_chb_test(njs_vm_t *vm, njs_opts_t *o static const njs_str_t expected = njs_str("arg: \"XYZ\" -5"); - njs_chb_init(&chain, vm->mem_pool); + njs_chb_init(&chain, njs_vm_memory_pool(vm)); p = njs_chb_reserve(&chain, 513); if (p == NULL) { @@ -24256,7 +24307,7 @@ njs_chb_test(njs_vm_t *vm, njs_opts_t *o } } - njs_mp_free(vm->mem_pool, string.start); + njs_mp_free(njs_vm_memory_pool(vm), string.start); for (i = 0; i < 222; i++) {; njs_chb_drain(&chain, 3); @@ -24335,7 +24386,7 @@ njs_chb_test(njs_vm_t *vm, njs_opts_t *o } njs_chb_destroy(&chain); - njs_mp_free(vm->mem_pool, string.start); + njs_mp_free(njs_vm_memory_pool(vm), string.start); done: @@ -24481,47 +24532,48 @@ failed: static njs_int_t njs_string_to_index_test(njs_vm_t *vm, njs_opts_t *opts, njs_stat_t *stat) { - njs_str_t s, string; - njs_int_t ret; - njs_bool_t success, is_integer_index; - njs_uint_t i; - njs_value_t value; + double num; + njs_str_t s; + njs_int_t ret; + njs_bool_t success; + njs_uint_t i; + njs_opaque_value_t value, input; static const struct { - njs_value_t value; + njs_str_t value; njs_str_t expected; - njs_bool_t is_integer_index; } tests[] = { - { njs_string(" 1"), njs_str("NaN"), 0 }, - { njs_string(""), njs_str("NaN"), 0 }, - { njs_string("+0"), njs_str("NaN"), 0 }, - { njs_string("-"), njs_str("NaN"), 0 }, - - { njs_string("-0"), njs_str("-0"), 0 }, - { njs_value(NJS_NUMBER, 0, -0.0), njs_str("-0"), 1 }, - - { njs_string("-1"), njs_str("-1"), 0 }, - { njs_string("0"), njs_str("0"), 1 }, - { njs_string("0."), njs_str("NaN"), 0 }, - { njs_string("0.0"), njs_str("NaN"), 0 }, - { njs_string("0x1"), njs_str("NaN"), 0 }, - { njs_string("1 "), njs_str("NaN"), 0 }, - { njs_string("1"), njs_str("1"), 1 }, - { njs_string("1."), njs_str("NaN"), 0 }, - { njs_string("1.1"), njs_str("1.1"), 0 }, - { njs_string("100"), njs_str("100"), 1 }, - { njs_string("1a"), njs_str("NaN"), 0 }, - { njs_string("1e+19"), njs_str("NaN"), 0 }, - { njs_string("1e+22"), njs_str("1e+22"), 0 }, - { njs_string("1e22"), njs_str("NaN"), 0 }, - { njs_string("4294967296"), njs_str("4294967296"), 0 }, + { njs_str(" 1"), njs_str("NaN") }, + { njs_str(""), njs_str("NaN") }, + { njs_str("+0"), njs_str("NaN") }, + { njs_str("-"), njs_str("NaN") }, + { njs_str("-0"), njs_str("-0") }, + { njs_str("-1"), njs_str("-1") }, + { njs_str("0"), njs_str("0") }, + { njs_str("0."), njs_str("NaN") }, + { njs_str("0.0"), njs_str("NaN") }, + { njs_str("0x1"), njs_str("NaN") }, + { njs_str("1 "), njs_str("NaN") }, + { njs_str("1"), njs_str("1") }, + { njs_str("1."), njs_str("NaN") }, + { njs_str("1.1"), njs_str("1.1") }, + { njs_str("100"), njs_str("100") }, + { njs_str("1a"), njs_str("NaN") }, + { njs_str("1e+19"), njs_str("NaN") }, + { njs_str("1e+22"), njs_str("1e+22") }, + { njs_str("1e22"), njs_str("NaN") }, + { njs_str("4294967296"), njs_str("4294967296") }, }; for (i = 0; i < njs_nitems(tests); i++) { - if (njs_is_string(&tests[i].value)) { - njs_set_number(&value, njs_string_to_index(&tests[i].value)); - - ret = njs_vm_value_dump(vm, &s, &value, 0, 0); + (void) njs_vm_value_string_set(vm, njs_value_arg(&input), + tests[i].value.start, + tests[i].value.length); + + num = njs_string_to_index(njs_value_arg(&input)); + njs_value_number_set(njs_value_arg(&value), num); + + ret = njs_vm_value_dump(vm, &s, njs_value_arg(&value), 0, 0); if (ret != NJS_OK) { njs_printf("njs_string_to_index_test: " "njs_vm_value_dump() failed\n"); @@ -24531,91 +24583,13 @@ njs_string_to_index_test(njs_vm_t *vm, n success = njs_strstr_eq(&tests[i].expected, &s); if (!success) { - njs_string_get(&tests[i].value, &string); njs_printf("njs_string_to_index_test(\"%V\"):\n" "expected: \"%V\"\n got: \"%V\"\n", - &string, &tests[i].expected, &s); + &tests[i].value, &tests[i].expected, &s); stat->failed++; continue; } - } - - is_integer_index = njs_key_is_integer_index(njs_number(&value), - &tests[i].value); - - if (tests[i].is_integer_index != is_integer_index) { - njs_string_get(&tests[i].value, &string); - njs_printf("njs_string_to_index_test2(\"%V\"):\n" - "expected: %b\n got: %b\n", - &string, tests[i].is_integer_index, is_integer_index); - - stat->failed++; - continue; - } - - stat->passed++; - } - - return NJS_OK; -} - - -static njs_int_t -njs_to_int32_test(njs_vm_t *vm, njs_opts_t *opts, njs_stat_t *stat) -{ - int32_t i32, second; - njs_uint_t i; - - static const struct { - double value; - int32_t expected; - } tests[] = { - { -1.0, -1 }, - { 0.0, 0 }, - { 0.001, 0 }, - { 1.0, 1 }, - { 2147483647.0, 2147483647 }, - { 2147483648.0, -2147483648 }, - { 2147483649.0, -2147483647 }, - { -1844674406941458432.0, -2147483648 }, - { 4.835703278458518e+24 /* 2**(53+29) + 2**30 */, 1073741824 }, - { 9.671406556917036e+24 /* 2**(53+30) + 2**31 */, -2147483648 }, - }; - - for (i = 0; i < njs_nitems(tests); i++) { - i32 = njs_number_to_int32(tests[i].value); - - if (i32 != tests[i].expected) { - njs_printf("njs_to_int32_test(%f):\n" - "expected: %D\n got: %D\n", - tests[i].value, tests[i].expected, i32); - - stat->failed++; - continue; - } - - second = njs_number_to_int32(i32); - - if (i32 != second) { - njs_printf("njs_to_int32_test(%f): not idempodent\n" - "expected: %D\n got: %D\n", - tests[i].value, i32, second); - - stat->failed++; - continue; - } - - second = njs_number_to_int32(njs_number_to_uint32(tests[i].value)); - - if (i32 != second) { - njs_printf("ToInt32(%f) != ToInt32(ToUint32(%f))\n" - "left: %D\n right: %D\n", - tests[i].value, tests[i].value, i32, second); - - stat->failed++; - continue; - } stat->passed++; } @@ -24637,7 +24611,7 @@ njs_addr2line_test(njs_vm_t *vm, njs_opt const char *name; } tests[] = { { njs_addr2line_test, njs_stringify(njs_addr2line_test) }, - { njs_to_int32_test, njs_stringify(njs_to_int32_test) }, + { njs_string_to_index_test, njs_stringify(njs_string_to_index_test) }, }; for (i = 0; i < njs_nitems(tests); i++) { @@ -24687,8 +24661,6 @@ njs_vm_internal_api_test(njs_unit_test_t njs_str("njs_sort_test") }, { njs_string_to_index_test, njs_str("njs_string_to_index_test") }, - { njs_to_int32_test, - njs_str("njs_to_int32_test") }, #ifdef NJS_HAVE_ADDR2LINE { njs_addr2line_test, njs_str("njs_addr2line_test") }, @@ -24764,7 +24736,7 @@ njs_options_parse(njs_opts_t *opts, int switch (*p) { case '?': case 'h': - (void) write(STDOUT_FILENO, help, njs_length(help)); + njs_printf("%*s", njs_length(help), help); return NJS_DONE; case 'd': From xeioex at nginx.com Sat May 6 03:11:11 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 06 May 2023 03:11:11 +0000 Subject: [njs] Removed dead store introduced in fd956d2a25a3. Message-ID: details: https://hg.nginx.org/njs/rev/bc78369be278 branches: changeset: 2108:bc78369be278 user: Dmitry Volyntsev date: Fri May 05 20:08:58 2023 -0700 description: Removed dead store introduced in fd956d2a25a3. Found by Clang static analyzer. diffstat: external/njs_query_string_module.c | 1 - 1 files changed, 0 insertions(+), 1 deletions(-) diffs (11 lines): diff -r 40af42ad829b -r bc78369be278 external/njs_query_string_module.c --- a/external/njs_query_string_module.c Fri May 05 20:08:57 2023 -0700 +++ b/external/njs_query_string_module.c Fri May 05 20:08:58 2023 -0700 @@ -157,7 +157,6 @@ njs_query_string_decode(njs_vm_t *vm, nj cp = 0; length = 0; - ret = NJS_ERROR; p = start; end = p + size; From xeioex at nginx.com Sat May 6 06:05:09 2023 From: xeioex at nginx.com (=?iso-8859-1?q?Dmitry_Volyntsev?=) Date: Fri, 05 May 2023 23:05:09 -0700 Subject: [PATCH] Configure: introduced --without-libxslt Message-ID: <4891e0920d7c0e89def2.1683353109@xeioex-VirtualBox> # HG changeset patch # User Dmitry Volyntsev # Date 1683353037 25200 # Fri May 05 23:03:57 2023 -0700 # Node ID 4891e0920d7c0e89def28694686e34294c69acf1 # Parent b71e69247483631bd8fc79a47cc32b762625b1fb Configure: introduced --without-libxslt. This allows to explicitly disable libxslt discovery by nginx and nginx addons. diff --git a/auto/lib/conf b/auto/lib/conf --- a/auto/lib/conf +++ b/auto/lib/conf @@ -29,8 +29,21 @@ if [ $USE_ZLIB = YES ]; then . auto/lib/zlib/conf fi -if [ $USE_LIBXSLT != NO ]; then +if [ $USE_LIBXSLT != NO -a $USE_LIBXSLT != DISABLED ]; then . auto/lib/libxslt/conf + +else + if [ $USE_LIBXSLT = DISABLED -a $HTTP = YES -a $HTTP_XSLT = YES ]; then + +cat << END + +$0: error: the HTTP ngx_http_xslt_module requires the libxslt library. +You can either disable the module by using --without-http_xslt_module +option or you have to enable the libxslt support. + +END + exit 1 + fi fi if [ $USE_LIBGD != NO ]; then diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -277,7 +277,7 @@ if [ $HTTP = YES ]; then . auto/module fi - if [ $HTTP_XSLT != NO ]; then + if [ $HTTP_XSLT != NO -a $USE_LIBXSLT != DISABLED ]; then ngx_module_name=ngx_http_xslt_filter_module ngx_module_incs= ngx_module_deps= diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -363,6 +363,8 @@ use the \"--with-mail_ssl_module\" optio --with-openssl=*) OPENSSL="$value" ;; --with-openssl-opt=*) OPENSSL_OPT="$value" ;; + --without-libxslt) USE_LIBXSLT=DISABLED ;; + --with-md5=*) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-md5\" option is deprecated" diff --git a/auto/summary b/auto/summary --- a/auto/summary +++ b/auto/summary @@ -28,6 +28,10 @@ case $OPENSSL in *) echo " + using OpenSSL library: $OPENSSL" ;; esac +if [ $USE_LIBXSLT = DISABLED ]; then + echo " + XSLT library is disabled" +fi + case $ZLIB in YES) echo " + using system zlib library" ;; NONE) echo " + zlib library is not used" ;; From mdounin at mdounin.ru Sat May 6 23:12:36 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sun, 7 May 2023 02:12:36 +0300 Subject: [PATCH] Configure: introduced --without-libxslt In-Reply-To: <4891e0920d7c0e89def2.1683353109@xeioex-VirtualBox> References: <4891e0920d7c0e89def2.1683353109@xeioex-VirtualBox> Message-ID: Hello! On Fri, May 05, 2023 at 11:05:09PM -0700, Dmitry Volyntsev wrote: > # HG changeset patch > # User Dmitry Volyntsev > # Date 1683353037 25200 > # Fri May 05 23:03:57 2023 -0700 > # Node ID 4891e0920d7c0e89def28694686e34294c69acf1 > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > Configure: introduced --without-libxslt. > > This allows to explicitly disable libxslt discovery by > nginx and nginx addons. > > diff --git a/auto/lib/conf b/auto/lib/conf > --- a/auto/lib/conf > +++ b/auto/lib/conf > @@ -29,8 +29,21 @@ if [ $USE_ZLIB = YES ]; then > . auto/lib/zlib/conf > fi > > -if [ $USE_LIBXSLT != NO ]; then > +if [ $USE_LIBXSLT != NO -a $USE_LIBXSLT != DISABLED ]; then > . auto/lib/libxslt/conf > + > +else > + if [ $USE_LIBXSLT = DISABLED -a $HTTP = YES -a $HTTP_XSLT = YES ]; then > + > +cat << END > + > +$0: error: the HTTP ngx_http_xslt_module requires the libxslt library. > +You can either disable the module by using --without-http_xslt_module > +option or you have to enable the libxslt support. > + > +END > + exit 1 > + fi > fi > > if [ $USE_LIBGD != NO ]; then > diff --git a/auto/modules b/auto/modules > --- a/auto/modules > +++ b/auto/modules > @@ -277,7 +277,7 @@ if [ $HTTP = YES ]; then > . auto/module > fi > > - if [ $HTTP_XSLT != NO ]; then > + if [ $HTTP_XSLT != NO -a $USE_LIBXSLT != DISABLED ]; then > ngx_module_name=ngx_http_xslt_filter_module > ngx_module_incs= > ngx_module_deps= > diff --git a/auto/options b/auto/options > --- a/auto/options > +++ b/auto/options > @@ -363,6 +363,8 @@ use the \"--with-mail_ssl_module\" optio > --with-openssl=*) OPENSSL="$value" ;; > --with-openssl-opt=*) OPENSSL_OPT="$value" ;; > > + --without-libxslt) USE_LIBXSLT=DISABLED ;; > + > --with-md5=*) > NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG > $0: warning: the \"--with-md5\" option is deprecated" The only "without" configure option for libraries we use is "--without-pcre", and it is disables PCRE library usage in the nginx core (notably, regular expressions in server names and locations). In contrast, the XSLT library is only used by the xslt filter module, and the natural way to disable its usage is to don't enable the module. Similarly, OpenSSL, zlib, GD, and GeoIP libraries are enabled by the corresponding modules, and not enabled when the modules are not enabled. It is not clear why XSLT should be different, and how this is expected to be used. -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Sun May 7 01:03:15 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 6 May 2023 18:03:15 -0700 Subject: [PATCH] Configure: introduced --without-libxslt In-Reply-To: References: <4891e0920d7c0e89def2.1683353109@xeioex-VirtualBox> Message-ID: <034ebc75-684a-d262-f1b3-bc0308b59f87@nginx.com> On 06.05.2023 16:12, Maxim Dounin wrote: > Hello! > > On Fri, May 05, 2023 at 11:05:09PM -0700, Dmitry Volyntsev wrote: > >> # HG changeset patch >> # User Dmitry Volyntsev >> # Date 1683353037 25200 >> # Fri May 05 23:03:57 2023 -0700 >> # Node ID 4891e0920d7c0e89def28694686e34294c69acf1 >> # Parent b71e69247483631bd8fc79a47cc32b762625b1fb >> Configure: introduced --without-libxslt. >> >> This allows to explicitly disable libxslt discovery by >> nginx and nginx addons. >> >> diff --git a/auto/lib/conf b/auto/lib/conf >> --- a/auto/lib/conf >> +++ b/auto/lib/conf >> @@ -29,8 +29,21 @@ if [ $USE_ZLIB = YES ]; then >> . auto/lib/zlib/conf >> fi >> >> -if [ $USE_LIBXSLT != NO ]; then >> +if [ $USE_LIBXSLT != NO -a $USE_LIBXSLT != DISABLED ]; then >> . auto/lib/libxslt/conf >> + >> +else >> + if [ $USE_LIBXSLT = DISABLED -a $HTTP = YES -a $HTTP_XSLT = YES ]; then >> + >> +cat << END >> + >> +$0: error: the HTTP ngx_http_xslt_module requires the libxslt library. >> +You can either disable the module by using --without-http_xslt_module >> +option or you have to enable the libxslt support. >> + >> +END >> + exit 1 >> + fi >> fi >> >> if [ $USE_LIBGD != NO ]; then >> diff --git a/auto/modules b/auto/modules >> --- a/auto/modules >> +++ b/auto/modules >> @@ -277,7 +277,7 @@ if [ $HTTP = YES ]; then >> . auto/module >> fi >> >> - if [ $HTTP_XSLT != NO ]; then >> + if [ $HTTP_XSLT != NO -a $USE_LIBXSLT != DISABLED ]; then >> ngx_module_name=ngx_http_xslt_filter_module >> ngx_module_incs= >> ngx_module_deps= >> diff --git a/auto/options b/auto/options >> --- a/auto/options >> +++ b/auto/options >> @@ -363,6 +363,8 @@ use the \"--with-mail_ssl_module\" optio >> --with-openssl=*) OPENSSL="$value" ;; >> --with-openssl-opt=*) OPENSSL_OPT="$value" ;; >> >> + --without-libxslt) USE_LIBXSLT=DISABLED ;; >> + >> --with-md5=*) >> NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG >> $0: warning: the \"--with-md5\" option is deprecated" > The only "without" configure option for libraries we use is > "--without-pcre", and it is disables PCRE library usage in the > nginx core (notably, regular expressions in server names and > locations). > > In contrast, the XSLT library is only used by the xslt filter > module, and the natural way to disable its usage is to don't > enable the module. Similarly, OpenSSL, zlib, GD, and GeoIP > libraries are enabled by the corresponding modules, and not > enabled when the modules are not enabled. > > It is not clear why XSLT should be different, and how this is > expected to be used. That is correct that XSLT library is only used for xslt filter. Nevertheless LIBXSLT is supported in ngx_module_libs for 3rd-party nginx modules, so for XSLT there are may be other uses outside of nginx core. Unlike OPENSSL and ZLIB which are essential parts of any modern nginx build, LIBXSLT is less commonly used and users sometimes want to disable LIBXSLT altogether. What is the suggested way for a user to build 3rd-party module when he/she wants to specifically disable a part which needs LIBXSLT? Alternatively there can be a way for nginx user building nginx to somehow signal to a 3rd-party module configure script what he/she wants. > From jordanc.carter at outlook.com Sun May 7 20:55:19 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 7 May 2023 21:55:19 +0100 Subject: [PATCH] Asynchronous close event handling for single peer upstreams Message-ID: # HG changeset patch # User jordanc.carter at outlook.com # Date 1683491710 -3600 # Sun May 07 21:35:10 2023 +0100 # Node ID e1ec9971da677b763c7576c729576d6f906631ae # Parent b71e69247483631bd8fc79a47cc32b762625b1fb Asynchronous close event handling for single peer upstreams Prevents additional upstream tries when consecutive asynchronous close errors are encountered for single peer upstreams utilizing keepalive connections. This replaces the current behavior of unlimited retries. diff -r b71e69247483 -r e1ec9971da67 src/event/ngx_event_connect.h --- a/src/event/ngx_event_connect.h Mon May 01 19:16:05 2023 +0400 +++ b/src/event/ngx_event_connect.h Sun May 07 21:35:10 2023 +0100 @@ -17,6 +17,7 @@ #define NGX_PEER_KEEPALIVE 1 #define NGX_PEER_NEXT 2 #define NGX_PEER_FAILED 4 +#define NGX_PEER_ASYNC_FAILED 8 typedef struct ngx_peer_connection_s ngx_peer_connection_t; @@ -64,6 +65,7 @@ unsigned transparent:1; unsigned so_keepalive:1; unsigned down:1; + unsigned async_failed:1; /* ngx_connection_log_error_e */ unsigned log_error:2; diff -r b71e69247483 -r e1ec9971da67 src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c Mon May 01 19:16:05 2023 +0400 +++ b/src/http/ngx_http_upstream.c Sun May 07 21:35:10 2023 +0100 @@ -4317,6 +4317,9 @@ { state = NGX_PEER_NEXT; + } else if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) { + state = NGX_PEER_FAILED | NGX_PEER_ASYNC_FAILED; + } else { state = NGX_PEER_FAILED; } @@ -4330,11 +4333,6 @@ "upstream timed out"); } - if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) { - /* TODO: inform balancer instead */ - u->peer.tries++; - } - switch (ft_type) { case NGX_HTTP_UPSTREAM_FT_TIMEOUT: diff -r b71e69247483 -r e1ec9971da67 src/http/ngx_http_upstream_round_robin.c --- a/src/http/ngx_http_upstream_round_robin.c Mon May 01 19:16:05 2023 +0400 +++ b/src/http/ngx_http_upstream_round_robin.c Sun May 07 21:35:10 2023 +0100 @@ -623,6 +623,12 @@ ngx_http_upstream_rr_peers_unlock(rrp->peers); pc->tries = 0; + + if (state & NGX_PEER_ASYNC_FAILED && !pc->async_failed) { + pc->async_failed = 1; + pc->tries = 1; + } + return; } From arut at nginx.com Mon May 8 12:15:49 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 8 May 2023 16:15:49 +0400 Subject: [PATCH 3 of 3] QUIC: path MTU discovery In-Reply-To: <765817FB-D1C5-45AB-A20D-86E00A2CB4EE@nginx.com> References: <13d43a278510f131101c.1680015100@arut-laptop> <765817FB-D1C5-45AB-A20D-86E00A2CB4EE@nginx.com> Message-ID: <20230508121549.scp7b7ralak6ujxo@N00W24XTQX> Hi, On Mon, May 01, 2023 at 08:58:55PM +0400, Sergey Kandaurov wrote: > > > On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1679993500 -14400 > > # Tue Mar 28 12:51:40 2023 +0400 > > # Branch quic > > # Node ID 13d43a278510f131101c7b19d87455a0171ebe2f > > # Parent c686c97f4abd6e1ca9a2cc2324d5a24f3d035c58 > > QUIC: path MTU discovery. > > > > MTU selection starts by probing the maximum allowed MTU first. After that, > > binary search is used to find the path MTU. > > > > Maximum allowed MTU is calculated as the minimum of max_udp_payload for client > > and server, and local interface MTU. > > > > diff --git a/auto/unix b/auto/unix > > --- a/auto/unix > > +++ b/auto/unix > > @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ > > . auto/feature > > > > > > +# IP packet fragmentation flags > > + > > +ngx_feature="IP_DONTFRAG" > > +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" > > +ngx_feature_run=no > > +ngx_feature_incs="#include > > + #include " > > +ngx_feature_path= > > +ngx_feature_libs= > > +ngx_feature_test="getsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" > > +. auto/feature > > + > > + > > +ngx_feature="IPV6_DONTFRAG" > > +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" > > +ngx_feature_run=no > > +ngx_feature_incs="#include > > + #include " > > +ngx_feature_path= > > +ngx_feature_libs= > > +ngx_feature_test="getsockopt(0, IPPROTO_IPV6, IPV6_DONTFRAG, NULL, 0)" > > +. auto/feature > > + > > + > > +# Linux MTU flags > > + > > +ngx_feature="IP_PMTUDISC_DO" > > +ngx_feature_name="NGX_HAVE_IP_PMTUDISC_DO" > > +ngx_feature_run=no > > +ngx_feature_incs="#include > > + #include " > > +ngx_feature_path= > > +ngx_feature_libs= > > +ngx_feature_test="getsockopt(0, IPPROTO_IP, IP_PMTUDISC_DO, NULL, 0)" > > +. auto/feature > > + > > + > > +ngx_feature="IPV6_PMTUDISC_DO" > > +ngx_feature_name="NGX_HAVE_IPV6_PMTUDISC_DO" > > +ngx_feature_run=no > > +ngx_feature_incs="#include > > + #include " > > +ngx_feature_path= > > +ngx_feature_libs= > > +ngx_feature_test="getsockopt(0, IPPROTO_IPV6, IPV6_PMTUDISC_DO, NULL, 0)" > > +. auto/feature > > + > > + > > ngx_feature="TCP_DEFER_ACCEPT" > > ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" > > ngx_feature_run=no > > @@ -920,6 +968,19 @@ ngx_feature_test="int i = FIONREAD; prin > > . auto/feature > > > > > > +ngx_feature="ioctl(SIOCGIFMTU)" > > +ngx_feature_name="NGX_HAVE_SIOCGIFMTU" > > +ngx_feature_run=no > > +ngx_feature_incs="#include > > + #include > > + #include " > > +ngx_feature_path= > > +ngx_feature_libs= > > +ngx_feature_test="int i = SIOCGIFMTU; struct ifreq ifr; > > + ifr.ifr_name[0] = 'e'; printf(\"%d\", i)" > > +. auto/feature > > + > > + > > ngx_feature="struct tm.tm_gmtoff" > > ngx_feature_name="NGX_HAVE_GMTOFF" > > ngx_feature_run=no > > @@ -1002,3 +1063,17 @@ ngx_feature_test='struct addrinfo *res; > > if (getaddrinfo("localhost", NULL, NULL, &res) != 0) return 1; > > freeaddrinfo(res)' > > . auto/feature > > + > > + > > +ngx_feature="getifaddrs()" > > +ngx_feature_name="NGX_HAVE_GETIFADDRS" > > +ngx_feature_run=no > > +ngx_feature_incs="#include > > + #include > > + #include " > > +ngx_feature_path= > > +ngx_feature_libs= > > +ngx_feature_test='struct ifaddrs *ifaddr; > > + if (getifaddrs(&ifaddr) != 0) return 1; > > + freeifaddrs(ifaddr)' > > +. auto/feature > > diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c > > --- a/src/core/ngx_connection.c > > +++ b/src/core/ngx_connection.c > > @@ -1010,6 +1010,74 @@ ngx_configure_listening_sockets(ngx_cycl > > } > > > > #endif > > + > > +#if (NGX_HAVE_IP_PMTUDISC_DO) > > + > > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { > > + value = 1; > > + > > + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_PMTUDISC_DO, > > + (const void *) &value, sizeof(int)) > > + == -1) > > + { > > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > > + "setsockopt(IP_PMTUDISC_DO) " > > + "for %V failed, ignored", > > + &ls[i].addr_text); > > + } > > + } > > + > > +#elif (NGX_HAVE_IP_DONTFRAG) > > + > > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { > > + value = 1; > > + > > + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, > > + (const void *) &value, sizeof(int)) > > + == -1) > > + { > > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > > + "setsockopt(IP_DONTFRAG) " > > + "for %V failed, ignored", > > + &ls[i].addr_text); > > + } > > + } > > + > > +#endif > > + > > +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_PMTUDISC_DO) > > + > > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { > > + value = 1; > > + > > + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_PMTUDISC_DO, > > + (const void *) &value, sizeof(int)) > > + == -1) > > + { > > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > > + "setsockopt(IPV6_PMTUDISC_DO) " > > + "for %V failed, ignored", > > + &ls[i].addr_text); > > + } > > + } > > + > > +#elif (NGX_HAVE_INET6 && NGX_HAVE_IPV6_DONTFRAG) > > + > > + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { > > + value = 1; > > + > > + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, > > + (const void *) &value, sizeof(int)) > > + == -1) > > + { > > + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, > > + "setsockopt(IPV6_DONTFRAG) " > > + "for %V failed, ignored", > > + &ls[i].addr_text); > > + } > > + } > > + > > +#endif > > } > > > > return; > > @@ -1507,6 +1575,10 @@ ngx_connection_error(ngx_connection_t *c > > } > > #endif > > > > + if (err == NGX_EMSGSIZE && c->log_error == NGX_ERROR_IGNORE_EMSGSIZE) { > > + return 0; > > + } > > + > > if (err == 0 > > || err == NGX_ECONNRESET > > #if (NGX_WIN32) > > @@ -1524,6 +1596,7 @@ ngx_connection_error(ngx_connection_t *c > > { > > switch (c->log_error) { > > > > + case NGX_ERROR_IGNORE_EMSGSIZE: > > case NGX_ERROR_IGNORE_EINVAL: > > case NGX_ERROR_IGNORE_ECONNRESET: > > case NGX_ERROR_INFO: > > diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h > > --- a/src/core/ngx_connection.h > > +++ b/src/core/ngx_connection.h > > @@ -97,7 +97,8 @@ typedef enum { > > NGX_ERROR_ERR, > > NGX_ERROR_INFO, > > NGX_ERROR_IGNORE_ECONNRESET, > > - NGX_ERROR_IGNORE_EINVAL > > + NGX_ERROR_IGNORE_EINVAL, > > + NGX_ERROR_IGNORE_EMSGSIZE > > } ngx_connection_log_error_e; > > > > > > I'd move the dontfrag part to a separate change for clarity. > It can be seen as a foundation for succeeding PLPMTUD work > not strictly related to it. > (Further, PLPMTUD is an optional feature, while dontfrag > is a MUST per RFC 9000, section 14.) You're right. Attached is a separate patch for this. [..] -- Roman Arutyunyan -------------- next part -------------- # HG changeset patch # User Roman Arutyunyan # Date 1683375807 -14400 # Sat May 06 16:23:27 2023 +0400 # Branch quic # Node ID afebde21cb32b9326219af075acc7dc415587d71 # Parent 9ae24a9ba7637646ea201a2014ae8294d4db2a82 QUIC: disabled datagram fragmentation. As per RFC 9000, Section 14: UDP datagrams MUST NOT be fragmented at the IP layer. diff --git a/auto/unix b/auto/unix --- a/auto/unix +++ b/auto/unix @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ . auto/feature +# IP packet fragmentation + +ngx_feature="IP_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IP_PMTUDISC_DO; + setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IPV6_PMTUDISC_DO; + setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IP_DONTFRAG" +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_DONTFRAG" +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" +. auto/feature + + ngx_feature="TCP_DEFER_ACCEPT" ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" ngx_feature_run=no diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1010,6 +1010,78 @@ ngx_configure_listening_sockets(ngx_cycl } #endif + +#if (NGX_HAVE_IP_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6) + +#if (NGX_HAVE_IPV6_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#endif } return; From pluknet at nginx.com Mon May 8 15:10:58 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 8 May 2023 19:10:58 +0400 Subject: [PATCH 3 of 3] QUIC: path MTU discovery In-Reply-To: <20230508121549.scp7b7ralak6ujxo@N00W24XTQX> References: <13d43a278510f131101c.1680015100@arut-laptop> <765817FB-D1C5-45AB-A20D-86E00A2CB4EE@nginx.com> <20230508121549.scp7b7ralak6ujxo@N00W24XTQX> Message-ID: <64B0C504-6479-4386-8B57-770D61176C6A@nginx.com> > On 8 May 2023, at 16:15, Roman Arutyunyan wrote: > > Hi, > > On Mon, May 01, 2023 at 08:58:55PM +0400, Sergey Kandaurov wrote: >> >>> On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: >>> >>> # HG changeset patch >>> # User Roman Arutyunyan >>> # Date 1679993500 -14400 >>> # Tue Mar 28 12:51:40 2023 +0400 >>> # Branch quic >>> # Node ID 13d43a278510f131101c7b19d87455a0171ebe2f >>> # Parent c686c97f4abd6e1ca9a2cc2324d5a24f3d035c58 >>> QUIC: path MTU discovery. >>> [..] >> >> I'd move the dontfrag part to a separate change for clarity. >> It can be seen as a foundation for succeeding PLPMTUD work >> not strictly related to it. >> (Further, PLPMTUD is an optional feature, while dontfrag >> is a MUST per RFC 9000, section 14.) > > You're right. Attached is a separate patch for this. > > [..] > I think it's fine. -- Sergey Kandaurov From mdounin at mdounin.ru Mon May 8 21:42:46 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 9 May 2023 00:42:46 +0300 Subject: [PATCH] Configure: introduced --without-libxslt In-Reply-To: <034ebc75-684a-d262-f1b3-bc0308b59f87@nginx.com> References: <4891e0920d7c0e89def2.1683353109@xeioex-VirtualBox> <034ebc75-684a-d262-f1b3-bc0308b59f87@nginx.com> Message-ID: Hello! On Sat, May 06, 2023 at 06:03:15PM -0700, Dmitry Volyntsev wrote: > On 06.05.2023 16:12, Maxim Dounin wrote: > > > On Fri, May 05, 2023 at 11:05:09PM -0700, Dmitry Volyntsev wrote: > > > >> # HG changeset patch > >> # User Dmitry Volyntsev > >> # Date 1683353037 25200 > >> # Fri May 05 23:03:57 2023 -0700 > >> # Node ID 4891e0920d7c0e89def28694686e34294c69acf1 > >> # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > >> Configure: introduced --without-libxslt. > >> > >> This allows to explicitly disable libxslt discovery by > >> nginx and nginx addons. > >> > >> diff --git a/auto/lib/conf b/auto/lib/conf > >> --- a/auto/lib/conf > >> +++ b/auto/lib/conf > >> @@ -29,8 +29,21 @@ if [ $USE_ZLIB = YES ]; then > >> . auto/lib/zlib/conf > >> fi > >> > >> -if [ $USE_LIBXSLT != NO ]; then > >> +if [ $USE_LIBXSLT != NO -a $USE_LIBXSLT != DISABLED ]; then > >> . auto/lib/libxslt/conf > >> + > >> +else > >> + if [ $USE_LIBXSLT = DISABLED -a $HTTP = YES -a $HTTP_XSLT = YES ]; then > >> + > >> +cat << END > >> + > >> +$0: error: the HTTP ngx_http_xslt_module requires the libxslt library. > >> +You can either disable the module by using --without-http_xslt_module > >> +option or you have to enable the libxslt support. > >> + > >> +END > >> + exit 1 > >> + fi > >> fi > >> > >> if [ $USE_LIBGD != NO ]; then > >> diff --git a/auto/modules b/auto/modules > >> --- a/auto/modules > >> +++ b/auto/modules > >> @@ -277,7 +277,7 @@ if [ $HTTP = YES ]; then > >> . auto/module > >> fi > >> > >> - if [ $HTTP_XSLT != NO ]; then > >> + if [ $HTTP_XSLT != NO -a $USE_LIBXSLT != DISABLED ]; then > >> ngx_module_name=ngx_http_xslt_filter_module > >> ngx_module_incs= > >> ngx_module_deps= > >> diff --git a/auto/options b/auto/options > >> --- a/auto/options > >> +++ b/auto/options > >> @@ -363,6 +363,8 @@ use the \"--with-mail_ssl_module\" optio > >> --with-openssl=*) OPENSSL="$value" ;; > >> --with-openssl-opt=*) OPENSSL_OPT="$value" ;; > >> > >> + --without-libxslt) USE_LIBXSLT=DISABLED ;; > >> + > >> --with-md5=*) > >> NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG > >> $0: warning: the \"--with-md5\" option is deprecated" > > > > The only "without" configure option for libraries we use is > > "--without-pcre", and it is disables PCRE library usage in the > > nginx core (notably, regular expressions in server names and > > locations). > > > > In contrast, the XSLT library is only used by the xslt filter > > module, and the natural way to disable its usage is to don't > > enable the module. Similarly, OpenSSL, zlib, GD, and GeoIP > > libraries are enabled by the corresponding modules, and not > > enabled when the modules are not enabled. > > > > It is not clear why XSLT should be different, and how this is > > expected to be used. > > That is correct that XSLT library is only used for xslt filter. > Nevertheless LIBXSLT is supported in ngx_module_libs for 3rd-party > nginx modules, so for XSLT there are may be other uses outside of > nginx core. Unlike OPENSSL and ZLIB which are essential parts of any > modern nginx build, LIBXSLT is less commonly used and users > sometimes want to disable LIBXSLT altogether. As for the other libraries, the expected approach to disable the XSLT library usage is to disable modules which depend on it. > What is the suggested way for a user to build 3rd-party module when > he/she wants to specifically disable a part which needs LIBXSLT? > > Alternatively there can be a way for nginx user building nginx to > somehow signal to a 3rd-party module configure script what he/she wants. As currently implemented, nginx expects an addon module to list libraries it depends on. If a module normally depends on a library, but can be configured to do not depend on it, it is something to be configured on the module side. A readily available solutions would be to pre-configure the module somehow, or use an environment variable to provide module-specific options. We can consider implementing a way to provide module-specific configure options, but I don't think I remember [m]any requests for this. -- Maxim Dounin http://mdounin.ru/ From 0x0davood at gmail.com Mon May 8 23:40:18 2023 From: 0x0davood at gmail.com (Davood Falahati) Date: Tue, 9 May 2023 01:40:18 +0200 Subject: enable request_auth module to send auth service error message body when it is allowed Message-ID: # HG changeset patch # User Davood Falahati <0x0davood at gmail.com> # Date 1683588448 -7200 # Tue May 09 01:27:28 2023 +0200 # Node ID 0977f155bc2d288eedf006033b9a5094d0e8098f # Parent b71e69247483631bd8fc79a47cc32b762625b1fb let request_auth_module pass auth body when it is allowed diff -r b71e69247483 -r 0977f155bc2d src/http/modules/ngx_http_auth_request_module.c --- a/src/http/modules/ngx_http_auth_request_module.c Mon May 01 19:16:05 2023 +0400 +++ b/src/http/modules/ngx_http_auth_request_module.c Tue May 09 01:27:28 2023 +0200 @@ -13,6 +13,7 @@ typedef struct { ngx_str_t uri; ngx_array_t *vars; + ngx_flag_t enable; } ngx_http_auth_request_conf_t; @@ -62,6 +63,12 @@ NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, + { ngx_string("send_auth_body"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_request_conf_t, enable), + NULL }, ngx_null_command }; @@ -106,6 +113,9 @@ ngx_http_post_subrequest_t *ps; ngx_http_auth_request_ctx_t *ctx; ngx_http_auth_request_conf_t *arcf; + ngx_list_t *hs; + ngx_buf_t *b; + ngx_chain_t out, *in; arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module); @@ -141,6 +151,36 @@ if (ctx->status == NGX_HTTP_UNAUTHORIZED) { sr = ctx->subrequest; + if (arcf->enable) { + + r->headers_out.content_type = sr->headers_out.content_type; + + hs = &sr->headers_out.headers; + + r->headers_out.headers = *hs; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + r->headers_out.status = ctx->status; + + b->last_buf = 1; + b->last_in_chain = 1; + b->memory = 1; + + out.buf = b; + out.next = NULL; + + in = ctx->subrequest->out; + in->next = &out; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, in); + } + h = sr->headers_out.www_authenticate; if (!h && sr->upstream) { @@ -323,6 +363,8 @@ conf->vars = NGX_CONF_UNSET_PTR; + conf->enable = NGX_CONF_UNSET; + return conf; } @@ -335,6 +377,7 @@ ngx_conf_merge_str_value(conf->uri, prev->uri, ""); ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); + ngx_conf_merge_value(conf->enable, prev->enable, 0); return NGX_CONF_OK; } -------------- next part -------------- An HTML attachment was scrubbed... URL: From 0x0davood at gmail.com Tue May 9 00:45:36 2023 From: 0x0davood at gmail.com (Davood Falahati) Date: Tue, 9 May 2023 02:45:36 +0200 Subject: keep response body of the subrequest inside the memory and use it if send_auth_body is set Message-ID: # HG changeset patch # User Davood Falahati <0x0davood at gmail.com> # Date 1683593026 -7200 # Tue May 09 02:43:46 2023 +0200 # Node ID 1053357966cda6a0902b748a9b4b8a214b36ccd4 # Parent b71e69247483631bd8fc79a47cc32b762625b1fb keep response body of the subrequest inside the memory and use it if send_auth_body is set diff -r b71e69247483 -r 1053357966cd src/http/modules/ngx_http_auth_request_module.c --- a/src/http/modules/ngx_http_auth_request_module.c Mon May 01 19:16:05 2023 +0400 +++ b/src/http/modules/ngx_http_auth_request_module.c Tue May 09 02:43:46 2023 +0200 @@ -13,6 +13,7 @@ typedef struct { ngx_str_t uri; ngx_array_t *vars; + ngx_flag_t enable; } ngx_http_auth_request_conf_t; @@ -62,6 +63,12 @@ NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, + { ngx_string("send_auth_body"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_request_conf_t, enable), + NULL }, ngx_null_command }; @@ -106,6 +113,9 @@ ngx_http_post_subrequest_t *ps; ngx_http_auth_request_ctx_t *ctx; ngx_http_auth_request_conf_t *arcf; + ngx_list_t *hs; + ngx_buf_t *b; + ngx_chain_t out, *in; arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module); @@ -141,6 +151,36 @@ if (ctx->status == NGX_HTTP_UNAUTHORIZED) { sr = ctx->subrequest; + if (arcf->enable) { + + r->headers_out.content_type = sr->headers_out.content_type; + + hs = &sr->headers_out.headers; + + r->headers_out.headers = *hs; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + r->headers_out.status = ctx->status; + + b->last_buf = 1; + b->last_in_chain = 1; + b->memory = 1; + + out.buf = b; + out.next = NULL; + + in = sr->out; + in->next = &out; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, in); + } + h = sr->headers_out.www_authenticate; if (!h && sr->upstream) { @@ -191,9 +231,12 @@ ps->handler = ngx_http_auth_request_done; ps->data = ctx; - + /* + * response body is being kept in memory and client won't receive it + * use subrequest->out to access the chain buffer + */ if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, - NGX_HTTP_SUBREQUEST_WAITED) + NGX_HTTP_SUBREQUEST_IN_MEMORY) != NGX_OK) { return NGX_ERROR; @@ -209,8 +252,6 @@ return NGX_ERROR; } - sr->header_only = 1; - ctx->subrequest = sr; ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module); @@ -323,6 +364,8 @@ conf->vars = NGX_CONF_UNSET_PTR; + conf->enable = NGX_CONF_UNSET; + return conf; } @@ -335,6 +378,7 @@ ngx_conf_merge_str_value(conf->uri, prev->uri, ""); ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); + ngx_conf_merge_value(conf->enable, prev->enable, 0); return NGX_CONF_OK; } -------------- next part -------------- An HTML attachment was scrubbed... URL: From mdounin at mdounin.ru Tue May 9 00:52:10 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 9 May 2023 03:52:10 +0300 Subject: enable request_auth module to send auth service error message body when it is allowed In-Reply-To: References: Message-ID: Hello! On Tue, May 09, 2023 at 01:40:18AM +0200, Davood Falahati wrote: > # HG changeset patch > # User Davood Falahati <0x0davood at gmail.com> > # Date 1683588448 -7200 > # Tue May 09 01:27:28 2023 +0200 > # Node ID 0977f155bc2d288eedf006033b9a5094d0e8098f > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > let request_auth_module pass auth body when it is allowed > > diff -r b71e69247483 -r 0977f155bc2d > src/http/modules/ngx_http_auth_request_module.c > --- a/src/http/modules/ngx_http_auth_request_module.c Mon May 01 19:16:05 > 2023 +0400 > +++ b/src/http/modules/ngx_http_auth_request_module.c Tue May 09 01:27:28 > 2023 +0200 > @@ -13,6 +13,7 @@ > typedef struct { > ngx_str_t uri; > ngx_array_t *vars; > + ngx_flag_t enable; > } ngx_http_auth_request_conf_t; > > > @@ -62,6 +63,12 @@ > NGX_HTTP_LOC_CONF_OFFSET, > 0, > NULL }, > + { ngx_string("send_auth_body"), > + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | > NGX_CONF_TAKE1, > + ngx_conf_set_flag_slot, > + NGX_HTTP_LOC_CONF_OFFSET, > + offsetof(ngx_http_auth_request_conf_t, enable), > + NULL }, > > ngx_null_command > }; > @@ -106,6 +113,9 @@ > ngx_http_post_subrequest_t *ps; > ngx_http_auth_request_ctx_t *ctx; > ngx_http_auth_request_conf_t *arcf; > + ngx_list_t *hs; > + ngx_buf_t *b; > + ngx_chain_t out, *in; > > arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module); > > @@ -141,6 +151,36 @@ > if (ctx->status == NGX_HTTP_UNAUTHORIZED) { > sr = ctx->subrequest; > > + if (arcf->enable) { > + > + r->headers_out.content_type = sr->headers_out.content_type; > + > + hs = &sr->headers_out.headers; > + > + r->headers_out.headers = *hs; > + > + b = ngx_calloc_buf(r->pool); > + if (b == NULL) { > + return NGX_ERROR; > + } > + > + r->headers_out.status = ctx->status; > + > + b->last_buf = 1; > + b->last_in_chain = 1; > + b->memory = 1; > + > + out.buf = b; > + out.next = NULL; > + > + in = ctx->subrequest->out; > + in->next = &out; > + > + ngx_http_send_header(r); > + > + return ngx_http_output_filter(r, in); > + } > + > h = sr->headers_out.www_authenticate; > > if (!h && sr->upstream) { > @@ -323,6 +363,8 @@ > > conf->vars = NGX_CONF_UNSET_PTR; > > + conf->enable = NGX_CONF_UNSET; > + > return conf; > } > > @@ -335,6 +377,7 @@ > > ngx_conf_merge_str_value(conf->uri, prev->uri, ""); > ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); > + ngx_conf_merge_value(conf->enable, prev->enable, 0); > > return NGX_CONF_OK; > } Thanks for the patch. It is, however, is not going to work for at least two reasons: 1. The ctx->subrequest->out is only available when there is a NGX_HTTP_SUBREQUEST_IN_MEMORY flag (and implies various restrictions). 2. The auth subrequst is created with the sr->header_only flag set, so the will be no response body available in at all. Futher, it might not be a good idea to copy all headers from the subrequest while not providing various links and pointers from the r->headers_out structure. This is going to break various filter modules, such as charset filter (which uses r->headers_out.charset, r->headers_out.override_charset, r->headers_out.content_encoding), sub filter (as testing content type uses r->headers_out.content_type_len), and many more things. Note well that "enable" isn't a good name for a field responsible for an optional feature. Similarly, "send_auth_body" does not look self-explanatory. Overall, please also take a look at http://nginx.org/en/docs/contributing_changes.html for some basic hints on how to submit patches. Most notably, it might be a good idea outline the use case for the feature you are trying to introduce and why existing features are not enough for this use case. The design of the module generally suggests that the custom response body, if needed, can be provided using the error_page directive, much like with other auth modules. Hope this helps. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue May 9 00:58:56 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 9 May 2023 03:58:56 +0300 Subject: keep response body of the subrequest inside the memory and use it if send_auth_body is set In-Reply-To: References: Message-ID: Hello! On Tue, May 09, 2023 at 02:45:36AM +0200, Davood Falahati wrote: > # HG changeset patch > # User Davood Falahati <0x0davood at gmail.com> > # Date 1683593026 -7200 > # Tue May 09 02:43:46 2023 +0200 > # Node ID 1053357966cda6a0902b748a9b4b8a214b36ccd4 > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > keep response body of the subrequest inside the memory and use it if > send_auth_body is set Please see the response to your previous patch. Please also note that when sending updated versions of a patch, it is usually a good idea to make sure they are properly threaded. When using hg email, use "--in-reply-to " to ensure correct threading. [...] -- Maxim Dounin http://mdounin.ru/ From pluknet at nginx.com Tue May 9 10:11:43 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 9 May 2023 14:11:43 +0400 Subject: [PATCH 1 of 3] QUIC: changed path validation timeout In-Reply-To: <20230503130044.m7l73z52qgxv6aym@N00W24XTQX> References: <121AA5DA-1F59-4544-A3CF-089CAC9B3CA8@nginx.com> <20230503130044.m7l73z52qgxv6aym@N00W24XTQX> Message-ID: <0BC9C2FA-22ED-401F-8E12-D95D45F0B2E5@nginx.com> > On 3 May 2023, at 17:00, Roman Arutyunyan wrote: > > Hi, > > On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote: >> >> >>> On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: >>> >>> # HG changeset patch >>> # User Roman Arutyunyan >>> # Date 1679925333 -14400 >>> # Mon Mar 27 17:55:33 2023 +0400 >>> # Branch quic >>> # Node ID f76e83412133085a6c82fce2c3e15b2c34a6e959 >>> # Parent 5fd628b89bb7fb5c95afa1dc914385f7ab79f6a3 >>> QUIC: changed path validation timeout. >>> >>> Path validation packets containing PATH_CHALLENGE frames are sent separately >>> from regular frame queue, because of the need to use a decicated path and >>> pad the packets. The packets are also resent separately from the regular >>> probe/lost detection mechanism. A path validation packet is resent 3 times, >>> each time after PTO expiration. Assuming constant PTO, the overall maximum >>> waiting time is 3 * PTO. According to RFC 9000, 8.2.4. Failed Path Validation, >>> the following value is recommended as a validation timeout: >>> >>> A value of three times the larger of the current PTO >>> or the PTO for the new path (using kInitialRtt, as >>> defined in [QUIC-RECOVERY]) is RECOMMENDED. >>> >>> The change adds PTO of the new path to the equation as the lower bound. >>> Also, max_ack_delay is now always accounted for, unlike previously, when >>> it was only used when there are packets in flight. As mentioned before, >>> PACH_CHALLENGE is not considered in-flight by nginx since it's processed >>> separately, but technically it is. >> >> I don't like an idea to make a separate function to calculate >> time for path validation retransmits. It looks like an existing >> function could be reused. >> >> I tend to think checking for inflight packets in ngx_quic_pto() >> isn't correct at the first place. The condition comes from >> the GetPtoTimeAndSpace example in 9002, A.8: >> >> : GetPtoTimeAndSpace(): >> : duration = (smoothed_rtt + max(4 * rttvar, kGranularity)) >> : * (2 ^ pto_count) >> : // Anti-deadlock PTO starts from the current time >> : if (no ack-eliciting packets in flight): >> : assert(!PeerCompletedAddressValidation()) >> : if (has handshake keys): >> : return (now() + duration), Handshake >> : else: >> : return (now() + duration), Initial >> : <..> >> : return pto_timeout, pto_space >> >> But PeerCompletedAddressValidation is always true for the server. >> The above anti-deadlock measure seems to only make sense for a client >> when it has no new data to send, but forced to send something to rise >> an anti-amplification limit for the server. This thought is supported >> by commentaries in places of GetPtoTimeAndSpace use. >> >> Removing the condition from ngx_quic_pto() makes possible to unify >> the function to use it for both regular PTO and path validation. >> >> Next is to make retransmits similar to a new connection establishment. >> Per RFC 9000, 8.2.1: >> : An endpoint SHOULD NOT probe a new path with packets containing a >> : PATH_CHALLENGE frame more frequently than it would send an Initial packet. >> >> I think we can improve path validation to use a separate backoff, >> path->tries can be used to base a backoff upon it. >> >> Since PATH_CHALLENGE are resent separately from the regular probe/lost >> detection mechanism, this needs to be moved out from ngx_quic_pto(). >> >> This makes the following series based on your patch. >> We could set an overall maximum waiting time of 3 * PTO and test it >> in pv handler in addition to the check for NGX_QUIC_PATH_RETRIES. > > Jftr, discussed all of the above in person. Agreed to implement that. > >> [..] >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1682338151 -14400 >> # Mon Apr 24 16:09:11 2023 +0400 >> # Branch quic >> # Node ID 808fe808e276496a9b026690c141201720744ab3 >> # Parent f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 >> QUIC: separated path validation retransmit backoff. >> >> Path validation packets containing PATH_CHALLENGE frames are sent separately >> from regular frame queue, because of the need to use a decicated path and pad >> the packets. The packets are sent periodically, separately from the regular >> probe/lost detection mechanism. A path validation packet is resent up to 3 >> times, each time after PTO expiration, with increasing per-path PTO backoff. >> >> 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 >> @@ -736,7 +736,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) - now); >> + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) >> + - now); >> >> if (w < 0) { >> w = 0; >> @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu >> >> duration = qc->avg_rtt; >> duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); >> - duration <<= qc->pto_count; >> >> if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { >> - duration += qc->ctp.max_ack_delay << qc->pto_count; >> + duration += qc->ctp.max_ack_delay; >> } >> >> return duration; >> @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) >> continue; >> } >> >> - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { >> + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) >> + - now) > 0) >> + { >> continue; >> } >> >> 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 >> @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t >> "quic initiated validation of path seq:%uL", path->seqnum); >> >> path->validating = 1; >> + path->tries = 0; >> >> if (RAND_bytes(path->challenge1, 8) != 1) { >> return NGX_ERROR; >> @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t >> pto = ngx_quic_pto(c, ctx); >> >> path->expires = ngx_current_msec + pto; >> - path->tries = NGX_QUIC_PATH_RETRIES; >> >> if (!qc->path_validation.timer_set) { >> ngx_add_timer(&qc->path_validation, pto); >> @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve >> qc = ngx_quic_get_connection(c); >> >> ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); >> - pto = ngx_quic_pto(c, ctx); >> >> next = -1; >> now = ngx_current_msec; >> @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve >> continue; >> } >> >> - if (--path->tries) { >> + if (++path->tries < NGX_QUIC_PATH_RETRIES) { >> + pto = ngx_quic_pto(c, ctx) << path->tries; > > Here we schedule a timer for 2 * PTO or 4 * PTO. Technically, our path > validation code allows several paths to be validated at the same time. If that > happens, 2 * PTO and 4 * PTO will be too much for the first attempt for the new > path, where the timeout is just PTO. As a result, the last path may wait 2x > or 4x longer than needed. Agree it needs to be addressed. Below is the first glance to set the next available pv timer: 1) in ngx_quic_validate_path(), a new path may own a shorter timer than already established, especially after several timeouts Previously, new path validation could be scheduled late. 2) in ngx_quic_handle_path_response_frame(), a validated path might left a spurious timer, while nothing more to validate or remaining paths have more distant timer This one is less severe, just extra work added. Unlike the two above, pv handler has a more complex logic I decided not to touch it. # HG changeset patch # User Sergey Kandaurov # Date 1683626923 -14400 # Tue May 09 14:08:43 2023 +0400 # Branch quic # Node ID 90f3e839532d899b09967cb2db3b3de30484c484 # Parent c9a6960c548501b5234ca7f7ff5ae23fad75b023 QUIC: reschedule path validation on path insertion/removal. Two issues fixed: - new path validation could be scheduled late - a validated path could leave a spurious timer 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 @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path( ngx_quic_path_t *path); static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); +static ngx_msec_int_t ngx_quic_next_pv_timer(ngx_connection_t *c); static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); @@ -78,6 +79,7 @@ ngx_quic_handle_path_response_frame(ngx_ { ngx_uint_t rst; ngx_queue_t *q; + ngx_msec_int_t next; ngx_quic_path_t *path, *prev; ngx_quic_connection_t *qc; @@ -169,6 +171,17 @@ valid: path->validating = 0; path->limited = 0; + /* reschedule if validated path owned next timer */ + + next = ngx_quic_next_pv_timer(c); + + if (next == -1) { + ngx_del_timer(&qc->path_validation); + + } else if (next > (ngx_msec_int_t) (path->expires - ngx_current_msec)) { + ngx_add_timer(&qc->path_validation, next); + } + return NGX_OK; } @@ -486,7 +499,7 @@ ngx_quic_handle_migration(ngx_connection static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) { - ngx_msec_t pto; + ngx_msec_t pto, next; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -517,6 +530,15 @@ ngx_quic_validate_path(ngx_connection_t if (!qc->path_validation.timer_set) { ngx_add_timer(&qc->path_validation, pto); + return NGX_OK; + } + + /* reschedule if new path owns next timer */ + + next = ngx_quic_next_pv_timer(c); + + if (next == pto) { + ngx_add_timer(&qc->path_validation, next); } return NGX_OK; @@ -563,6 +585,41 @@ ngx_quic_send_path_challenge(ngx_connect } +static ngx_msec_int_t +ngx_quic_next_pv_timer(ngx_connection_t *c) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left, next; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + next = -1; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (!path->validating) { + continue; + } + + left = path->expires - now; + + if (next == -1 || left < next) { + next = left; + } + } + + return next; +} + + void ngx_quic_path_validation_handler(ngx_event_t *ev) { > Luckily, this is not possible, because when we start > validation of a path, the validation for the old path is stopped. As internally discussed, it is still possible on apparent migration, where path validation starts on a previous active and now active paths. > This > allows us to simplify the PATH_RESPONSE and timeout handlers to only handle > qc->path. I suggest to add a patch for this. Hence, this wont work if client validates backup path (which is not qc->path). > It will also affect PMTUD, > since the same handler will be responsible for MTU packets. Also, it's > probably a good idea to suspend PMTUD for the old path when we switch to a > new one. This will happend naturally if we support only one path in those > handlers. > -- Sergey Kandaurov From pluknet at nginx.com Tue May 9 12:58:51 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 9 May 2023 16:58:51 +0400 Subject: [PATCH 1 of 3] QUIC: changed path validation timeout In-Reply-To: <0BC9C2FA-22ED-401F-8E12-D95D45F0B2E5@nginx.com> References: <121AA5DA-1F59-4544-A3CF-089CAC9B3CA8@nginx.com> <20230503130044.m7l73z52qgxv6aym@N00W24XTQX> <0BC9C2FA-22ED-401F-8E12-D95D45F0B2E5@nginx.com> Message-ID: > On 9 May 2023, at 14:11, Sergey Kandaurov wrote: > >> >> On 3 May 2023, at 17:00, Roman Arutyunyan wrote: >> >> Hi, >> >> On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote: >>> >>> >>>> On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: >>>> >>>> # HG changeset patch >>>> # User Roman Arutyunyan >>>> # Date 1679925333 -14400 >>>> # Mon Mar 27 17:55:33 2023 +0400 >>>> # Branch quic >>>> # Node ID f76e83412133085a6c82fce2c3e15b2c34a6e959 >>>> # Parent 5fd628b89bb7fb5c95afa1dc914385f7ab79f6a3 >>>> QUIC: changed path validation timeout. >>>> >>>> Path validation packets containing PATH_CHALLENGE frames are sent separately >>>> from regular frame queue, because of the need to use a decicated path and >>>> pad the packets. The packets are also resent separately from the regular >>>> probe/lost detection mechanism. A path validation packet is resent 3 times, >>>> each time after PTO expiration. Assuming constant PTO, the overall maximum >>>> waiting time is 3 * PTO. According to RFC 9000, 8.2.4. Failed Path Validation, >>>> the following value is recommended as a validation timeout: >>>> >>>> A value of three times the larger of the current PTO >>>> or the PTO for the new path (using kInitialRtt, as >>>> defined in [QUIC-RECOVERY]) is RECOMMENDED. >>>> >>>> The change adds PTO of the new path to the equation as the lower bound. >>>> Also, max_ack_delay is now always accounted for, unlike previously, when >>>> it was only used when there are packets in flight. As mentioned before, >>>> PACH_CHALLENGE is not considered in-flight by nginx since it's processed >>>> separately, but technically it is. >>> >>> I don't like an idea to make a separate function to calculate >>> time for path validation retransmits. It looks like an existing >>> function could be reused. >>> >>> I tend to think checking for inflight packets in ngx_quic_pto() >>> isn't correct at the first place. The condition comes from >>> the GetPtoTimeAndSpace example in 9002, A.8: >>> >>> : GetPtoTimeAndSpace(): >>> : duration = (smoothed_rtt + max(4 * rttvar, kGranularity)) >>> : * (2 ^ pto_count) >>> : // Anti-deadlock PTO starts from the current time >>> : if (no ack-eliciting packets in flight): >>> : assert(!PeerCompletedAddressValidation()) >>> : if (has handshake keys): >>> : return (now() + duration), Handshake >>> : else: >>> : return (now() + duration), Initial >>> : <..> >>> : return pto_timeout, pto_space >>> >>> But PeerCompletedAddressValidation is always true for the server. >>> The above anti-deadlock measure seems to only make sense for a client >>> when it has no new data to send, but forced to send something to rise >>> an anti-amplification limit for the server. This thought is supported >>> by commentaries in places of GetPtoTimeAndSpace use. >>> >>> Removing the condition from ngx_quic_pto() makes possible to unify >>> the function to use it for both regular PTO and path validation. >>> >>> Next is to make retransmits similar to a new connection establishment. >>> Per RFC 9000, 8.2.1: >>> : An endpoint SHOULD NOT probe a new path with packets containing a >>> : PATH_CHALLENGE frame more frequently than it would send an Initial packet. >>> >>> I think we can improve path validation to use a separate backoff, >>> path->tries can be used to base a backoff upon it. >>> >>> Since PATH_CHALLENGE are resent separately from the regular probe/lost >>> detection mechanism, this needs to be moved out from ngx_quic_pto(). >>> >>> This makes the following series based on your patch. >>> We could set an overall maximum waiting time of 3 * PTO and test it >>> in pv handler in addition to the check for NGX_QUIC_PATH_RETRIES. >> >> Jftr, discussed all of the above in person. Agreed to implement that. >> >>> [..] >>> # HG changeset patch >>> # User Sergey Kandaurov >>> # Date 1682338151 -14400 >>> # Mon Apr 24 16:09:11 2023 +0400 >>> # Branch quic >>> # Node ID 808fe808e276496a9b026690c141201720744ab3 >>> # Parent f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 >>> QUIC: separated path validation retransmit backoff. >>> >>> Path validation packets containing PATH_CHALLENGE frames are sent separately >>> from regular frame queue, because of the need to use a decicated path and pad >>> the packets. The packets are sent periodically, separately from the regular >>> probe/lost detection mechanism. A path validation packet is resent up to 3 >>> times, each time after PTO expiration, with increasing per-path PTO backoff. >>> >>> 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 >>> @@ -736,7 +736,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) - now); >>> + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) >>> + - now); >>> >>> if (w < 0) { >>> w = 0; >>> @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu >>> >>> duration = qc->avg_rtt; >>> duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); >>> - duration <<= qc->pto_count; >>> >>> if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { >>> - duration += qc->ctp.max_ack_delay << qc->pto_count; >>> + duration += qc->ctp.max_ack_delay; >>> } >>> >>> return duration; >>> @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) >>> continue; >>> } >>> >>> - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { >>> + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) >>> + - now) > 0) >>> + { >>> continue; >>> } >>> >>> 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 >>> @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t >>> "quic initiated validation of path seq:%uL", path->seqnum); >>> >>> path->validating = 1; >>> + path->tries = 0; >>> >>> if (RAND_bytes(path->challenge1, 8) != 1) { >>> return NGX_ERROR; >>> @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t >>> pto = ngx_quic_pto(c, ctx); >>> >>> path->expires = ngx_current_msec + pto; >>> - path->tries = NGX_QUIC_PATH_RETRIES; >>> >>> if (!qc->path_validation.timer_set) { >>> ngx_add_timer(&qc->path_validation, pto); >>> @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve >>> qc = ngx_quic_get_connection(c); >>> >>> ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); >>> - pto = ngx_quic_pto(c, ctx); >>> >>> next = -1; >>> now = ngx_current_msec; >>> @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve >>> continue; >>> } >>> >>> - if (--path->tries) { >>> + if (++path->tries < NGX_QUIC_PATH_RETRIES) { >>> + pto = ngx_quic_pto(c, ctx) << path->tries; >> >> Here we schedule a timer for 2 * PTO or 4 * PTO. Technically, our path >> validation code allows several paths to be validated at the same time. If that >> happens, 2 * PTO and 4 * PTO will be too much for the first attempt for the new >> path, where the timeout is just PTO. As a result, the last path may wait 2x >> or 4x longer than needed. > > Agree it needs to be addressed. > > Below is the first glance to set the next available pv timer: > 1) in ngx_quic_validate_path(), a new path may own a shorter timer > than already established, especially after several timeouts > Previously, new path validation could be scheduled late. > 2) in ngx_quic_handle_path_response_frame(), a validated path > might left a spurious timer, while nothing more to validate > or remaining paths have more distant timer > This one is less severe, just extra work added. > > Unlike the two above, pv handler has a more complex > logic I decided not to touch it. > Updated patch, with timers logic moved inside similar to ngx_quic_set_lost_timer(). # HG changeset patch # User Sergey Kandaurov # Date 1683636833 -14400 # Tue May 09 16:53:53 2023 +0400 # Branch quic # Node ID a40ce69032547110b1a10e953ca55dfc684f131d # Parent c9a6960c548501b5234ca7f7ff5ae23fad75b023 QUIC: reschedule path validation on path insertion/removal. Two issues fixed: - new path validation could be scheduled late - a validated path could leave a spurious timer 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 @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path( ngx_quic_path_t *path); static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); +static void ngx_quic_set_path_timer(ngx_connection_t *c); static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); @@ -169,6 +170,8 @@ valid: path->validating = 0; path->limited = 0; + ngx_quic_set_path_timer(c); + return NGX_OK; } @@ -515,9 +518,7 @@ ngx_quic_validate_path(ngx_connection_t path->expires = ngx_current_msec + pto; - if (!qc->path_validation.timer_set) { - ngx_add_timer(&qc->path_validation, pto); - } + ngx_quic_set_path_timer(c); return NGX_OK; } @@ -563,6 +564,46 @@ ngx_quic_send_path_challenge(ngx_connect } +static void +ngx_quic_set_path_timer(ngx_connection_t *c) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left, next; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + next = -1; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (!path->validating) { + continue; + } + + left = path->expires - now; + + if (next == -1 || left < next) { + next = left; + } + } + + if (next == -1) { + ngx_del_timer(&qc->path_validation); + + } else { + ngx_add_timer(&qc->path_validation, next); + } +} + + void ngx_quic_path_validation_handler(ngx_event_t *ev) { -- Sergey Kandaurov From 0x0davood at gmail.com Tue May 9 13:19:53 2023 From: 0x0davood at gmail.com (Davood Falahati) Date: Tue, 9 May 2023 15:19:53 +0200 Subject: The reason I think auth_request_module should be able to send the auth response body In-Reply-To: References: Message-ID: Good day Maxim, thanks for your comprehensive response and sorry for breaking some conventions in this mailing list because I'm a real novice here. I would top post your points and try to answer them as far as I can. > 1. The ctx->subrequest->out is only available when there is a NGX_HTTP_SUBREQUEST_IN_MEMORY flag (and implies various restrictions). I am not aware of the side effects and restrictions it might incur. From the documentation, it reads: >> NGX_HTTP_SUBREQUEST_IN_MEMORY - Output is not sent to the client, but rather stored in memory. The flag only affects subrequests which are processed by one of the proxying modules. After a subrequest is finalized its output is available in r->out of type ngx_buf_t. > 2. The auth subrequest is created with the sr->header_only flag set, so the will be no response body available in at all. a rooky fault from my side. I think I have sent to emails in which one of them had sr->header_only set. In another email, I removed it. Thus, I can expect response body set in r->out buffer. > Futher, it might not be a good idea to copy all headers from the subrequest while not providing various links and pointers from the r->headers_out structure. This is going to break various filter modules, such as charset filter (which uses r->headers_out.charset, r->headers_out.override_charset, r->headers_out.content_encoding), sub filter (as testing content type uses r->headers_out.content_type_len), and many more things. good point, I wasn't aware of that and I would try to do it differently. > Note well that "enable" isn't a good name for a field responsible for an optional feature. Similarly, "send_auth_body" does not look self-explanatory. Agree. Will come up with more clear naming. > Overall, please also take a look at http://nginx.org/en/docs/contributing_changes.html for some basic hints on how to submit patches. Indeed, sorry for some pedantic mistakes. > Most notably, it might be a good idea outline the use case for the feature you are trying to introduce and why existing features are not enough for this use case. The design of the module generally suggests that the custom response body, if needed, can be provided using the error_page directive, much like with other auth modules. The reason I ended up in this patch was a problem we had in our company. We started using auth_request_module and it was working as expected, but suddenly, we noticed our frontend which handles the error messages, fails when the requests are not being successfully authenticated. In other words, we needed our frontend not to break its error handling routine when it receives a 401 or 403. For example, a user wants to see a resource but it has no proper role to do so. We want to show a message expressing the problem using our existing error handling mechanisms. Using custom error_page was not the solution for us. Our proxied endpoints should have changed a lot to set error responses into headers and it was a hefty, breaking workload for us and we decided not to go with it. Then I decided to modify the auth_request_module to make it transparent about the auth service error messages in case the implementers want it and they take the risk of showing those error messages. I think adding a flag to let the auth modules be pass-through would be a nice feature for the great Nginx. I hope I was informative enough, Davood Falahati On Tue, May 9, 2023 at 12:11 PM wrote: > Send nginx-devel mailing list submissions to > nginx-devel at nginx.org > > To subscribe or unsubscribe via the World Wide Web, visit > https://mailman.nginx.org/mailman/listinfo/nginx-devel > or, via email, send a message with subject or body 'help' to > nginx-devel-request at nginx.org > > You can reach the person managing the list at > nginx-devel-owner at nginx.org > > When replying, please edit your Subject line so it is more specific > than "Re: Contents of nginx-devel digest..." > > > Today's Topics: > > 1. keep response body of the subrequest inside the memory and > use it if send_auth_body is set (Davood Falahati) > 2. Re: enable request_auth module to send auth service error > message body when it is allowed (Maxim Dounin) > 3. Re: keep response body of the subrequest inside the memory > and use it if send_auth_body is set (Maxim Dounin) > 4. Re: [PATCH 1 of 3] QUIC: changed path validation timeout > (Sergey Kandaurov) > > > ---------------------------------------------------------------------- > > Message: 1 > Date: Tue, 9 May 2023 02:45:36 +0200 > From: Davood Falahati <0x0davood at gmail.com> > To: nginx-devel at nginx.org > Subject: keep response body of the subrequest inside the memory and > use it if send_auth_body is set > Message-ID: > < > CADTHY-HSfhTynDi3h2ZF6Kx+9LW0Nf9fcG6uuoasWrxuh9H1Aw at mail.gmail.com> > Content-Type: text/plain; charset="utf-8" > > # HG changeset patch > # User Davood Falahati <0x0davood at gmail.com> > # Date 1683593026 -7200 > # Tue May 09 02:43:46 2023 +0200 > # Node ID 1053357966cda6a0902b748a9b4b8a214b36ccd4 > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > keep response body of the subrequest inside the memory and use it if > send_auth_body is set > > diff -r b71e69247483 -r 1053357966cd > src/http/modules/ngx_http_auth_request_module.c > --- a/src/http/modules/ngx_http_auth_request_module.c Mon May 01 19:16:05 > 2023 +0400 > +++ b/src/http/modules/ngx_http_auth_request_module.c Tue May 09 02:43:46 > 2023 +0200 > @@ -13,6 +13,7 @@ > typedef struct { > ngx_str_t uri; > ngx_array_t *vars; > + ngx_flag_t enable; > } ngx_http_auth_request_conf_t; > > > @@ -62,6 +63,12 @@ > NGX_HTTP_LOC_CONF_OFFSET, > 0, > NULL }, > + { ngx_string("send_auth_body"), > + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | > NGX_CONF_TAKE1, > + ngx_conf_set_flag_slot, > + NGX_HTTP_LOC_CONF_OFFSET, > + offsetof(ngx_http_auth_request_conf_t, enable), > + NULL }, > > ngx_null_command > }; > @@ -106,6 +113,9 @@ > ngx_http_post_subrequest_t *ps; > ngx_http_auth_request_ctx_t *ctx; > ngx_http_auth_request_conf_t *arcf; > + ngx_list_t *hs; > + ngx_buf_t *b; > + ngx_chain_t out, *in; > > arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module); > > @@ -141,6 +151,36 @@ > if (ctx->status == NGX_HTTP_UNAUTHORIZED) { > sr = ctx->subrequest; > > + if (arcf->enable) { > + > + r->headers_out.content_type = > sr->headers_out.content_type; > + > + hs = &sr->headers_out.headers; > + > + r->headers_out.headers = *hs; > + > + b = ngx_calloc_buf(r->pool); > + if (b == NULL) { > + return NGX_ERROR; > + } > + > + r->headers_out.status = ctx->status; > + > + b->last_buf = 1; > + b->last_in_chain = 1; > + b->memory = 1; > + > + out.buf = b; > + out.next = NULL; > + > + in = sr->out; > + in->next = &out; > + > + ngx_http_send_header(r); > + > + return ngx_http_output_filter(r, in); > + } > + > h = sr->headers_out.www_authenticate; > > if (!h && sr->upstream) { > @@ -191,9 +231,12 @@ > > ps->handler = ngx_http_auth_request_done; > ps->data = ctx; > - > + /* > + * response body is being kept in memory and client won't receive it > + * use subrequest->out to access the chain buffer > + */ > if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, > - NGX_HTTP_SUBREQUEST_WAITED) > + NGX_HTTP_SUBREQUEST_IN_MEMORY) > != NGX_OK) > { > return NGX_ERROR; > @@ -209,8 +252,6 @@ > return NGX_ERROR; > } > > - sr->header_only = 1; > - > ctx->subrequest = sr; > > ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module); > @@ -323,6 +364,8 @@ > > conf->vars = NGX_CONF_UNSET_PTR; > > + conf->enable = NGX_CONF_UNSET; > + > return conf; > } > > @@ -335,6 +378,7 @@ > > ngx_conf_merge_str_value(conf->uri, prev->uri, ""); > ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); > + ngx_conf_merge_value(conf->enable, prev->enable, 0); > > return NGX_CONF_OK; > } > -------------- next part -------------- > An HTML attachment was scrubbed... > URL: < > http://mailman.nginx.org/pipermail/nginx-devel/attachments/20230509/ea8a5325/attachment-0001.htm > > > > ------------------------------ > > Message: 2 > Date: Tue, 9 May 2023 03:52:10 +0300 > From: Maxim Dounin > To: nginx-devel at nginx.org > Subject: Re: enable request_auth module to send auth service error > message body when it is allowed > Message-ID: > Content-Type: text/plain; charset=us-ascii > > Hello! > > On Tue, May 09, 2023 at 01:40:18AM +0200, Davood Falahati wrote: > > > # HG changeset patch > > # User Davood Falahati <0x0davood at gmail.com> > > # Date 1683588448 -7200 > > # Tue May 09 01:27:28 2023 +0200 > > # Node ID 0977f155bc2d288eedf006033b9a5094d0e8098f > > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > > let request_auth_module pass auth body when it is allowed > > > > diff -r b71e69247483 -r 0977f155bc2d > > src/http/modules/ngx_http_auth_request_module.c > > --- a/src/http/modules/ngx_http_auth_request_module.c Mon May 01 19:16:05 > > 2023 +0400 > > +++ b/src/http/modules/ngx_http_auth_request_module.c Tue May 09 01:27:28 > > 2023 +0200 > > @@ -13,6 +13,7 @@ > > typedef struct { > > ngx_str_t uri; > > ngx_array_t *vars; > > + ngx_flag_t enable; > > } ngx_http_auth_request_conf_t; > > > > > > @@ -62,6 +63,12 @@ > > NGX_HTTP_LOC_CONF_OFFSET, > > 0, > > NULL }, > > + { ngx_string("send_auth_body"), > > + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | > > NGX_CONF_TAKE1, > > + ngx_conf_set_flag_slot, > > + NGX_HTTP_LOC_CONF_OFFSET, > > + offsetof(ngx_http_auth_request_conf_t, enable), > > + NULL }, > > > > ngx_null_command > > }; > > @@ -106,6 +113,9 @@ > > ngx_http_post_subrequest_t *ps; > > ngx_http_auth_request_ctx_t *ctx; > > ngx_http_auth_request_conf_t *arcf; > > + ngx_list_t *hs; > > + ngx_buf_t *b; > > + ngx_chain_t out, *in; > > > > arcf = ngx_http_get_module_loc_conf(r, > ngx_http_auth_request_module); > > > > @@ -141,6 +151,36 @@ > > if (ctx->status == NGX_HTTP_UNAUTHORIZED) { > > sr = ctx->subrequest; > > > > + if (arcf->enable) { > > + > > + r->headers_out.content_type = > sr->headers_out.content_type; > > + > > + hs = &sr->headers_out.headers; > > + > > + r->headers_out.headers = *hs; > > + > > + b = ngx_calloc_buf(r->pool); > > + if (b == NULL) { > > + return NGX_ERROR; > > + } > > + > > + r->headers_out.status = ctx->status; > > + > > + b->last_buf = 1; > > + b->last_in_chain = 1; > > + b->memory = 1; > > + > > + out.buf = b; > > + out.next = NULL; > > + > > + in = ctx->subrequest->out; > > + in->next = &out; > > + > > + ngx_http_send_header(r); > > + > > + return ngx_http_output_filter(r, in); > > + } > > + > > h = sr->headers_out.www_authenticate; > > > > if (!h && sr->upstream) { > > @@ -323,6 +363,8 @@ > > > > conf->vars = NGX_CONF_UNSET_PTR; > > > > + conf->enable = NGX_CONF_UNSET; > > + > > return conf; > > } > > > > @@ -335,6 +377,7 @@ > > > > ngx_conf_merge_str_value(conf->uri, prev->uri, ""); > > ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); > > + ngx_conf_merge_value(conf->enable, prev->enable, 0); > > > > return NGX_CONF_OK; > > } > > Thanks for the patch. It is, however, is not going to work for at > least two reasons: > > 1. The ctx->subrequest->out is only available when there is a > NGX_HTTP_SUBREQUEST_IN_MEMORY flag (and implies various > restrictions). > > 2. The auth subrequst is created with the sr->header_only flag > set, so the will be no response body available in at all. > > Futher, it might not be a good idea to copy all headers from the > subrequest while not providing various links and pointers from the > r->headers_out structure. This is going to break various filter > modules, such as charset filter (which uses > r->headers_out.charset, r->headers_out.override_charset, > r->headers_out.content_encoding), sub filter (as testing content > type uses r->headers_out.content_type_len), and many more things. > > Note well that "enable" isn't a good name for a field responsible > for an optional feature. Similarly, "send_auth_body" does not > look self-explanatory. > > Overall, please also take a look at > http://nginx.org/en/docs/contributing_changes.html for some basic > hints on how to submit patches. > > Most notably, it might be a good idea outline the use case for the > feature you are trying to introduce and why existing features are > not enough for this use case. The design of the module generally > suggests that the custom response body, if needed, can be provided > using the error_page directive, much like with other auth modules. > > Hope this helps. > > -- > Maxim Dounin > http://mdounin.ru/ > > ------------------------------ > > Message: 3 > Date: Tue, 9 May 2023 03:58:56 +0300 > From: Maxim Dounin > To: nginx-devel at nginx.org > Subject: Re: keep response body of the subrequest inside the memory > and use it if send_auth_body is set > Message-ID: > Content-Type: text/plain; charset=us-ascii > > Hello! > > On Tue, May 09, 2023 at 02:45:36AM +0200, Davood Falahati wrote: > > > # HG changeset patch > > # User Davood Falahati <0x0davood at gmail.com> > > # Date 1683593026 -7200 > > # Tue May 09 02:43:46 2023 +0200 > > # Node ID 1053357966cda6a0902b748a9b4b8a214b36ccd4 > > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > > keep response body of the subrequest inside the memory and use it if > > send_auth_body is set > > Please see the response to your previous patch. > > Please also note that when sending updated versions of a patch, it > is usually a good idea to make sure they are properly threaded. > When using hg email, use "--in-reply-to " to ensure correct > threading. > > [...] > > -- > Maxim Dounin > http://mdounin.ru/ > > ------------------------------ > > Message: 4 > Date: Tue, 9 May 2023 14:11:43 +0400 > From: Sergey Kandaurov > To: nginx-devel at nginx.org > Subject: Re: [PATCH 1 of 3] QUIC: changed path validation timeout > Message-ID: <0BC9C2FA-22ED-401F-8E12-D95D45F0B2E5 at nginx.com> > Content-Type: text/plain; charset=us-ascii > > > > On 3 May 2023, at 17:00, Roman Arutyunyan wrote: > > > > Hi, > > > > On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote: > >> > >> > >>> On 28 Mar 2023, at 18:51, Roman Arutyunyan wrote: > >>> > >>> # HG changeset patch > >>> # User Roman Arutyunyan > >>> # Date 1679925333 -14400 > >>> # Mon Mar 27 17:55:33 2023 +0400 > >>> # Branch quic > >>> # Node ID f76e83412133085a6c82fce2c3e15b2c34a6e959 > >>> # Parent 5fd628b89bb7fb5c95afa1dc914385f7ab79f6a3 > >>> QUIC: changed path validation timeout. > >>> > >>> Path validation packets containing PATH_CHALLENGE frames are sent > separately > >>> from regular frame queue, because of the need to use a decicated path > and > >>> pad the packets. The packets are also resent separately from the > regular > >>> probe/lost detection mechanism. A path validation packet is resent 3 > times, > >>> each time after PTO expiration. Assuming constant PTO, the overall > maximum > >>> waiting time is 3 * PTO. According to RFC 9000, 8.2.4. Failed Path > Validation, > >>> the following value is recommended as a validation timeout: > >>> > >>> A value of three times the larger of the current PTO > >>> or the PTO for the new path (using kInitialRtt, as > >>> defined in [QUIC-RECOVERY]) is RECOMMENDED. > >>> > >>> The change adds PTO of the new path to the equation as the lower bound. > >>> Also, max_ack_delay is now always accounted for, unlike previously, > when > >>> it was only used when there are packets in flight. As mentioned > before, > >>> PACH_CHALLENGE is not considered in-flight by nginx since it's > processed > >>> separately, but technically it is. > >> > >> I don't like an idea to make a separate function to calculate > >> time for path validation retransmits. It looks like an existing > >> function could be reused. > >> > >> I tend to think checking for inflight packets in ngx_quic_pto() > >> isn't correct at the first place. The condition comes from > >> the GetPtoTimeAndSpace example in 9002, A.8: > >> > >> : GetPtoTimeAndSpace(): > >> : duration = (smoothed_rtt + max(4 * rttvar, kGranularity)) > >> : * (2 ^ pto_count) > >> : // Anti-deadlock PTO starts from the current time > >> : if (no ack-eliciting packets in flight): > >> : assert(!PeerCompletedAddressValidation()) > >> : if (has handshake keys): > >> : return (now() + duration), Handshake > >> : else: > >> : return (now() + duration), Initial > >> : <..> > >> : return pto_timeout, pto_space > >> > >> But PeerCompletedAddressValidation is always true for the server. > >> The above anti-deadlock measure seems to only make sense for a client > >> when it has no new data to send, but forced to send something to rise > >> an anti-amplification limit for the server. This thought is supported > >> by commentaries in places of GetPtoTimeAndSpace use. > >> > >> Removing the condition from ngx_quic_pto() makes possible to unify > >> the function to use it for both regular PTO and path validation. > >> > >> Next is to make retransmits similar to a new connection establishment. > >> Per RFC 9000, 8.2.1: > >> : An endpoint SHOULD NOT probe a new path with packets containing a > >> : PATH_CHALLENGE frame more frequently than it would send an Initial > packet. > >> > >> I think we can improve path validation to use a separate backoff, > >> path->tries can be used to base a backoff upon it. > >> > >> Since PATH_CHALLENGE are resent separately from the regular probe/lost > >> detection mechanism, this needs to be moved out from ngx_quic_pto(). > >> > >> This makes the following series based on your patch. > >> We could set an overall maximum waiting time of 3 * PTO and test it > >> in pv handler in addition to the check for NGX_QUIC_PATH_RETRIES. > > > > Jftr, discussed all of the above in person. Agreed to implement that. > > > >> [..] > >> # HG changeset patch > >> # User Sergey Kandaurov > >> # Date 1682338151 -14400 > >> # Mon Apr 24 16:09:11 2023 +0400 > >> # Branch quic > >> # Node ID 808fe808e276496a9b026690c141201720744ab3 > >> # Parent f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 > >> QUIC: separated path validation retransmit backoff. > >> > >> Path validation packets containing PATH_CHALLENGE frames are sent > separately > >> from regular frame queue, because of the need to use a decicated path > and pad > >> the packets. The packets are sent periodically, separately from the > regular > >> probe/lost detection mechanism. A path validation packet is resent up > to 3 > >> times, each time after PTO expiration, with increasing per-path PTO > backoff. > >> > >> 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 > >> @@ -736,7 +736,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) - now); > >> + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << > qc->pto_count) > >> + - now); > >> > >> if (w < 0) { > >> w = 0; > >> @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu > >> > >> duration = qc->avg_rtt; > >> duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); > >> - duration <<= qc->pto_count; > >> > >> if (ctx->level == ssl_encryption_application && c->ssl->handshaked) > { > >> - duration += qc->ctp.max_ack_delay << qc->pto_count; > >> + duration += qc->ctp.max_ack_delay; > >> } > >> > >> return duration; > >> @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) > >> continue; > >> } > >> > >> - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > > 0) { > >> + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << > qc->pto_count) > >> + - now) > 0) > >> + { > >> continue; > >> } > >> > >> 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 > >> @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t > >> "quic initiated validation of path seq:%uL", > path->seqnum); > >> > >> path->validating = 1; > >> + path->tries = 0; > >> > >> if (RAND_bytes(path->challenge1, 8) != 1) { > >> return NGX_ERROR; > >> @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t > >> pto = ngx_quic_pto(c, ctx); > >> > >> path->expires = ngx_current_msec + pto; > >> - path->tries = NGX_QUIC_PATH_RETRIES; > >> > >> if (!qc->path_validation.timer_set) { > >> ngx_add_timer(&qc->path_validation, pto); > >> @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve > >> qc = ngx_quic_get_connection(c); > >> > >> ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); > >> - pto = ngx_quic_pto(c, ctx); > >> > >> next = -1; > >> now = ngx_current_msec; > >> @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve > >> continue; > >> } > >> > >> - if (--path->tries) { > >> + if (++path->tries < NGX_QUIC_PATH_RETRIES) { > >> + pto = ngx_quic_pto(c, ctx) << path->tries; > > > > Here we schedule a timer for 2 * PTO or 4 * PTO. Technically, our path > > validation code allows several paths to be validated at the same time. > If that > > happens, 2 * PTO and 4 * PTO will be too much for the first attempt for > the new > > path, where the timeout is just PTO. As a result, the last path may > wait 2x > > or 4x longer than needed. > > Agree it needs to be addressed. > > Below is the first glance to set the next available pv timer: > 1) in ngx_quic_validate_path(), a new path may own a shorter timer > than already established, especially after several timeouts > Previously, new path validation could be scheduled late. > 2) in ngx_quic_handle_path_response_frame(), a validated path > might left a spurious timer, while nothing more to validate > or remaining paths have more distant timer > This one is less severe, just extra work added. > > Unlike the two above, pv handler has a more complex > logic I decided not to touch it. > > # HG changeset patch > # User Sergey Kandaurov > # Date 1683626923 -14400 > # Tue May 09 14:08:43 2023 +0400 > # Branch quic > # Node ID 90f3e839532d899b09967cb2db3b3de30484c484 > # Parent c9a6960c548501b5234ca7f7ff5ae23fad75b023 > QUIC: reschedule path validation on path insertion/removal. > > Two issues fixed: > - new path validation could be scheduled late > - a validated path could leave a spurious timer > > 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 > @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path( > ngx_quic_path_t *path); > static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, > ngx_quic_path_t *path); > +static ngx_msec_int_t ngx_quic_next_pv_timer(ngx_connection_t *c); > static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t > tag); > > > @@ -78,6 +79,7 @@ ngx_quic_handle_path_response_frame(ngx_ > { > ngx_uint_t rst; > ngx_queue_t *q; > + ngx_msec_int_t next; > ngx_quic_path_t *path, *prev; > ngx_quic_connection_t *qc; > > @@ -169,6 +171,17 @@ valid: > path->validating = 0; > path->limited = 0; > > + /* reschedule if validated path owned next timer */ > + > + next = ngx_quic_next_pv_timer(c); > + > + if (next == -1) { > + ngx_del_timer(&qc->path_validation); > + > + } else if (next > (ngx_msec_int_t) (path->expires - > ngx_current_msec)) { > + ngx_add_timer(&qc->path_validation, next); > + } > + > return NGX_OK; > } > > @@ -486,7 +499,7 @@ ngx_quic_handle_migration(ngx_connection > static ngx_int_t > ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) > { > - ngx_msec_t pto; > + ngx_msec_t pto, next; > ngx_quic_send_ctx_t *ctx; > ngx_quic_connection_t *qc; > > @@ -517,6 +530,15 @@ ngx_quic_validate_path(ngx_connection_t > > if (!qc->path_validation.timer_set) { > ngx_add_timer(&qc->path_validation, pto); > + return NGX_OK; > + } > + > + /* reschedule if new path owns next timer */ > + > + next = ngx_quic_next_pv_timer(c); > + > + if (next == pto) { > + ngx_add_timer(&qc->path_validation, next); > } > > return NGX_OK; > @@ -563,6 +585,41 @@ ngx_quic_send_path_challenge(ngx_connect > } > > > +static ngx_msec_int_t > +ngx_quic_next_pv_timer(ngx_connection_t *c) > +{ > + ngx_msec_t now; > + ngx_queue_t *q; > + ngx_msec_int_t left, next; > + ngx_quic_path_t *path; > + ngx_quic_connection_t *qc; > + > + qc = ngx_quic_get_connection(c); > + > + now = ngx_current_msec; > + next = -1; > + > + for (q = ngx_queue_head(&qc->paths); > + q != ngx_queue_sentinel(&qc->paths); > + q = ngx_queue_next(q)) > + { > + path = ngx_queue_data(q, ngx_quic_path_t, queue); > + > + if (!path->validating) { > + continue; > + } > + > + left = path->expires - now; > + > + if (next == -1 || left < next) { > + next = left; > + } > + } > + > + return next; > +} > + > + > void > ngx_quic_path_validation_handler(ngx_event_t *ev) > { > > > > Luckily, this is not possible, because when we start > > validation of a path, the validation for the old path is stopped. > > As internally discussed, it is still possible on apparent migration, > where path validation starts on a previous active and now active paths. > > > This > > allows us to simplify the PATH_RESPONSE and timeout handlers to only > handle > > qc->path. I suggest to add a patch for this. > > Hence, this wont work if client validates backup path (which is not > qc->path). > > > It will also affect PMTUD, > > since the same handler will be responsible for MTU packets. Also, it's > > probably a good idea to suspend PMTUD for the old path when we switch to > a > > new one. This will happend naturally if we support only one path in > those > > handlers. > > > > -- > Sergey Kandaurov > > > ------------------------------ > > Subject: Digest Footer > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > > > ------------------------------ > > End of nginx-devel Digest, Vol 153, Issue 16 > ******************************************** > -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Tue May 9 15:07:09 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 9 May 2023 19:07:09 +0400 Subject: [PATCH 1 of 3] QUIC: changed path validation timeout In-Reply-To: References: <121AA5DA-1F59-4544-A3CF-089CAC9B3CA8@nginx.com> <20230503130044.m7l73z52qgxv6aym@N00W24XTQX> <0BC9C2FA-22ED-401F-8E12-D95D45F0B2E5@nginx.com> Message-ID: > On 9 May 2023, at 16:58, Sergey Kandaurov wrote: > >> >> On 9 May 2023, at 14:11, Sergey Kandaurov wrote: >> >>> >>> On 3 May 2023, at 17:00, Roman Arutyunyan wrote: >>> >>> Hi, >>> >>> On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote: >>>> >>>> >>> >>>> [..] >>>> # HG changeset patch >>>> # User Sergey Kandaurov >>>> # Date 1682338151 -14400 >>>> # Mon Apr 24 16:09:11 2023 +0400 >>>> # Branch quic >>>> # Node ID 808fe808e276496a9b026690c141201720744ab3 >>>> # Parent f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 >>>> QUIC: separated path validation retransmit backoff. >>>> >>>> Path validation packets containing PATH_CHALLENGE frames are sent separately >>>> from regular frame queue, because of the need to use a decicated path and pad >>>> the packets. The packets are sent periodically, separately from the regular >>>> probe/lost detection mechanism. A path validation packet is resent up to 3 >>>> times, each time after PTO expiration, with increasing per-path PTO backoff. >>>> >>>> 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 >>>> @@ -736,7 +736,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) - now); >>>> + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) >>>> + - now); >>>> >>>> if (w < 0) { >>>> w = 0; >>>> @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu >>>> >>>> duration = qc->avg_rtt; >>>> duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); >>>> - duration <<= qc->pto_count; >>>> >>>> if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { >>>> - duration += qc->ctp.max_ack_delay << qc->pto_count; >>>> + duration += qc->ctp.max_ack_delay; >>>> } >>>> >>>> return duration; >>>> @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) >>>> continue; >>>> } >>>> >>>> - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { >>>> + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) >>>> + - now) > 0) >>>> + { >>>> continue; >>>> } >>>> >>>> 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 >>>> @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t >>>> "quic initiated validation of path seq:%uL", path->seqnum); >>>> >>>> path->validating = 1; >>>> + path->tries = 0; >>>> >>>> if (RAND_bytes(path->challenge1, 8) != 1) { >>>> return NGX_ERROR; >>>> @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t >>>> pto = ngx_quic_pto(c, ctx); >>>> >>>> path->expires = ngx_current_msec + pto; >>>> - path->tries = NGX_QUIC_PATH_RETRIES; >>>> >>>> if (!qc->path_validation.timer_set) { >>>> ngx_add_timer(&qc->path_validation, pto); >>>> @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve >>>> qc = ngx_quic_get_connection(c); >>>> >>>> ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); >>>> - pto = ngx_quic_pto(c, ctx); >>>> >>>> next = -1; >>>> now = ngx_current_msec; >>>> @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve >>>> continue; >>>> } >>>> >>>> - if (--path->tries) { >>>> + if (++path->tries < NGX_QUIC_PATH_RETRIES) { >>>> + pto = ngx_quic_pto(c, ctx) << path->tries; >>> >>> Here we schedule a timer for 2 * PTO or 4 * PTO. Technically, our path >>> validation code allows several paths to be validated at the same time. If that >>> happens, 2 * PTO and 4 * PTO will be too much for the first attempt for the new >>> path, where the timeout is just PTO. As a result, the last path may wait 2x >>> or 4x longer than needed. >> >> Agree it needs to be addressed. >> >> Below is the first glance to set the next available pv timer: >> 1) in ngx_quic_validate_path(), a new path may own a shorter timer >> than already established, especially after several timeouts >> Previously, new path validation could be scheduled late. >> 2) in ngx_quic_handle_path_response_frame(), a validated path >> might left a spurious timer, while nothing more to validate >> or remaining paths have more distant timer >> This one is less severe, just extra work added. >> >> Unlike the two above, pv handler has a more complex >> logic I decided not to touch it. >> > > Updated patch, with timers logic moved inside > similar to ngx_quic_set_lost_timer(). > > # HG changeset patch > # User Sergey Kandaurov > # Date 1683636833 -14400 > # Tue May 09 16:53:53 2023 +0400 > # Branch quic > # Node ID a40ce69032547110b1a10e953ca55dfc684f131d > # Parent c9a6960c548501b5234ca7f7ff5ae23fad75b023 > QUIC: reschedule path validation on path insertion/removal. > > Two issues fixed: > - new path validation could be scheduled late > - a validated path could leave a spurious timer > > 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 > @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path( > ngx_quic_path_t *path); > static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, > ngx_quic_path_t *path); > +static void ngx_quic_set_path_timer(ngx_connection_t *c); > static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); > > > @@ -169,6 +170,8 @@ valid: > path->validating = 0; > path->limited = 0; > > + ngx_quic_set_path_timer(c); > + > return NGX_OK; > } > > @@ -515,9 +518,7 @@ ngx_quic_validate_path(ngx_connection_t > > path->expires = ngx_current_msec + pto; > > - if (!qc->path_validation.timer_set) { > - ngx_add_timer(&qc->path_validation, pto); > - } > + ngx_quic_set_path_timer(c); > > return NGX_OK; > } > @@ -563,6 +564,46 @@ ngx_quic_send_path_challenge(ngx_connect > } > > > +static void > +ngx_quic_set_path_timer(ngx_connection_t *c) > +{ > + ngx_msec_t now; > + ngx_queue_t *q; > + ngx_msec_int_t left, next; > + ngx_quic_path_t *path; > + ngx_quic_connection_t *qc; > + > + qc = ngx_quic_get_connection(c); > + > + now = ngx_current_msec; > + next = -1; > + > + for (q = ngx_queue_head(&qc->paths); > + q != ngx_queue_sentinel(&qc->paths); > + q = ngx_queue_next(q)) > + { > + path = ngx_queue_data(q, ngx_quic_path_t, queue); > + > + if (!path->validating) { > + continue; > + } > + > + left = path->expires - now; > + > + if (next == -1 || left < next) { > + next = left; > + } > + } > + > + if (next == -1) { > + ngx_del_timer(&qc->path_validation); > + > + } else { > + ngx_add_timer(&qc->path_validation, next); > + } > +} > + > + > void > ngx_quic_path_validation_handler(ngx_event_t *ev) > { > Addendum to handle negation expiration time. Previously, when updating path validation timer, another path being in the process of validation could already expire, notably when both were scheduled on apparent migration. This resulted in negative expiration time and could left a path without a timer. 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 @@ -586,17 +586,18 @@ ngx_quic_set_path_timer(ngx_connection_t } left = path->expires - now; + left = ngx_max(left, 1); if (next == -1 || left < next) { next = left; } } - if (next == -1) { - ngx_del_timer(&qc->path_validation); + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); - } else { - ngx_add_timer(&qc->path_validation, next); + } else if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); } } -- Sergey Kandaurov From arut at nginx.com Tue May 9 15:29:12 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Tue, 9 May 2023 19:29:12 +0400 Subject: [PATCH 1 of 3] QUIC: changed path validation timeout In-Reply-To: References: <121AA5DA-1F59-4544-A3CF-089CAC9B3CA8@nginx.com> <20230503130044.m7l73z52qgxv6aym@N00W24XTQX> <0BC9C2FA-22ED-401F-8E12-D95D45F0B2E5@nginx.com> Message-ID: <20230509152912.4qhihjsptqtza64k@N00W24XTQX> On Tue, May 09, 2023 at 07:07:09PM +0400, Sergey Kandaurov wrote: > > > On 9 May 2023, at 16:58, Sergey Kandaurov wrote: > > > >> > >> On 9 May 2023, at 14:11, Sergey Kandaurov wrote: > >> > >>> > >>> On 3 May 2023, at 17:00, Roman Arutyunyan wrote: > >>> > >>> Hi, > >>> > >>> On Mon, Apr 24, 2023 at 04:15:21PM +0400, Sergey Kandaurov wrote: > >>>> > >>>> > >>> > >>>> [..] > >>>> # HG changeset patch > >>>> # User Sergey Kandaurov > >>>> # Date 1682338151 -14400 > >>>> # Mon Apr 24 16:09:11 2023 +0400 > >>>> # Branch quic > >>>> # Node ID 808fe808e276496a9b026690c141201720744ab3 > >>>> # Parent f49aba6e3fb54843d3e3bd5df26dbb45f5d3d687 > >>>> QUIC: separated path validation retransmit backoff. > >>>> > >>>> Path validation packets containing PATH_CHALLENGE frames are sent separately > >>>> from regular frame queue, because of the need to use a decicated path and pad > >>>> the packets. The packets are sent periodically, separately from the regular > >>>> probe/lost detection mechanism. A path validation packet is resent up to 3 > >>>> times, each time after PTO expiration, with increasing per-path PTO backoff. > >>>> > >>>> 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 > >>>> @@ -736,7 +736,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) - now); > >>>> + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) > >>>> + - now); > >>>> > >>>> if (w < 0) { > >>>> w = 0; > >>>> @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_qu > >>>> > >>>> duration = qc->avg_rtt; > >>>> duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); > >>>> - duration <<= qc->pto_count; > >>>> > >>>> if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { > >>>> - duration += qc->ctp.max_ack_delay << qc->pto_count; > >>>> + duration += qc->ctp.max_ack_delay; > >>>> } > >>>> > >>>> return duration; > >>>> @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) > >>>> continue; > >>>> } > >>>> > >>>> - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { > >>>> + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) > >>>> + - now) > 0) > >>>> + { > >>>> continue; > >>>> } > >>>> > >>>> 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 > >>>> @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t > >>>> "quic initiated validation of path seq:%uL", path->seqnum); > >>>> > >>>> path->validating = 1; > >>>> + path->tries = 0; > >>>> > >>>> if (RAND_bytes(path->challenge1, 8) != 1) { > >>>> return NGX_ERROR; > >>>> @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t > >>>> pto = ngx_quic_pto(c, ctx); > >>>> > >>>> path->expires = ngx_current_msec + pto; > >>>> - path->tries = NGX_QUIC_PATH_RETRIES; > >>>> > >>>> if (!qc->path_validation.timer_set) { > >>>> ngx_add_timer(&qc->path_validation, pto); > >>>> @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_eve > >>>> qc = ngx_quic_get_connection(c); > >>>> > >>>> ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); > >>>> - pto = ngx_quic_pto(c, ctx); > >>>> > >>>> next = -1; > >>>> now = ngx_current_msec; > >>>> @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_eve > >>>> continue; > >>>> } > >>>> > >>>> - if (--path->tries) { > >>>> + if (++path->tries < NGX_QUIC_PATH_RETRIES) { > >>>> + pto = ngx_quic_pto(c, ctx) << path->tries; > >>> > >>> Here we schedule a timer for 2 * PTO or 4 * PTO. Technically, our path > >>> validation code allows several paths to be validated at the same time. If that > >>> happens, 2 * PTO and 4 * PTO will be too much for the first attempt for the new > >>> path, where the timeout is just PTO. As a result, the last path may wait 2x > >>> or 4x longer than needed. > >> > >> Agree it needs to be addressed. > >> > >> Below is the first glance to set the next available pv timer: > >> 1) in ngx_quic_validate_path(), a new path may own a shorter timer > >> than already established, especially after several timeouts > >> Previously, new path validation could be scheduled late. > >> 2) in ngx_quic_handle_path_response_frame(), a validated path > >> might left a spurious timer, while nothing more to validate > >> or remaining paths have more distant timer > >> This one is less severe, just extra work added. > >> > >> Unlike the two above, pv handler has a more complex > >> logic I decided not to touch it. > >> > > > > Updated patch, with timers logic moved inside > > similar to ngx_quic_set_lost_timer(). > > > > # HG changeset patch > > # User Sergey Kandaurov > > # Date 1683636833 -14400 > > # Tue May 09 16:53:53 2023 +0400 > > # Branch quic > > # Node ID a40ce69032547110b1a10e953ca55dfc684f131d > > # Parent c9a6960c548501b5234ca7f7ff5ae23fad75b023 > > QUIC: reschedule path validation on path insertion/removal. > > > > Two issues fixed: > > - new path validation could be scheduled late > > - a validated path could leave a spurious timer > > > > 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 > > @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path( > > ngx_quic_path_t *path); > > static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, > > ngx_quic_path_t *path); > > +static void ngx_quic_set_path_timer(ngx_connection_t *c); > > static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); > > > > > > @@ -169,6 +170,8 @@ valid: > > path->validating = 0; > > path->limited = 0; > > > > + ngx_quic_set_path_timer(c); > > + > > return NGX_OK; > > } > > > > @@ -515,9 +518,7 @@ ngx_quic_validate_path(ngx_connection_t > > > > path->expires = ngx_current_msec + pto; > > > > - if (!qc->path_validation.timer_set) { > > - ngx_add_timer(&qc->path_validation, pto); > > - } > > + ngx_quic_set_path_timer(c); > > > > return NGX_OK; > > } > > @@ -563,6 +564,46 @@ ngx_quic_send_path_challenge(ngx_connect > > } > > > > > > +static void > > +ngx_quic_set_path_timer(ngx_connection_t *c) > > +{ > > + ngx_msec_t now; > > + ngx_queue_t *q; > > + ngx_msec_int_t left, next; > > + ngx_quic_path_t *path; > > + ngx_quic_connection_t *qc; > > + > > + qc = ngx_quic_get_connection(c); > > + > > + now = ngx_current_msec; > > + next = -1; > > + > > + for (q = ngx_queue_head(&qc->paths); > > + q != ngx_queue_sentinel(&qc->paths); > > + q = ngx_queue_next(q)) > > + { > > + path = ngx_queue_data(q, ngx_quic_path_t, queue); > > + > > + if (!path->validating) { > > + continue; > > + } > > + > > + left = path->expires - now; > > + > > + if (next == -1 || left < next) { > > + next = left; > > + } > > + } > > + > > + if (next == -1) { > > + ngx_del_timer(&qc->path_validation); > > + > > + } else { > > + ngx_add_timer(&qc->path_validation, next); > > + } > > +} > > + > > + > > void > > ngx_quic_path_validation_handler(ngx_event_t *ev) > > { > > > > Addendum to handle negation expiration time. > > Previously, when updating path validation timer, another path being > in the process of validation could already expire, notably when both > were scheduled on apparent migration. > This resulted in negative expiration time and could left a path > without a timer. > > 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 > @@ -586,17 +586,18 @@ ngx_quic_set_path_timer(ngx_connection_t > } > > left = path->expires - now; > + left = ngx_max(left, 1); > > if (next == -1 || left < next) { > next = left; > } > } > > - if (next == -1) { > - ngx_del_timer(&qc->path_validation); > + if (next != -1) { > + ngx_add_timer(&qc->path_validation, next); > > - } else { > - ngx_add_timer(&qc->path_validation, next); > + } else if (qc->path_validation.timer_set) { > + ngx_del_timer(&qc->path_validation); > } > } Looks good -- Roman Arutyunyan From xeioex at nginx.com Wed May 10 02:00:02 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 10 May 2023 02:00:02 +0000 Subject: [njs] HTTP: fixed r.status setter when filtering. Message-ID: details: https://hg.nginx.org/njs/rev/2467a70fd45a branches: changeset: 2109:2467a70fd45a user: Dmitry Volyntsev date: Mon May 08 16:40:50 2023 -0700 description: HTTP: fixed r.status setter when filtering. diffstat: nginx/ngx_http_js_module.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (11 lines): diff -r bc78369be278 -r 2467a70fd45a nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Fri May 05 20:08:58 2023 -0700 +++ b/nginx/ngx_http_js_module.c Mon May 08 16:40:50 2023 -0700 @@ -2091,6 +2091,7 @@ ngx_http_js_ext_status(njs_vm_t *vm, njs } r->headers_out.status = n; + r->headers_out.status_line.len = 0; njs_value_undefined_set(retval); From xeioex at nginx.com Wed May 10 02:00:04 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 10 May 2023 02:00:04 +0000 Subject: [njs] Shell: simplified input completion handler. Message-ID: details: https://hg.nginx.org/njs/rev/d610d744bbd2 branches: changeset: 2110:d610d744bbd2 user: Dmitry Volyntsev date: Mon May 08 22:03:32 2023 -0700 description: Shell: simplified input completion handler. Previously, the completion logic was split between njs_vm_completion() and njs_completion_generator() in shell. Now the completion part is done in njs_vm_completion(), as a result njs_completion_generator() is simplified. diffstat: src/njs_builtin.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/njs_shell.c | 88 +++---------------------------------------- 2 files changed, 110 insertions(+), 86 deletions(-) diffs (312 lines): diff -r 2467a70fd45a -r d610d744bbd2 src/njs_builtin.c --- a/src/njs_builtin.c Mon May 08 16:40:50 2023 -0700 +++ b/src/njs_builtin.c Mon May 08 22:03:32 2023 -0700 @@ -27,7 +27,10 @@ static njs_int_t njs_global_this_prop_ha njs_value_t *retval); static njs_arr_t *njs_vm_expression_completions(njs_vm_t *vm, njs_str_t *expression); -static njs_arr_t *njs_object_completions(njs_vm_t *vm, njs_value_t *object); +static njs_arr_t *njs_vm_global_var_completions(njs_vm_t *vm, + njs_str_t *expression); +static njs_arr_t *njs_object_completions(njs_vm_t *vm, njs_value_t *object, + njs_str_t *expression); static njs_int_t njs_env_hash_init(njs_vm_t *vm, njs_lvlhsh_t *hash, char **environment); @@ -544,15 +547,76 @@ njs_builtin_completions(njs_vm_t *vm) njs_arr_t * njs_vm_completions(njs_vm_t *vm, njs_str_t *expression) { + u_char *p, *end; + if (expression == NULL) { return njs_builtin_completions(vm); } + p = expression->start; + end = p + expression->length; + + while (p < end && *p != '.') { p++; } + + if (p == end) { + return njs_vm_global_var_completions(vm, expression); + } + return njs_vm_expression_completions(vm, expression); } static njs_arr_t * +njs_vm_global_var_completions(njs_vm_t *vm, njs_str_t *expression) +{ + njs_str_t *completion; + njs_arr_t *array; + njs_rbtree_t *variables; + njs_rbtree_node_t *node; + njs_variable_node_t *vnode; + const njs_lexer_entry_t *lex_entry; + + variables = (vm->global_scope != NULL) ? &vm->global_scope->variables + : NULL; + if (njs_slow_path(variables == NULL)) { + return NULL; + } + + array = njs_arr_create(vm->mem_pool, 8, sizeof(njs_str_t)); + if (njs_slow_path(array == NULL)) { + return NULL; + } + + node = njs_rbtree_min(variables); + + while (njs_rbtree_is_there_successor(variables, node)) { + vnode = (njs_variable_node_t *) node; + + node = njs_rbtree_node_successor(variables, node); + + lex_entry = njs_lexer_entry(vnode->key); + if (lex_entry == NULL) { + continue; + } + + if (lex_entry->name.length >= expression->length + && njs_strncmp(expression->start, lex_entry->name.start, + expression->length) == 0) + { + completion = njs_arr_add(array); + if (njs_slow_path(completion == NULL)) { + return NULL; + } + + *completion = lex_entry->name; + } + } + + return array; +} + + +static njs_arr_t * njs_vm_expression_completions(njs_vm_t *vm, njs_str_t *expression) { u_char *p, *end; @@ -611,6 +675,10 @@ njs_vm_expression_completions(njs_vm_t * ret = njs_lvlhsh_find(njs_object_hash(value), &lhq); if (njs_slow_path(ret != NJS_OK)) { + if (ret == NJS_DECLINED) { + break; + } + return NULL; } @@ -625,20 +693,31 @@ njs_vm_expression_completions(njs_vm_t * value = njs_prop_value(prop); } - return njs_object_completions(vm, value); + return njs_object_completions(vm, value, expression); } static njs_arr_t * -njs_object_completions(njs_vm_t *vm, njs_value_t *object) +njs_object_completions(njs_vm_t *vm, njs_value_t *object, njs_str_t *expression) { + u_char *prefix; double num; + size_t len; njs_arr_t *array; - njs_str_t *completion; + njs_str_t *completion, key; njs_uint_t n; njs_array_t *keys; njs_value_type_t type; + prefix = expression->start + expression->length; + + while (prefix > expression->start && *prefix != '.') { + prefix--; + } + + prefix++; + len = expression->length - (prefix - expression->start); + array = NULL; type = object->type; @@ -657,6 +736,14 @@ njs_object_completions(njs_vm_t *vm, njs } for (n = 0; n < keys->length; n++) { + njs_string_get(&keys->start[n], &key); + + if (len != 0 + && njs_strncmp(key.start, prefix, njs_min(len, key.length)) != 0) + { + continue; + } + num = njs_key_to_index(&keys->start[n]); if (!njs_key_is_integer_index(num, &keys->start[n])) { @@ -667,7 +754,18 @@ njs_object_completions(njs_vm_t *vm, njs goto done; } - njs_string_get(&keys->start[n], completion); + completion->length = (prefix - expression->start) + key.length + 1; + completion->start = njs_mp_alloc(vm->mem_pool, completion->length); + if (completion == NULL) { + njs_arr_destroy(array); + array = NULL; + goto done; + } + + njs_sprintf(completion->start, + completion->start + completion->length, + "%*s%V%Z", prefix - expression->start, + expression->start, &key); } } diff -r 2467a70fd45a -r d610d744bbd2 src/njs_shell.c --- a/src/njs_shell.c Mon May 08 16:40:50 2023 -0700 +++ b/src/njs_shell.c Mon May 08 22:03:32 2023 -0700 @@ -63,8 +63,7 @@ typedef struct { njs_rbtree_node_t *node; enum { - NJS_COMPLETION_VAR = 0, - NJS_COMPLETION_SUFFIX, + NJS_COMPLETION_SUFFIX = 0, NJS_COMPLETION_GLOBAL } phase; } njs_completion_t; @@ -1189,78 +1188,31 @@ njs_editline_init(void) static char * njs_completion_generator(const char *text, int state) { - char *completion; - size_t len; - njs_str_t expression, *suffix; - njs_vm_t *vm; - const char *p; - njs_rbtree_t *variables; - njs_completion_t *cmpl; - njs_variable_node_t *var_node; - const njs_lexer_entry_t *lex_entry; + njs_str_t expression, *suffix; + njs_vm_t *vm; + njs_completion_t *cmpl; vm = njs_console.vm; cmpl = &njs_console.completion; if (state == 0) { - cmpl->phase = 0; + cmpl->phase = NJS_COMPLETION_SUFFIX; cmpl->index = 0; cmpl->length = njs_strlen(text); cmpl->suffix_completions = NULL; - - if (vm->global_scope != NULL) { - cmpl->node = njs_rbtree_min(&vm->global_scope->variables); - } } next: switch (cmpl->phase) { - case NJS_COMPLETION_VAR: - variables = (vm->global_scope != NULL) ? &vm->global_scope->variables - : NULL; - - if (variables == NULL) { - njs_next_phase(cmpl); - } - - while (njs_rbtree_is_there_successor(variables, cmpl->node)) { - var_node = (njs_variable_node_t *) cmpl->node; - - lex_entry = njs_lexer_entry(var_node->key); - if (lex_entry == NULL) { - break; - } - - cmpl->node = njs_rbtree_node_successor(variables, cmpl->node); - - if (lex_entry->name.length >= cmpl->length - && njs_strncmp(text, lex_entry->name.start, cmpl->length) == 0) - { - return njs_editline(&lex_entry->name); - } - - } - - njs_next_phase(cmpl); - case NJS_COMPLETION_SUFFIX: if (cmpl->length == 0) { njs_next_phase(cmpl); } if (cmpl->suffix_completions == NULL) { - /* Getting the longest prefix before a '.' */ - - p = &text[cmpl->length - 1]; - while (p > text && *p != '.') { p--; } - - if (*p != '.') { - njs_next_phase(cmpl); - } - expression.start = (u_char *) text; - expression.length = p - text; + expression.length = cmpl->length; cmpl->suffix_completions = njs_vm_completions(vm, &expression); if (cmpl->suffix_completions == NULL) { @@ -1268,18 +1220,6 @@ next: } } - /* Getting the right-most suffix after a '.' */ - - len = 0; - p = &text[cmpl->length - 1]; - - while (p > text && *p != '.') { - p--; - len++; - } - - p++; - for ( ;; ) { if (cmpl->index >= cmpl->suffix_completions->items) { njs_next_phase(cmpl); @@ -1287,21 +1227,7 @@ next: suffix = njs_completion(cmpl->suffix_completions, cmpl->index++); - if (len != 0 && njs_strncmp(suffix->start, p, - njs_min(len, suffix->length)) != 0) - { - continue; - } - - len = suffix->length + (p - text) + 1; - completion = malloc(len); - if (completion == NULL) { - return NULL; - } - - njs_sprintf((u_char *) completion, (u_char *) completion + len, - "%*s%V%Z", p - text, text, suffix); - return completion; + return njs_editline(suffix); } case NJS_COMPLETION_GLOBAL: From xeioex at nginx.com Wed May 10 02:00:06 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 10 May 2023 02:00:06 +0000 Subject: [njs] Shell: CLIs is rewritten using public API. Message-ID: details: https://hg.nginx.org/njs/rev/fc8d1b125cef branches: changeset: 2111:fc8d1b125cef user: Dmitry Volyntsev date: Tue May 09 18:18:33 2023 -0700 description: Shell: CLIs is rewritten using public API. diffstat: auto/make | 8 +- external/njs_shell.c | 1591 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/njs.h | 6 +- src/njs_builtin.c | 2 +- src/njs_shell.c | 1578 ------------------------------------------------- src/njs_vm.c | 10 + src/njs_vm.h | 2 - 7 files changed, 1609 insertions(+), 1588 deletions(-) diffs (truncated from 3268 to 1000 lines): diff -r d610d744bbd2 -r fc8d1b125cef auto/make --- a/auto/make Mon May 08 22:03:32 2023 -0700 +++ b/auto/make Tue May 09 18:18:33 2023 -0700 @@ -104,10 +104,10 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/njs: \\ $NJS_BUILD_DIR/libnjs.a \\ - src/njs_shell.c + external/njs_shell.c \$(NJS_LINK) -o $NJS_BUILD_DIR/njs \$(NJS_CFLAGS) \\ $NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) -Injs \\ - src/njs_shell.c \\ + external/njs_shell.c \\ $NJS_BUILD_DIR/libnjs.a \\ $NJS_LD_OPT -lm $NJS_LIBS $NJS_LIB_AUX_LIBS $NJS_READLINE_LIB @@ -118,12 +118,12 @@ END cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/njs_process_script_fuzzer.o: \\ - src/njs_shell.c + external/njs_shell.c \$(NJS_CC) -c \$(CFLAGS) $NJS_LIB_AUX_CFLAGS \\ \$(NJS_LIB_INCS) -Injs \\ -DNJS_FUZZER_TARGET \\ -o $NJS_BUILD_DIR/njs_process_script_fuzzer.o \\ - src/njs_shell.c + external/njs_shell.c $NJS_BUILD_DIR/njs_process_script_fuzzer: \\ $NJS_BUILD_DIR/libnjs.a \\ diff -r d610d744bbd2 -r fc8d1b125cef external/njs_shell.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/external/njs_shell.c Tue May 09 18:18:33 2023 -0700 @@ -0,0 +1,1591 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) + +#include +#include +#include +#if (NJS_HAVE_EDITLINE) +#include +#elif (NJS_HAVE_EDIT_READLINE) +#include +#else +#include +#if (NJS_HAVE_GNU_READLINE) +#include +#endif +#endif + +#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; + uint8_t interactive; + uint8_t module; + uint8_t quiet; + uint8_t sandbox; + uint8_t safe; + uint8_t version; + uint8_t ast; + uint8_t unhandled_rejection; + uint8_t opcode_debug; + uint8_t generator_debug; + int exit_code; + int stack_size; + + char *file; + char *command; + size_t n_paths; + char **paths; + char **argv; + njs_uint_t argc; +} njs_opts_t; + + +typedef struct { + size_t index; + size_t length; + njs_arr_t *completions; + njs_arr_t *suffix_completions; + njs_rbtree_node_t *node; + + enum { + NJS_COMPLETION_SUFFIX = 0, + NJS_COMPLETION_GLOBAL + } phase; +} njs_completion_t; + + +typedef struct { + njs_vm_event_t vm_event; + njs_queue_link_t link; +} njs_ev_t; + + +typedef struct { + njs_opaque_value_t name; + uint64_t time; +} njs_timelabel_t; + + +typedef struct { + njs_vm_t *vm; + + njs_lvlhsh_t events; /* njs_ev_t * */ + njs_queue_t posted_events; + + njs_lvlhsh_t labels; /* njs_timelabel_t */ + + njs_completion_t completion; +} njs_console_t; + + +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_script(njs_vm_t *vm, void *runtime, + const njs_str_t *script); + +#ifndef NJS_FUZZER_TARGET + +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, + njs_vm_opt_t *vm_options); +static njs_int_t njs_editline_init(void); +static char *njs_completion_generator(const char *text, int state); +#endif + +#endif + +static njs_int_t njs_ext_console_log(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t indent, njs_value_t *retval); +static njs_int_t njs_ext_console_time(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_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 njs_timelabel_hash_test(njs_lvlhsh_query_t *lhq, void *data); + +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 njs_external_t njs_ext_console[] = { + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("dump"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_log, + .magic8 = 1, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("log"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_log, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "Console", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("time"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_time, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("timeEnd"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_time_end, + } + }, + +}; + + +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 const njs_lvlhsh_proto_t njs_timelabel_hash_proto njs_aligned(64) = { + NJS_LVLHSH_DEFAULT, + njs_timelabel_hash_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, +}; + + +njs_module_t njs_console_module = { + .name = njs_str("console"), + .init = njs_externals_init, +}; + + +static njs_module_t *njs_console_addon_modules[] = { + &njs_console_module, + NULL, +}; + + +static njs_int_t njs_console_proto_id; + + +static njs_console_t njs_console; + + +#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_memzero(&opts, sizeof(njs_opts_t)); + opts.interactive = 1; + + ret = njs_options_parse(&opts, argc, argv); + if (ret != NJS_OK) { + ret = (ret == NJS_DONE) ? NJS_OK : NJS_ERROR; + goto done; + } + + if (opts.version != 0) { + njs_printf("%s\n", NJS_VERSION); + ret = NJS_OK; + goto done; + } + + njs_mm_denormals(opts.denormals); + + njs_vm_opt_init(&vm_options); + + if (opts.file == NULL) { + opts.file = (opts.command == NULL) ? (char *) "shell" + : (char *) "string"; + } + + 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); + } + +done: + + njs_options_free(&opts); + + return (ret == NJS_OK) ? EXIT_SUCCESS : opts.exit_code; +} + + +static njs_int_t +njs_options_parse(njs_opts_t *opts, int argc, char **argv) +{ + char *p, **paths; + njs_int_t i, ret; + njs_uint_t n; + + static const char help[] = + "njs [options] [-c string | script.js | -] [script args]\n" + "\n" + "Interactive shell: " +#ifdef NJS_HAVE_READLINE + "enabled\n" +#else + "disabled\n" +#endif + "\n" + "Options:\n" + " -a print AST.\n" + " -c specify the command to execute.\n" + " -d print disassembled code.\n" + " -e set failure exit code.\n" + " -f disabled denormals mode.\n" +#ifdef NJS_DEBUG_GENERATOR + " -g enable generator debug.\n" +#endif + " -j set the maximum stack size in bytes.\n" +#ifdef NJS_DEBUG_OPCODE + " -o enable opcode debug.\n" +#endif + " -p set path prefix for modules.\n" + " -q disable interactive introduction prompt.\n" + " -r ignore unhandled promise rejection.\n" + " -s sandbox mode.\n" + " -t script|module source code type (script is default).\n" + " -v print njs version and exit.\n" + " -u disable \"unsafe\" mode.\n" + " script.js | - run code from a file or stdin.\n"; + + ret = NJS_DONE; + + opts->denormals = 1; + opts->exit_code = EXIT_FAILURE; + opts->unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW; + + p = getenv("NJS_EXIT_CODE"); + if (p != NULL) { + opts->exit_code = atoi(p); + } + + for (i = 1; i < argc; i++) { + + p = argv[i]; + + if (p[0] != '-' || (p[0] == '-' && p[1] == '\0')) { + opts->interactive = 0; + opts->file = argv[i]; + goto done; + } + + p++; + + switch (*p) { + case '?': + case 'h': + njs_printf("%*s", njs_length(help), help); + return ret; + + case 'a': + opts->ast = 1; + break; + + case 'c': + opts->interactive = 0; + + if (++i < argc) { + opts->command = argv[i]; + goto done; + } + + njs_stderror("option \"-c\" requires argument\n"); + return NJS_ERROR; + + case 'd': + opts->disassemble = 1; + break; + + case 'e': + if (++i < argc) { + opts->exit_code = atoi(argv[i]); + break; + } + + njs_stderror("option \"-e\" requires argument\n"); + return NJS_ERROR; + + case 'f': + +#if !(NJS_HAVE_DENORMALS_CONTROL) + njs_stderror("option \"-f\" is not supported\n"); + return NJS_ERROR; +#endif + + opts->denormals = 0; + break; + +#ifdef NJS_DEBUG_GENERATOR + case 'g': + opts->generator_debug = 1; + break; +#endif + case 'j': + if (++i < argc) { + opts->stack_size = atoi(argv[i]); + break; + } + + njs_stderror("option \"-j\" requires argument\n"); + return NJS_ERROR; + +#ifdef NJS_DEBUG_OPCODE + case 'o': + opts->opcode_debug = 1; + break; +#endif + + case 'p': + if (++i < argc) { + opts->n_paths++; + paths = realloc(opts->paths, opts->n_paths * sizeof(char *)); + if (paths == NULL) { + njs_stderror("failed to add path\n"); + return NJS_ERROR; + } + + opts->paths = paths; + opts->paths[opts->n_paths - 1] = argv[i]; + break; + } + + njs_stderror("option \"-p\" requires directory name\n"); + return NJS_ERROR; + + case 'q': + opts->quiet = 1; + break; + + case 'r': + opts->unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_IGNORE; + break; + + case 's': + opts->sandbox = 1; + break; + + case 't': + if (++i < argc) { + if (strcmp(argv[i], "module") == 0) { + opts->module = 1; + + } else if (strcmp(argv[i], "script") != 0) { + njs_stderror("option \"-t\" unexpected source type: %s\n", + argv[i]); + return NJS_ERROR; + } + + break; + } + + njs_stderror("option \"-t\" requires source type\n"); + return NJS_ERROR; + case 'v': + case 'V': + opts->version = 1; + break; + + case 'u': + opts->safe = 1; + break; + + default: + njs_stderror("Unknown argument: \"%s\" " + "try \"%s -h\" for available options\n", argv[i], + argv[0]); + return NJS_ERROR; + } + } + +done: + + opts->argc = njs_max(argc - i + 1, 2); + opts->argv = malloc(sizeof(char*) * opts->argc); + if (opts->argv == NULL) { + njs_stderror("failed to alloc argv\n"); + return NJS_ERROR; + } + + opts->argv[0] = argv[0]; + opts->argv[1] = (opts->file != NULL) ? opts->file : (char *) ""; + for (n = 2; n < opts->argc; n++) { + opts->argv[n] = argv[i + n - 1]; + } + + return NJS_OK; +} + + +static void +njs_options_free(njs_opts_t *opts) +{ + if (opts->paths != NULL) { + free(opts->paths); + } + + if (opts->argv != NULL) { + free(opts->argv); + } +} + + +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 + }; + + if (size == 0) { + return 0; + } + + 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); + + if (njs_fast_path(vm != NULL)) { + script.length = size; + script.start = (u_char *) data; + + (void) njs_process_script(vm, NULL, &script); + njs_vm_destroy(vm); + } + + return 0; +} + +#endif + +static njs_int_t +njs_console_init(njs_vm_t *vm, njs_console_t *console) +{ + console->vm = vm; + + njs_lvlhsh_init(&console->events); + njs_queue_init(&console->posted_events); + + njs_lvlhsh_init(&console->labels); + + console->completion.completions = njs_vm_completions(vm, NULL); + if (console->completion.completions == NULL) { + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_int_t +njs_externals_init(njs_vm_t *vm) +{ + njs_int_t ret; + njs_value_t *value; + njs_console_t *console; + njs_opaque_value_t method; + + static const njs_str_t console_name = njs_str("console"); + static const njs_str_t print_name = njs_str("print"); + static const njs_str_t console_log = njs_str("console.log"); + + console = njs_vm_options(vm)->external; + + njs_console_proto_id = njs_vm_external_prototype(vm, njs_ext_console, + njs_nitems(njs_ext_console)); + if (njs_slow_path(njs_console_proto_id < 0)) { + njs_stderror("failed to add \"console\" proto\n"); + return NJS_ERROR; + } + + value = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_opaque_value_t)); + if (njs_slow_path(value == NULL)) { + return NJS_ERROR; + } + + ret = njs_vm_external_create(vm, value, njs_console_proto_id, console, 0); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &console_name, value, 0); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_value(vm, &console_log, njs_value_arg(&method)); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &print_name, njs_value_arg(&method), 0); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_console_init(vm, console); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_vm_t * +njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options) +{ + u_char *p, *start; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t path; + njs_uint_t i; + + vm = njs_vm_create(vm_options); + if (vm == NULL) { + njs_stderror("failed to create vm\n"); + return NULL; + } + + for (i = 0; i < opts->n_paths; i++) { + path.start = (u_char *) opts->paths[i]; + path.length = njs_strlen(opts->paths[i]); + + ret = njs_vm_add_path(vm, &path); + if (ret != NJS_OK) { + njs_stderror("failed to add path\n"); + return NULL; + } + } + + start = (u_char *) getenv("NJS_PATH"); + if (start == NULL) { + return vm; + } + + for ( ;; ) { + p = njs_strchr(start, ':'); + + path.start = start; + path.length = (p != NULL) ? (size_t) (p - start) : njs_strlen(start); + + ret = njs_vm_add_path(vm, &path); + if (ret != NJS_OK) { + njs_stderror("failed to add path\n"); + return NULL; + } + + if (p == NULL) { + break; + } + + start = p + 1; + } + + return vm; +} + + +static void +njs_console_output(njs_vm_t *vm, njs_value_t *value, njs_int_t ret) +{ + njs_str_t out; + + if (ret == NJS_OK) { + if (njs_vm_value_dump(vm, &out, value, 0, 1) != NJS_OK) { + njs_stderror("Shell:failed to get retval from VM\n"); + return; + } + + if (njs_vm_options(vm)->interactive) { + njs_print(out.start, out.length); + njs_print("\n", 1); + } + + } else { + njs_vm_exception_string(vm, &out); + njs_stderror("Thrown:\n%V\n", &out); + } +} + + +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; + + if (runtime == NULL) { + njs_stderror("njs_process_events(): no runtime\n"); + return NJS_ERROR; + } + + console = runtime; + + events = &console->posted_events; + + for ( ;; ) { + link = njs_queue_first(events); + + if (link == njs_queue_tail(events)) { + break; + } + + ev = njs_queue_link_data(link, njs_ev_t, link); + + njs_queue_remove(&ev->link); + ev->link.prev = NULL; + ev->link.next = NULL; + + njs_vm_post_event(console->vm, ev->vm_event, NULL, 0); + } + + return NJS_OK; +} + + +static njs_int_t +njs_process_script(njs_vm_t *vm, void *runtime, const njs_str_t *script) +{ + u_char *start, *end; + njs_int_t ret; + njs_opaque_value_t retval; + + start = script->start; + end = start + script->length; + + ret = njs_vm_compile(vm, &start, end); + + if (ret == NJS_OK) { + if (start == end) { + ret = njs_vm_start(vm, njs_value_arg(&retval)); + + } else { + njs_vm_error(vm, "Extra characters at the end of the script"); From xeioex at nginx.com Wed May 10 02:00:08 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 10 May 2023 02:00:08 +0000 Subject: [njs] Shell: added $262 as external in CLI. Message-ID: details: https://hg.nginx.org/njs/rev/70e7701a4588 branches: changeset: 2112:70e7701a4588 user: Dmitry Volyntsev date: Tue May 09 18:58:52 2023 -0700 description: Shell: added $262 as external in CLI. diffstat: external/njs_shell.c | 60 +++++++++++++++++++++++++++++++++++------- src/njs_array_buffer.c | 25 +++++++++++++++++ src/njs_array_buffer.h | 2 + src/test/njs_externals_test.c | 29 +++----------------- 4 files changed, 82 insertions(+), 34 deletions(-) diffs (205 lines): diff -r fc8d1b125cef -r 70e7701a4588 external/njs_shell.c --- a/external/njs_shell.c Tue May 09 18:18:33 2023 -0700 +++ b/external/njs_shell.c Tue May 09 18:58:52 2023 -0700 @@ -145,6 +145,9 @@ static njs_int_t lvlhsh_key_test(njs_lvl static void *lvlhsh_pool_alloc(void *pool, size_t size); static void lvlhsh_pool_free(void *pool, void *p, size_t size); +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); + static njs_external_t njs_ext_console[] = { @@ -204,6 +207,30 @@ static njs_external_t njs_ext_console[] }; +static njs_external_t njs_ext_262[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "$262", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("detachArrayBuffer"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_array_buffer_detach, + } + }, + +}; + + static const njs_lvlhsh_proto_t lvlhsh_proto njs_aligned(64) = { NJS_LVLHSH_LARGE_SLAB, lvlhsh_key_test, @@ -779,12 +806,12 @@ njs_console_init(njs_vm_t *vm, njs_conso static njs_int_t njs_externals_init(njs_vm_t *vm) { - njs_int_t ret; - njs_value_t *value; + njs_int_t ret, proto_id; njs_console_t *console; - njs_opaque_value_t method; + njs_opaque_value_t value, method; static const njs_str_t console_name = njs_str("console"); + 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"); @@ -797,17 +824,13 @@ njs_externals_init(njs_vm_t *vm) return NJS_ERROR; } - value = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_opaque_value_t)); - if (njs_slow_path(value == NULL)) { - return NJS_ERROR; - } - - ret = njs_vm_external_create(vm, value, njs_console_proto_id, console, 0); + ret = njs_vm_external_create(vm, njs_value_arg(&value), + njs_console_proto_id, console, 0); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - ret = njs_vm_bind(vm, &console_name, value, 0); + ret = njs_vm_bind(vm, &console_name, njs_value_arg(&value), 0); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -827,6 +850,23 @@ njs_externals_init(njs_vm_t *vm) return NJS_ERROR; } + proto_id = njs_vm_external_prototype(vm, njs_ext_262, + njs_nitems(njs_ext_262)); + if (njs_slow_path(proto_id < 0)) { + njs_stderror("failed to add \"$262\" proto\n"); + return NJS_ERROR; + } + + ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &dollar_262, njs_value_arg(&value), 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + return NJS_OK; } diff -r fc8d1b125cef -r 70e7701a4588 src/njs_array_buffer.c --- a/src/njs_array_buffer.c Tue May 09 18:18:33 2023 -0700 +++ b/src/njs_array_buffer.c Tue May 09 18:58:52 2023 -0700 @@ -240,6 +240,31 @@ njs_array_buffer_prototype_slice(njs_vm_ } +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) +{ + njs_value_t *value; + njs_array_buffer_t *buffer; + + value = njs_arg(args, nargs, 1); + if (njs_slow_path(!njs_is_array_buffer(value))) { + njs_type_error(vm, "\"this\" is not an ArrayBuffer"); + return NJS_ERROR; + } + + buffer = njs_array_buffer(value); + buffer->u.data = NULL; + buffer->size = 0; + + njs_set_null(retval); + + return NJS_OK; +} + + + + static const njs_object_prop_t njs_array_buffer_prototype_properties[] = { NJS_DECLARE_PROP_HANDLER("constructor", diff -r fc8d1b125cef -r 70e7701a4588 src/njs_array_buffer.h --- a/src/njs_array_buffer.h Tue May 09 18:18:33 2023 -0700 +++ b/src/njs_array_buffer.h Tue May 09 18:58:52 2023 -0700 @@ -15,6 +15,8 @@ njs_array_buffer_t *njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, njs_bool_t zeroing); njs_int_t njs_array_buffer_writable(njs_vm_t *vm, njs_array_buffer_t *buffer); +NJS_EXPORT 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); njs_inline njs_array_buffer_t * njs_array_buffer_slice(njs_vm_t *vm, njs_array_buffer_t *this, int64_t start, diff -r fc8d1b125cef -r 70e7701a4588 src/test/njs_externals_test.c --- a/src/test/njs_externals_test.c Tue May 09 18:18:33 2023 -0700 +++ b/src/test/njs_externals_test.c Tue May 09 18:58:52 2023 -0700 @@ -26,6 +26,10 @@ typedef struct { } njs_unit_test_prop_t; +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); + + static njs_int_t njs_external_r_proto_id; @@ -548,29 +552,6 @@ njs_unit_test_constructor(njs_vm_t *vm, static njs_int_t -njs_262_detach_array_buffer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) -{ - njs_value_t *value; - njs_array_buffer_t *buffer; - - value = njs_arg(args, nargs, 1); - if (njs_slow_path(!njs_is_array_buffer(value))) { - njs_type_error(vm, "\"this\" is not an ArrayBuffer"); - return NJS_ERROR; - } - - buffer = njs_array_buffer(value); - buffer->u.data = NULL; - buffer->size = 0; - - njs_set_null(retval); - - return NJS_OK; -} - - -static njs_int_t njs_262_bytes_from_array_like(njs_vm_t *vm, njs_value_t *value, njs_value_t *retval) { @@ -749,7 +730,7 @@ static njs_external_t njs_unit_test_262 .configurable = 1, .enumerable = 1, .u.method = { - .native = njs_262_detach_array_buffer, + .native = njs_array_buffer_detach, } }, From xeioex at nginx.com Wed May 10 07:00:35 2023 From: xeioex at nginx.com (=?iso-8859-1?q?Dmitry_Volyntsev?=) Date: Wed, 10 May 2023 00:00:35 -0700 Subject: [PATCH] Configure: introduced --with-*-module-opt option In-Reply-To: References: Message-ID: <85643f113750f1c49a7e.1683702035@xeioex-VirtualBox> # HG changeset patch # User Dmitry Volyntsev # Date 1683701820 25200 # Tue May 09 23:57:00 2023 -0700 # Node ID 85643f113750f1c49a7ea3dea568da4e3137ec4b # Parent b71e69247483631bd8fc79a47cc32b762625b1fb Configure: introduced --with-*-module-opt option. --with-*-module-opt provides a generic way to pass arbitrary configure options to an nginx addon configure script. For example when --with-foo-module-opt=value is provided the following variable is defined: $NGX_FOO_MODULE_OPT. While $NGX_FOO_MODULE_OPT can be provided as an environment variable it is inconsistent with other similar options like --with-pcre-opt or --with-openssl-opt. Also the introduced option enforces a unified named convention for opt variables for nginx addons. diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -343,6 +343,11 @@ use the \"--with-mail_ssl_module\" optio --add-module=*) NGX_ADDONS="$NGX_ADDONS $value" ;; --add-dynamic-module=*) DYNAMIC_ADDONS="$DYNAMIC_ADDONS $value" ;; + --with-*-module-opt=*) + mod=`echo "$option" | sed -e 's/--with-//' -e 's/-module-opt.*//' \ + | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` + eval "NGX_${mod}_MODULE_OPT"="$value" + ;; --with-compat) NGX_COMPAT=YES ;; From arut at nginx.com Wed May 10 11:49:06 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 10 May 2023 15:49:06 +0400 Subject: [PATCH] QUIC: fixed OpenSSL compat layer with OpenSSL master branch In-Reply-To: References: Message-ID: <20230510114906.3jjghapkqd3t54gk@N00W24XTQX> On Tue, Apr 11, 2023 at 01:39:23AM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1681162552 -14400 > # Tue Apr 11 01:35:52 2023 +0400 > # Branch quic > # Node ID e058f1f9d40f185e19098d65a47e3be128d4cb46 > # Parent 9ea62b6250f225578f703da5e230853a7a84df7d > QUIC: fixed OpenSSL compat layer with OpenSSL master branch. > > The layer is enabled as a fallback if the QUIC support is configured and the > BoringSSL API wasn't detected, or when using the --with-openssl option, also > compatible with QuicTLS and LibreSSL. For the latter, the layer is assumed > to be present if QUIC was requested, so it needs to be undefined to prevent > QUIC API redefinition as appropriate. > > A previously used approach to test the TLSEXT_TYPE_quic_transport_parameters > macro doesn't work with OpenSSL 3.2 master branch where this macro appeared > with incompatible QUIC API. To fix the build there, the test is revised to > pass only for QuicTLS and LibreSSL. > > diff --git a/src/event/quic/ngx_event_quic_openssl_compat.h b/src/event/quic/ngx_event_quic_openssl_compat.h > --- a/src/event/quic/ngx_event_quic_openssl_compat.h > +++ b/src/event/quic/ngx_event_quic_openssl_compat.h > @@ -7,7 +7,8 @@ > #ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ > #define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ > > -#ifdef TLSEXT_TYPE_quic_transport_parameters > +#if defined SSL_R_MISSING_QUIC_TRANSPORT_PARAMETERS_EXTENSION \ > + || defined LIBRESSL_VERSION_NUMBER > #undef NGX_QUIC_OPENSSL_COMPAT > #else > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks ok From mdounin at mdounin.ru Wed May 10 16:46:11 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Wed, 10 May 2023 19:46:11 +0300 Subject: [PATCH] Configure: introduced --with-*-module-opt option In-Reply-To: <85643f113750f1c49a7e.1683702035@xeioex-VirtualBox> References: <85643f113750f1c49a7e.1683702035@xeioex-VirtualBox> Message-ID: Hello! On Wed, May 10, 2023 at 12:00:35AM -0700, Dmitry Volyntsev wrote: > # HG changeset patch > # User Dmitry Volyntsev > # Date 1683701820 25200 > # Tue May 09 23:57:00 2023 -0700 > # Node ID 85643f113750f1c49a7ea3dea568da4e3137ec4b > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > Configure: introduced --with-*-module-opt option. > > --with-*-module-opt provides a generic way to pass arbitrary configure > options to an nginx addon configure script. > > For example when --with-foo-module-opt=value is provided the following > variable is defined: $NGX_FOO_MODULE_OPT. > > While $NGX_FOO_MODULE_OPT can be provided as an environment variable it > is inconsistent with other similar options like --with-pcre-opt or > --with-openssl-opt. Also the introduced option enforces a unified named > convention for opt variables for nginx addons. > > diff --git a/auto/options b/auto/options > --- a/auto/options > +++ b/auto/options > @@ -343,6 +343,11 @@ use the \"--with-mail_ssl_module\" optio > > --add-module=*) NGX_ADDONS="$NGX_ADDONS $value" ;; > --add-dynamic-module=*) DYNAMIC_ADDONS="$DYNAMIC_ADDONS $value" ;; > + --with-*-module-opt=*) > + mod=`echo "$option" | sed -e 's/--with-//' -e 's/-module-opt.*//' \ > + | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` > + eval "NGX_${mod}_MODULE_OPT"="$value" > + ;; > > --with-compat) NGX_COMPAT=YES ;; > I don't think I like this approach. It's hardly different from the environment variable approach suggested previously, and provides no syntax checking and/or error reporting. -- Maxim Dounin http://mdounin.ru/ From artem.konev at nginx.com Wed May 10 17:12:45 2023 From: artem.konev at nginx.com (=?iso-8859-1?q?Artem_Konev?=) Date: Wed, 10 May 2023 18:12:45 +0100 Subject: [PATCH] Added info about the Unit 1.30.0 release Message-ID: xml/index.xml | 10 ++++++++++ 1 files changed, 10 insertions(+), 0 deletions(-) # HG changeset patch # User Artem Konev # Date 1683738668 -3600 # Wed May 10 18:11:08 2023 +0100 # Node ID f4e032c1111807aae791fed8336ab4e220d223d8 # Parent f474b151cf25e2c7edb92b326df975c10ba01e00 Added info about the Unit 1.30.0 release. diff --git a/xml/index.xml b/xml/index.xml --- a/xml/index.xml +++ b/xml/index.xml @@ -7,6 +7,16 @@ + + +unit-1.30.0 version has been +released, +featuring URI rewrite, improved logging, and +njs +module support. + + + nginx-1.24.0 From maxim at nginx.com Wed May 10 17:15:48 2023 From: maxim at nginx.com (Maxim Konovalov) Date: Wed, 10 May 2023 10:15:48 -0700 Subject: [PATCH] Added info about the Unit 1.30.0 release In-Reply-To: References: Message-ID: On 10.05.2023 10:12, Artem Konev wrote: > xml/index.xml | 10 ++++++++++ > 1 files changed, 10 insertions(+), 0 deletions(-) > > > # HG changeset patch > # User Artem Konev > # Date 1683738668 -3600 > # Wed May 10 18:11:08 2023 +0100 > # Node ID f4e032c1111807aae791fed8336ab4e220d223d8 > # Parent f474b151cf25e2c7edb92b326df975c10ba01e00 > Added info about the Unit 1.30.0 release. > > diff --git a/xml/index.xml b/xml/index.xml > --- a/xml/index.xml > +++ b/xml/index.xml > @@ -7,6 +7,16 @@ > > > > + > + > +unit-1.30.0 version has been > +released, > +featuring URI rewrite, improved logging, and > +njs > +module support. > + > + Looks good. -- Maxim Konovalov From arut at nginx.com Wed May 10 17:46:43 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 10 May 2023 21:46:43 +0400 Subject: [PATCH 3 of 3] QUIC: keep stream sockaddr and addr_text constant In-Reply-To: <43f0ceffa227a33e5c5c.1683030855@arut-laptop> References: <43f0ceffa227a33e5c5c.1683030855@arut-laptop> Message-ID: <20230510174643.myyxjnrhw2oz5bjb@N00W24XTQX> Hi, On Tue, May 02, 2023 at 04:34:15PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1682679819 -14400 > # Fri Apr 28 15:03:39 2023 +0400 > # Branch quic > # Node ID 43f0ceffa227a33e5c5ceb35b77f9a1f86dd2481 > # Parent cdc41ec778ffae822fefce639e67f2f57e3667f0 > QUIC: keep stream sockaddr and addr_text constant. > > HTTP and Stream variables $remote_addr and $binary_remote_addr rely on > constant client address, particularly because they are cacheable. > However, QUIC client may migrate to a new address. While there's no perfect > way to handle this, the proposed solution is to copy client address to QUIC > stream at stream creation. Previously, the address was only referenced, which > could result in changing it while stream was active, which in turn would lead > to broken cached variables values, since address length is cached as well. While testing this, it was found that $remote_addr truncation happens at the QUIC level since the addr_text string is copied by value and retains the old length after migration. The new commit log: QUIC: keep stream sockaddr and addr_text constant. HTTP and Stream variables $remote_addr and $binary_remote_addr rely on constant client address, particularly because they are cacheable. However, QUIC client may migrate to a new address. While there's no perfect way to handle this, the proposed solution is to copy client address to QUIC stream at stream creation. The change also fixes truncated $remote_addr if migration happened while the stream was active. The reason is addr_text string was copied to stream by value. [..] -- Roman Arutyunyan From thresh at nginx.com Thu May 11 02:00:57 2023 From: thresh at nginx.com (=?iso-8859-1?q?Konstantin_Pavlov?=) Date: Wed, 10 May 2023 19:00:57 -0700 Subject: [PATCH] Linux packages: added Ubuntu 23.04 "lunar" Message-ID: # HG changeset patch # User Konstantin Pavlov # Date 1683770379 25200 # Wed May 10 18:59:39 2023 -0700 # Node ID e53e7065223e4ede0fdcb4872ae3be39197d8c04 # Parent 2baa5da77e6933c9945834fdeabd71e0ed6c0ff2 Linux packages: added Ubuntu 23.04 "lunar". diff -r 2baa5da77e69 -r e53e7065223e xml/en/linux_packages.xml --- a/xml/en/linux_packages.xml Mon Mar 27 16:25:44 2023 -0700 +++ b/xml/en/linux_packages.xml Wed May 10 18:59:39 2023 -0700 @@ -7,7 +7,7 @@
+ rev="85">
@@ -92,6 +92,11 @@ versions: x86_64, aarch64/arm64 + +23.04 “lunar” +x86_64, aarch64/arm64 + + diff -r 2baa5da77e69 -r e53e7065223e xml/ru/linux_packages.xml --- a/xml/ru/linux_packages.xml Mon Mar 27 16:25:44 2023 -0700 +++ b/xml/ru/linux_packages.xml Wed May 10 18:59:39 2023 -0700 @@ -7,7 +7,7 @@
+ rev="85">
@@ -92,6 +92,11 @@ x86_64, aarch64/arm64 + +23.04 “lunar” +x86_64, aarch64/arm64 + + From maxim at nginx.com Thu May 11 02:55:29 2023 From: maxim at nginx.com (Maxim Konovalov) Date: Wed, 10 May 2023 19:55:29 -0700 Subject: [PATCH] Linux packages: added Ubuntu 23.04 "lunar" In-Reply-To: References: Message-ID: On 10.05.2023 19:00, Konstantin Pavlov wrote: > # HG changeset patch > # User Konstantin Pavlov > # Date 1683770379 25200 > # Wed May 10 18:59:39 2023 -0700 > # Node ID e53e7065223e4ede0fdcb4872ae3be39197d8c04 > # Parent 2baa5da77e6933c9945834fdeabd71e0ed6c0ff2 > Linux packages: added Ubuntu 23.04 "lunar". > [...] Looks good. -- Maxim Konovalov From xeioex at nginx.com Thu May 11 04:26:12 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 11 May 2023 04:26:12 +0000 Subject: [njs] Fetch: removed special treatment of forbidden headers. Message-ID: details: https://hg.nginx.org/njs/rev/4aed0532158c branches: changeset: 2113:4aed0532158c user: Dmitry Volyntsev date: Tue May 09 22:09:13 2023 -0700 description: Fetch: removed special treatment of forbidden headers. In c43261bad627 (0.7.10), a notion of forbidden headers was introduced in accordance in Fetch API. In the API the Forbidden headers are not allowed to be changed from JavaScript code for security reasons. The restriction is removed because there are use cases where Host (which is considered forbidden) is different from the host address in URL and JavaScript code is expected to be a trusted source (unlike a browser context). This closes #638 issue on Github. diffstat: nginx/ngx_js_fetch.c | 65 ---------------------------------------------------- 1 files changed, 0 insertions(+), 65 deletions(-) diffs (82 lines): diff -r 70e7701a4588 -r 4aed0532158c nginx/ngx_js_fetch.c --- a/nginx/ngx_js_fetch.c Tue May 09 18:58:52 2023 -0700 +++ b/nginx/ngx_js_fetch.c Tue May 09 22:09:13 2023 -0700 @@ -2184,43 +2184,6 @@ ngx_js_headers_append(njs_vm_t *vm, ngx_ ngx_uint_t i; ngx_js_tb_elt_t *h, **ph; ngx_list_part_t *part; - const njs_str_t *f; - - static const njs_str_t forbidded_request[] = { - njs_str("Accept-Charset"), - njs_str("Accept-Encoding"), - njs_str("Access-Control-Request-Headers"), - njs_str("Access-Control-Request-Method"), - njs_str("Connection"), - njs_str("Content-Length"), - njs_str("Cookie"), - njs_str("Date"), - njs_str("DNT"), - njs_str("Expect"), - njs_str("Host"), - njs_str("Keep-Alive"), - njs_str("Origin"), - njs_str("Referer"), - njs_str("Set-Cookie"), - njs_str("TE"), - njs_str("Trailer"), - njs_str("Transfer-Encoding"), - njs_str("Upgrade"), - njs_str("Via"), - njs_null_str, - }; - - static const njs_str_t forbidded_response[] = { - njs_str("Set-Cookie"), - njs_str("Set-Cookie2"), - njs_null_str, - }; - - static const njs_str_t forbidded_request_prefix[] = { - njs_str("proxy-"), - njs_str("sec-"), - njs_null_str, - }; ngx_js_http_trim(&value, &vlen, 0); @@ -2253,34 +2216,6 @@ ngx_js_headers_append(njs_vm_t *vm, ngx_ return NJS_ERROR; } - if (headers->guard == GUARD_REQUEST) { - for (f = &forbidded_request[0]; f->length != 0; f++) { - if (len == f->length - && (njs_strncasecmp(name, f->start, len) == 0)) - { - return NJS_OK; - } - } - - for (f = &forbidded_request_prefix[0]; f->length != 0; f++) { - if (len >= f->length - && (njs_strncasecmp(name, f->start, f->length) == 0)) - { - return NJS_OK; - } - } - } - - if (headers->guard == GUARD_RESPONSE) { - for (f = &forbidded_response[0]; f->length != 0; f++) { - if (len == f->length - && (njs_strncasecmp(name, f->start, len) == 0)) - { - return NJS_OK; - } - } - } - ph = NULL; part = &headers->header_list.part; h = part->elts; From xeioex at nginx.com Thu May 11 04:26:14 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 11 May 2023 04:26:14 +0000 Subject: [njs] Fetch: insuring Host header is always the first header. Message-ID: details: https://hg.nginx.org/njs/rev/89c821242caf branches: changeset: 2114:89c821242caf user: Dmitry Volyntsev date: Tue May 09 22:09:13 2023 -0700 description: Fetch: insuring Host header is always the first header. diffstat: nginx/ngx_js_fetch.c | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 45 insertions(+), 4 deletions(-) diffs (79 lines): diff -r 4aed0532158c -r 89c821242caf nginx/ngx_js_fetch.c --- a/nginx/ngx_js_fetch.c Tue May 09 22:09:13 2023 -0700 +++ b/nginx/ngx_js_fetch.c Tue May 09 22:09:13 2023 -0700 @@ -658,6 +658,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value njs_int_t ret; ngx_url_t u; ngx_uint_t i; + njs_bool_t has_host; ngx_pool_t *pool; njs_value_t *init, *value; ngx_js_http_t *http; @@ -746,10 +747,42 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value njs_chb_append(&http->chain, u.uri.data, u.uri.len); njs_chb_append_literal(&http->chain, " HTTP/1.1" CRLF); - njs_chb_append_literal(&http->chain, "Host: "); - njs_chb_append(&http->chain, u.host.data, u.host.len); - njs_chb_append_literal(&http->chain, CRLF); - njs_chb_append_literal(&http->chain, "Connection: close" CRLF); + has_host = 0; + part = &request.headers.header_list.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash == 0) { + continue; + } + + if (h[i].key.len == 4 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Host", 4) == 0) + { + has_host = 1; + njs_chb_append_literal(&http->chain, "Host: "); + njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); + njs_chb_append_literal(&http->chain, CRLF); + break; + } + } + + if (!has_host) { + njs_chb_append_literal(&http->chain, "Host: "); + njs_chb_append(&http->chain, u.host.data, u.host.len); + njs_chb_append_literal(&http->chain, CRLF); + } part = &request.headers.header_list.part; h = part->elts; @@ -770,12 +803,20 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value continue; } + if (h[i].key.len == 4 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Host", 4) == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); njs_chb_append_literal(&http->chain, CRLF); } + njs_chb_append_literal(&http->chain, "Connection: close" CRLF); + #if (NGX_SSL) http->tls_name.data = u.host.data; http->tls_name.len = u.host.len; From xeioex at nginx.com Thu May 11 04:26:16 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 11 May 2023 04:26:16 +0000 Subject: [njs] Fixed memory allocation failure introduced in fc8d1b125cef. Message-ID: details: https://hg.nginx.org/njs/rev/a140e71b0fbf branches: changeset: 2115:a140e71b0fbf user: Dmitry Volyntsev date: Wed May 10 20:50:53 2023 -0700 description: Fixed memory allocation failure introduced in fc8d1b125cef. Found by Coverity (CID 1529969). diffstat: src/njs_builtin.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 89c821242caf -r a140e71b0fbf src/njs_builtin.c --- a/src/njs_builtin.c Tue May 09 22:09:13 2023 -0700 +++ b/src/njs_builtin.c Wed May 10 20:50:53 2023 -0700 @@ -756,7 +756,7 @@ njs_object_completions(njs_vm_t *vm, njs completion->length = (prefix - expression->start) + key.length + 1; completion->start = njs_mp_alloc(vm->mem_pool, completion->length); - if (completion == NULL) { + if (njs_slow_path(completion->start == NULL)) { njs_arr_destroy(array); array = NULL; goto done; From arut at nginx.com Thu May 11 06:26:32 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 11 May 2023 10:26:32 +0400 Subject: [PATCH 0 of 2] QUIC datagram size updates Message-ID: - The first patch brings down max input datagram size from 65535 to 65527. - The second patch eliminates "quic_mtu" directive, which currently sets max_udp_payload_size transport parameter value. According to RFC 9000, this value is not related to MTU, but is effectively the max size of input buffer. From arut at nginx.com Thu May 11 06:26:33 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 11 May 2023 10:26:33 +0400 Subject: [PATCH 1 of 2] QUIC: resized input datagram buffer from 65535 to 65527 In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1683784174 -14400 # Thu May 11 09:49:34 2023 +0400 # Branch quic # Node ID d2cc7adb261edc92988411ac7e0c8059419c201d # Parent d565cf69ff5d82d76011fdd8af03ae42b2cb145b QUIC: resized input datagram buffer from 65535 to 65527. The value of 65527 is the maximum permitted UDP payload size. diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c --- a/src/event/quic/ngx_event_quic_udp.c +++ b/src/event/quic/ngx_event_quic_udp.c @@ -34,7 +34,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) ngx_event_conf_t *ecf; ngx_connection_t *c, *lc; ngx_quic_socket_t *qsock; - static u_char buffer[65535]; + static u_char buffer[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; #if (NGX_HAVE_ADDRINFO_CMSG) u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; From arut at nginx.com Thu May 11 06:26:34 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 11 May 2023 10:26:34 +0400 Subject: [PATCH 2 of 2] QUIC: removed "quic_mtu" directive In-Reply-To: References: Message-ID: <5db9c42c3d4bf862642b.1683786394@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1683783928 -14400 # Thu May 11 09:45:28 2023 +0400 # Branch quic # Node ID 5db9c42c3d4bf862642b85d8f508257a1ad9c694 # Parent d2cc7adb261edc92988411ac7e0c8059419c201d QUIC: removed "quic_mtu" directive. The directive used to set the value of the "max_udp_payload_size" transport parameter. According to RFC 9000, Section 18.2, the value specifies the size of buffer for reading incoming datagrams: This limit does act as an additional constraint on datagram size in the same way as the path MTU, but it is a property of the endpoint and not the path; see Section 14. It is expected that this is the space an endpoint dedicates to holding incoming packets. Current QUIC implementation uses the maximum possible buffer size (65527) for reading datagrams. diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -69,7 +69,6 @@ typedef struct { ngx_flag_t disable_active_migration; ngx_msec_t timeout; ngx_str_t host_key; - size_t mtu; size_t stream_buffer_size; ngx_uint_t max_concurrent_streams_bidi; ngx_uint_t max_concurrent_streams_uni; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1987,7 +1987,7 @@ ngx_quic_init_transport_params(ngx_quic_ tp->max_idle_timeout = qcf->timeout; - tp->max_udp_payload_size = qcf->mtu; + tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; nstreams = qcf->max_concurrent_streams_bidi + qcf->max_concurrent_streams_uni; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -16,8 +16,6 @@ static ngx_int_t ngx_http_v3_add_variabl static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_http_quic_mtu(ngx_conf_t *cf, void *post, - void *data); static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); @@ -26,10 +24,6 @@ static char *ngx_http_v3_merge_loc_conf( static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_conf_post_t ngx_http_quic_mtu_post = - { ngx_http_quic_mtu }; - - static ngx_command_t ngx_http_v3_commands[] = { { ngx_string("http3"), @@ -95,13 +89,6 @@ static ngx_command_t ngx_http_v3_comman offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), NULL }, - { ngx_string("quic_mtu"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.mtu), - &ngx_http_quic_mtu_post }, - { ngx_string("quic_host_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_quic_host_key, @@ -240,7 +227,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; - h3scf->quic.mtu = NGX_CONF_UNSET_SIZE; h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; @@ -277,9 +263,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c conf->max_blocked_streams = conf->max_concurrent_streams; - ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - ngx_conf_merge_size_value(conf->quic.stream_buffer_size, prev->quic.stream_buffer_size, 65536); @@ -335,26 +318,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c static char * -ngx_http_quic_mtu(ngx_conf_t *cf, void *post, void *data) -{ - size_t *sp = data; - - if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_mtu\" must be between %d and %d", - NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_v3_srv_conf_t *h3scf = conf; diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -16,12 +16,9 @@ static ngx_int_t ngx_stream_quic_add_var static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data); static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_conf_post_t ngx_stream_quic_mtu_post = - { ngx_stream_quic_mtu }; static ngx_command_t ngx_stream_quic_commands[] = { @@ -32,13 +29,6 @@ static ngx_command_t ngx_stream_quic_co offsetof(ngx_quic_conf_t, timeout), NULL }, - { ngx_string("quic_mtu"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, mtu), - &ngx_stream_quic_mtu_post }, - { ngx_string("quic_stream_buffer_size"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -175,7 +165,6 @@ ngx_stream_quic_create_srv_conf(ngx_conf */ conf->timeout = NGX_CONF_UNSET_MSEC; - conf->mtu = NGX_CONF_UNSET_SIZE; conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; @@ -199,9 +188,6 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); - ngx_conf_merge_size_value(conf->mtu, prev->mtu, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - ngx_conf_merge_size_value(conf->stream_buffer_size, prev->stream_buffer_size, 65536); @@ -260,26 +246,6 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_ static char * -ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data) -{ - size_t *sp = data; - - if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_mtu\" must be between %d and %d", - NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_quic_conf_t *qcf = conf; From arut at nginx.com Thu May 11 06:40:00 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Thu, 11 May 2023 10:40:00 +0400 Subject: [PATCH 2 of 2] QUIC: removed "quic_mtu" directive In-Reply-To: <5db9c42c3d4bf862642b.1683786394@arut-laptop> References: <5db9c42c3d4bf862642b.1683786394@arut-laptop> Message-ID: <20230511064000.d46pqu4utabxzefb@N00W24XTQX> Hi, On Thu, May 11, 2023 at 10:26:34AM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1683783928 -14400 > # Thu May 11 09:45:28 2023 +0400 > # Branch quic > # Node ID 5db9c42c3d4bf862642b85d8f508257a1ad9c694 > # Parent d2cc7adb261edc92988411ac7e0c8059419c201d > QUIC: removed "quic_mtu" directive. > > The directive used to set the value of the "max_udp_payload_size" transport > parameter. According to RFC 9000, Section 18.2, the value specifies the size > of buffer for reading incoming datagrams: > > This limit does act as an additional constraint on datagram size in > the same way as the path MTU, but it is a property of the endpoint > and not the path; see Section 14. It is expected that this is the > space an endpoint dedicates to holding incoming packets. > > Current QUIC implementation uses the maximum possible buffer size (65527) for > reading datagrams. [..] Plus README update: diff --git a/README b/README --- a/README +++ b/README @@ -123,10 +123,6 @@ 3. Configuration quic_gso on; - To limit maximum UDP payload size on receive path: - - quic_mtu ; - To set host key for various tokens: quic_host_key ; @@ -209,14 +205,6 @@ 4. Directives Optimized sending is only supported on Linux featuring UDP_SEGMENT. - Syntax: quic_mtu size; - Default: quic_mtu 65527; - Context: http | stream, server - - Sets the QUIC max_udp_payload_size transport parameter value. - This is the maximum UDP payload that we are willing to receive. - - Syntax: quic_host_key file; Default: - Context: http | stream, server -- Roman Arutyunyan From arut at nginx.com Thu May 11 09:23:13 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 11 May 2023 13:23:13 +0400 Subject: [PATCH] HTTP/3: removed "http3" parameter of "listen" directive Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1683796930 -14400 # Thu May 11 13:22:10 2023 +0400 # Branch quic # Node ID f721a6689bd0a7278f362e67f323a5087919c0ac # Parent 0d89203a863a34227d3c9e0ddcf07a670c0e78fc HTTP/3: removed "http3" parameter of "listen" directive. The parameter has been deprecated since c851a2ed5ce8. diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -477,7 +477,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; - } else if (h3scf->enable || hc->addr_conf->http3) { + } else if (h3scf->enable) { srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1242,7 +1242,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, n ngx_uint_t http2; #endif #if (NGX_HTTP_V3) - ngx_uint_t http3; ngx_uint_t quic; #endif @@ -1287,7 +1286,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, n protocols_prev |= addr[i].opt.http2 << 2; #endif #if (NGX_HTTP_V3) - http3 = lsopt->http3 || addr[i].opt.http3; quic = lsopt->quic || addr[i].opt.quic; #endif @@ -1378,7 +1376,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, n addr[i].opt.http2 = http2; #endif #if (NGX_HTTP_V3) - addr[i].opt.http3 = http3; addr[i].opt.quic = quic; #endif @@ -1929,7 +1926,6 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h addrs[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - addrs[i].conf.http3 = addr[i].opt.http3; addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1998,7 +1994,6 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_ addrs6[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - addrs6[i].conf.http3 = addr[i].opt.http3; addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4186,23 +4186,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx #endif } - if (ngx_strcmp(value[n].data, "http3") == 0) { -#if (NGX_HTTP_V3) - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "the \"http3\" parameter is deprecated, " - "use \"quic\" parameter instead"); - lsopt.quic = 1; - lsopt.http3 = 1; - lsopt.type = SOCK_DGRAM; - continue; -#else - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the \"http3\" parameter requires " - "ngx_http_v3_module"); - return NGX_CONF_ERROR; -#endif - } - if (ngx_strcmp(value[n].data, "quic") == 0) { #if (NGX_HTTP_V3) lsopt.quic = 1; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -75,7 +75,6 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; - unsigned http3:1; unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; @@ -240,7 +239,6 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; - unsigned http3:1; unsigned quic:1; unsigned proxy_protocol:1; }; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1014,14 +1014,12 @@ ngx_http_v3_process_request_header(ngx_h h3c = ngx_http_v3_get_session(c); h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - if (!r->http_connection->addr_conf->http3) { - if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client attempted to request the server name " - "for which the negotiated protocol is disabled"); - ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); - return NGX_ERROR; - } + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; } if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { From pluknet at nginx.com Thu May 11 10:26:41 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 11 May 2023 14:26:41 +0400 Subject: [PATCH 3 of 3] QUIC: keep stream sockaddr and addr_text constant In-Reply-To: <20230510174643.myyxjnrhw2oz5bjb@N00W24XTQX> References: <43f0ceffa227a33e5c5c.1683030855@arut-laptop> <20230510174643.myyxjnrhw2oz5bjb@N00W24XTQX> Message-ID: > On 10 May 2023, at 21:46, Roman Arutyunyan wrote: > > Hi, > > On Tue, May 02, 2023 at 04:34:15PM +0400, Roman Arutyunyan wrote: >> # HG changeset patch >> # User Roman Arutyunyan >> # Date 1682679819 -14400 >> # Fri Apr 28 15:03:39 2023 +0400 >> # Branch quic >> # Node ID 43f0ceffa227a33e5c5ceb35b77f9a1f86dd2481 >> # Parent cdc41ec778ffae822fefce639e67f2f57e3667f0 >> QUIC: keep stream sockaddr and addr_text constant. >> >> HTTP and Stream variables $remote_addr and $binary_remote_addr rely on >> constant client address, particularly because they are cacheable. >> However, QUIC client may migrate to a new address. While there's no perfect >> way to handle this, the proposed solution is to copy client address to QUIC >> stream at stream creation. Previously, the address was only referenced, which >> could result in changing it while stream was active, which in turn would lead >> to broken cached variables values, since address length is cached as well. > > While testing this, it was found that $remote_addr truncation happens at the > QUIC level since the addr_text string is copied by value and retains the old > length after migration. The new commit log: > > QUIC: keep stream sockaddr and addr_text constant. > > HTTP and Stream variables $remote_addr and $binary_remote_addr rely on > constant client address, particularly because they are cacheable. > However, QUIC client may migrate to a new address. While there's no perfect > way to handle this, the proposed solution is to copy client address to QUIC > stream at stream creation. > > The change also fixes truncated $remote_addr if migration happened while the > stream was active. The reason is addr_text string was copied to stream by > value. > > [..] > All series looks good for me. For the record, reproduced with the following tests I intend to push later. # HG changeset patch # User Sergey Kandaurov # Date 1683800560 -14400 # Thu May 11 14:22:40 2023 +0400 # Branch quic # Node ID 9bd3e671cdf9f6f4883a77368458721dc2d2b5ac # Parent 4eb8f6d9bd13a36cb46da6caebf1a2f1a6710f2d Tests: basic QUIC migration tests. diff --git a/lib/Test/Nginx/HTTP3.pm b/lib/Test/Nginx/HTTP3.pm --- a/lib/Test/Nginx/HTTP3.pm +++ b/lib/Test/Nginx/HTTP3.pm @@ -92,6 +92,7 @@ sub init { $self->{dcid} = Crypt::PRNG::random_bytes(18); $self->{salt} = "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" . "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; + $self->{ncid} = []; $self->{early_data} = $early_data; $self->retry(); @@ -277,6 +278,12 @@ sub path_response { $self->{socket}->syswrite($self->encrypt_aead($frame, 3)); } +sub ping { + my ($self) = @_; + my $frame = "\x01\x00\x00\x00"; + $self->{socket}->syswrite($self->encrypt_aead($frame, 3)); +} + ############################################################################### # HTTP/3 routines @@ -1481,6 +1488,11 @@ sub handle_frames { ]); } + @frames = grep { $_->{type} eq 'NCID' } @$frames; + while (my $frame = shift @frames) { + push @{$self->{ncid}}, $frame; + } + my $ack = $self->{ack}[$level]; # stop tracking acknowledged ACK ranges diff --git a/quic_migration.t b/quic_migration.t new file mode 100644 --- /dev/null +++ b/quic_migration.t @@ -0,0 +1,134 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for quic migration. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP3; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require Crypt::Misc; die if $Crypt::Misc::VERSION < 0.067; }; +plan(skip_all => 'CryptX version >= 0.067 required') if $@; + +plan(skip_all => '127.0.0.20 local address required') + unless defined IO::Socket::INET->new( LocalAddr => '127.0.0.20' ); + +my $t = Test::Nginx->new()->has(qw/http http_v3/) + ->has_daemon('openssl')->plan(2); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + ssl_certificate_key localhost.key; + ssl_certificate localhost.crt; + ssl_protocols TLSv1.3; + + server { + listen 127.0.0.1:%%PORT_8980_UDP%% quic; + server_name localhost; + + location / { + add_header X-IP $remote_addr; + } + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('index.html', ''); +$t->run(); + +############################################################################### + +# test that $remote_addr is not truncated after migration (ticket #2488), +# to test, we migrate to another address large enough in text representation, +# then send a request on the new path + +my $s = Test::Nginx::HTTP3->new(); +$s->new_connection_id(1, 0, "foobar1", "foobarbazbazzzzz"); + +$s->{socket} = IO::Socket::INET->new( + Proto => "udp", + LocalAddr => '127.0.0.20', + PeerAddr => '127.0.0.1:' . port(8980), +); +$s->{scid} = "foobar1"; +$s->{dcid} = $s->{ncid}[0]{cid}; +$s->ping(); + +my $frames = $s->read(all => [{ type => 'PATH_CHALLENGE' }]); +my ($frame) = grep { $_->{type} eq "PATH_CHALLENGE" } @$frames; +$s->path_response($frame->{data}); + +$frames = $s->read(all => [{ sid => $s->new_stream(), fin => 1 }]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{'x-ip'}, '127.0.0.20', 'remote addr after migration'); + +# test that $remote_addr is not truncated while in the process of migration; +# the same but migration occurs on receiving a request stream itself, +# which is the first non-probing frame on the new path; +# previously this led to $remote_addr truncation in the following order: +# - stream held original sockaddr/addr_text references on stream creation +# - values were rewritten as part of handling connection migration +# - stream was handled referencing rewritten values, with old local lengths +# now sockaddr and addr_text are expected to keep copies on stream creation + +$s = Test::Nginx::HTTP3->new(); +$s->new_connection_id(1, 0, "foobar1", "foobarbazbazzzzz"); + +$s->{socket} = IO::Socket::INET->new( + Proto => "udp", + LocalAddr => '127.0.0.20', + PeerAddr => '127.0.0.1:' . port(8980), +); +$s->{scid} = "foobar1"; +$s->{dcid} = $s->{ncid}[0]{cid}; + +$frames = $s->read(all => [{ sid => $s->new_stream(), fin => 1 }]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{'x-ip'}, '127.0.0.1', 'remote addr on migration'); + +############################################################################### -- Sergey Kandaurov From artem.konev at nginx.com Thu May 11 11:16:57 2023 From: artem.konev at nginx.com (=?iso-8859-1?q?Artem_Konev?=) Date: Thu, 11 May 2023 12:16:57 +0100 Subject: [PATCH] Added direct link to release announcement for Unit 1.30.0 Message-ID: <60fdc933eb443fcf7056.1683803817@GP194LPYCG> xml/index.xml | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) # HG changeset patch # User Artem Konev # Date 1683803670 -3600 # Thu May 11 12:14:30 2023 +0100 # Node ID 60fdc933eb443fcf7056a47c0264676bf81516f2 # Parent fed0092ee55482c0bfcb7d0f5688d1779db0a28a Added direct link to release announcement for Unit 1.30.0. diff --git a/xml/index.xml b/xml/index.xml --- a/xml/index.xml +++ b/xml/index.xml @@ -10,7 +10,7 @@ unit-1.30.0 version has been -released, +released, featuring URI rewrite, improved logging, and njs module support. From yar at nginx.com Thu May 11 11:19:02 2023 From: yar at nginx.com (Yaroslav Zhuravlev) Date: Thu, 11 May 2023 12:19:02 +0100 Subject: [PATCH] Added direct link to release announcement for Unit 1.30.0 In-Reply-To: <60fdc933eb443fcf7056.1683803817@GP194LPYCG> References: <60fdc933eb443fcf7056.1683803817@GP194LPYCG> Message-ID: <6A2B19EA-E789-4729-8407-378BBEFE140D@nginx.com> Looks good > On 11 May 2023, at 12:16, Artem Konev wrote: > > xml/index.xml | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > > # HG changeset patch > # User Artem Konev > # Date 1683803670 -3600 > # Thu May 11 12:14:30 2023 +0100 > # Node ID 60fdc933eb443fcf7056a47c0264676bf81516f2 > # Parent fed0092ee55482c0bfcb7d0f5688d1779db0a28a > Added direct link to release announcement for Unit 1.30.0. > > diff --git a/xml/index.xml b/xml/index.xml > --- a/xml/index.xml > +++ b/xml/index.xml > @@ -10,7 +10,7 @@ > > > unit-1.30.0 version has been > -released, > +released, > featuring URI rewrite, improved logging, and > njs > module support. > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel From pluknet at nginx.com Thu May 11 11:31:25 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 11 May 2023 15:31:25 +0400 Subject: [PATCH] Tests: HTTP/2 tests with error_page and return In-Reply-To: References: <90aaa942972884dcd67b.1682952385@enoparse.local> Message-ID: > On 3 May 2023, at 01:45, Maxim Dounin wrote: > > Hello! > > On Tue, May 02, 2023 at 05:10:55PM +0400, Sergey Kandaurov wrote: > >>> On 2 May 2023, at 00:59, Maxim Dounin wrote: >>> >>> On Mon, May 01, 2023 at 06:46:25PM +0400, Sergey Kandaurov wrote: >>> >>>> # HG changeset patch >>>> # User Sergey Kandaurov >>>> # Date 1682952238 -14400 >>>> # Mon May 01 18:43:58 2023 +0400 >>>> # Node ID 90aaa942972884dcd67b6744fde39a154fec5d13 >>>> # Parent 36a4563f7f005184547575f5ac4f22ef53a59c72 >>>> Tests: HTTP/2 tests with error_page and return. >>>> >>>> diff --git a/h2_error_page.t b/h2_error_page.t >>>> new file mode 100644 >>>> --- /dev/null >>>> +++ b/h2_error_page.t >>>> @@ -0,0 +1,88 @@ >>>> +#!/usr/bin/perl >>>> + >>>> +# (C) Sergey Kandaurov >>>> +# (C) Nginx, Inc. >>>> + >>>> +# Tests for HTTP/2 protocol with error_page directive. >>>> + >>>> +############################################################################### >>>> + >>>> +use warnings; >>>> +use strict; >>>> + >>>> +use Test::More; >>>> + >>>> +BEGIN { use FindBin; chdir($FindBin::Bin); } >>>> + >>>> +use lib 'lib'; >>>> +use Test::Nginx; >>>> +use Test::Nginx::HTTP2; >>>> + >>>> +############################################################################### >>>> + >>>> +select STDERR; $| = 1; >>>> +select STDOUT; $| = 1; >>>> + >>>> +my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(2) >>>> + ->write_file_expand('nginx.conf', <<'EOF'); >>>> + >>>> +%%TEST_GLOBALS%% >>>> + >>>> +daemon off; >>>> + >>>> +events { >>>> +} >>>> + >>>> +http { >>>> + %%TEST_GLOBALS_HTTP%% >>>> + >>>> + server { >>>> + listen 127.0.0.1:8080 http2; >>>> + server_name localhost; >>>> + >>>> + lingering_close off; >>>> + >>>> + error_page 400 = /close; >>>> + >>>> + location / { } >>>> + >>>> + location /close { >>>> + return 444; >>>> + } >>>> + } >>>> +} >>>> + >>>> +EOF >>>> + >>>> +$t->run(); >>>> + >>>> +############################################################################### >>>> + >>>> +my ($sid, $frames, $frame); >>>> + >>>> +# tests for socket leak with "return 444" in error_page >>>> + >>>> +# ticket #274 >>>> + >>>> +my $s1 = Test::Nginx::HTTP2->new(); >>>> +$sid = $s1->new_stream({ headers => [ >>>> + { name => ':method', value => 'GET' }, >>>> + { name => ':path', value => '/' }, >>>> + { name => ':authority', value => 'localhost' }]}); >>>> +$frames = $s1->read(all => [{ type => 'RST_STREAM' }]); >>>> + >>>> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; >>>> +is($frame->{sid}, $sid, 'error 400 return 444 - missing header'); >>> >>> This clearly needs details about the header being missed, as well >>> as expected and observed behaviour, not just the ticket number. >> >> The description is provided in associated commit logs, a proper >> source to seek for details, tagged with appropriate ticket numbers. >> A brief description what happens here is given above. > > Even assuming commits are readily available (they are not in > most cases), commit logs and even the code changes are not enough > to see what actually missed here: that is, it worth to mention > lack of mandatory ":scheme" pseudo-header. Sure, I don't mind to add extra comments if that provides further explanation. > > Also, it might be important to mention why the test is expected to > fail without the fix (and if it's expected to fail), and why it > succeeds with the fix. Note that the tickets in question are > about connection being left open, and not about RST_STREAM not > being sent. Well, the connection is expected to be kept. Unlike in HTTP/1.x, HTTP/2 is a multiplexing protocol. That means an error condition in an individual stream doesn't lead to the entire connection close. Rather, malformed requests lead to a stream error indicated with RST_STREAM. Let's look at RFC 7540: 5.4. Error Handling HTTP/2 framing permits two classes of error: o An error condition that renders the entire connection unusable is a connection error. o An error in an individual stream is a stream error. So either GOAWAY or RST_STREAM is expected. 8.1.2.6. Malformed Requests and Responses Malformed requests or responses that are detected MUST be treated as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. This is exactly what happens in nginx: it sends RST_STREAM as an indication of immediate termination of a stream. > > Note well that RST_STREAM is not something one might expect with > "return 444;", and rather an implementation detail. See above, thanks. > A better > approach might be to check instead for a connection being closed > and not timed out on the client side (this might complicate things > though, and might not worth the effort). The problem is that stream termination didn't happen. As such, stream count never fall below 1, which prevented to install a keepalive timer. So, when it's time to shutdown a worker process, there are no timers left in connection. > >> If you insist, we can add further details inline: >> >> # a socket leak observed on missing ngx_http_run_posted_requests() such as >> # in ngx_http_v2_run_request(), e.g. if ngx_http_v2_construct_request_line() >> # failed due to missing mandatory ":scheme" pseudo-header (ticket #274) > > These are indeed mostly implementation details. A better comment > might be: > > # make sure there is no socket leak when the request is rejected > # due to missing mandatory ":scheme" pseudo-header and "return 444;" > # is used in error_page 400 (ticket #274) > >>> >>>> + >>>> +# ticket #2455 >>>> + >>>> +my $s2 = Test::Nginx::HTTP2->new(); >>>> +$sid = $s2->new_stream({ method => 'foo' }); >>>> +$frames = $s2->read(all => [{ type => 'RST_STREAM' }]); >>>> + >>>> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; >>>> +is($frame->{sid}, $sid, 'error 400 return 444 - invalid header'); >>> >>> Same here. >> >> # another case with missing ngx_http_run_posted_requests() is in >> # ngx_http_v2_state_process_header() error handling (ticket #2455), >> # can be triggered with invalid pseudo-header > > Same here: > > # make sure there is no socket leak when the request is rejected > # due to invalid method with lower-case letters and "return 444;" > # is used in error_page 400 (ticket #2455) > Thank you, much appreciated. Applied to the changeset. >> >>> >>>> + >>>> +$t->stop(); > > This also might worth an explicit comment. Something like > > # while keeping $s1 and $s2, stop nginx; this should result in > # "open socket ... left in connection ..." alerts if any of these > # sockets is still open > > should be good enough. > Thanks for the review, pushed. -- Sergey Kandaurov From pluknet at nginx.com Thu May 11 11:48:48 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 11 May 2023 15:48:48 +0400 Subject: [PATCH 04 of 11] Tests: fixed server_tokens tests for build names with spaces In-Reply-To: <605cab711606724e5879.1681702288@vm-bsd.mdounin.ru> References: <605cab711606724e5879.1681702288@vm-bsd.mdounin.ru> Message-ID: > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > # HG changeset patch > # User Maxim Dounin > # Date 1681702253 -10800 > # Mon Apr 17 06:30:53 2023 +0300 > # Node ID 605cab711606724e5879e8a81d5d21797e5ddcfb > # Parent f704912ed09f3494a815709710c3744b0adca50b > Tests: fixed server_tokens tests for build names with spaces. > > Build names can contain spaces, and previously used pattern, "--build=(\S+)", > failed to properly match such build names. Instead, now we simply test > that some build name is provided in the Server header. Further, the in the Server header and error pages (but I won't insist, existing wording looks clear enough) > $t->has_module() method is now used to check if a build name is set > instead of directly testing the $t->{_configure_args} internal field. > > diff --git a/h2_server_tokens.t b/h2_server_tokens.t > --- a/h2_server_tokens.t > +++ b/h2_server_tokens.t > @@ -106,7 +106,7 @@ like(header_server('/on/200'), qr/^$re$/ > like(header_server('/on/404'), qr/^$re$/, 'http2 tokens on 404'); > like(body('/on/404'), $re, 'http2 tokens on 404 body'); > > -$re = qr/$re \Q($1)\E/ if $t->{_configure_args} =~ /--build=(\S+)/; > +$re = qr/$re \(.*\)/ if $t->has_module('--build='); > > like(header_server('/b/200'), qr/^$re$/, 'http2 tokens build 200'); > like(header_server('/b/404'), qr/^$re$/, 'http2 tokens build 404'); > diff --git a/server_tokens.t b/server_tokens.t > --- a/server_tokens.t > +++ b/server_tokens.t > @@ -105,7 +105,7 @@ like(http_get_server('/on/200'), $re, 't > like(http_get_server('/on/404'), $re, 'tokens on 404'); > like(http_body('/on/404'), $re, 'tokens on 404 body'); > > -$re = qr/$re \Q($1)\E/ if $t->{_configure_args} =~ /--build=(\S+)/; > +$re = qr/$re \(.*\)/ if $t->has_module('--build='); > > like(http_get_server('/b/200'), $re, 'tokens build 200'); > like(http_get_server('/b/404'), $re, 'tokens build 404'); Looks good. -- Sergey Kandaurov From pluknet at nginx.com Thu May 11 14:27:12 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 11 May 2023 18:27:12 +0400 Subject: [PATCH 10 of 11] Tests: reworked http SSL tests to use IO::Socket::SSL In-Reply-To: <2aaba5bbc0366bffe1f4.1681702294@vm-bsd.mdounin.ru> References: <2aaba5bbc0366bffe1f4.1681702294@vm-bsd.mdounin.ru> Message-ID: <3EA12B7C-3EA3-4BFB-8C2A-9C13238BFD08@nginx.com> > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > # HG changeset patch > # User Maxim Dounin > # Date 1681702264 -10800 > # Mon Apr 17 06:31:04 2023 +0300 > # Node ID 2aaba5bbc0366bffe1f468105b1185cd48efbc93 > # Parent 90913cb36b512c45cd9a171cbb4320b12ff24b48 > Tests: reworked http SSL tests to use IO::Socket::SSL. > > Relevant infrastructure is provided in Test::Nginx http() functions. > This also ensures that SSL handshake and various read and write operations > are guarded with timeouts. > > The ssl_sni_reneg.t test uses IO::Socket::SSL::_get_ssl_object() to access > the Net::SSLeay object directly and trigger renegotation. While > not exactly correct, this seems to be good enough for tests. > > Similarly, IO::Socket::SSL::_get_ssl_object() is used in ssl_stapling.t, > since SSL_ocsp_staple_callback is called with the socket instead of the > Net::SSLeay object. > > Similarly, IO::Socket::SSL::_get_ssl_object() is used in ssl_verify_client.t, > since there seems to be no way to obtain CA list with IO::Socket::SSL. This is one of the reasons why it was written in Net::SSLeay. The intention was to avoid using undocumented _get_ssl_object. The resulting code throughout the series is sometimes hard to read now. Still, if you believe it is better to rewrite it in IO::Socket:SSL, I'm ok with it. You can treat this as a positive review. See minor comments below and in other patches. > > Notable change to http() request interface is that http_end() now closes > the socket. This is to make sure that SSL connections are properly > closed and SSL sessions are not removed from the IO::Socket::SSL session > cache. This affected access_log.t, which was modified accordingly. > > diff --git a/access_log.t b/access_log.t > --- a/access_log.t > +++ b/access_log.t > @@ -161,11 +161,11 @@ http_get('/varlog?logname=0'); > http_get('/varlog?logname=filename'); > > my $s = http('', start => 1); > -http_get('/addr', socket => $s); > my $addr = $s->sockhost(); > my $port = $s->sockport(); > my $saddr = $s->peerhost(); > my $sport = $s->peerport(); > +http_get('/addr', socket => $s); > > http_get('/binary'); > > diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm > --- a/lib/Test/Nginx.pm > +++ b/lib/Test/Nginx.pm > @@ -838,13 +838,15 @@ sub http($;%) { > my $s = http_start($request, %extra); > > return $s if $extra{start} or !defined $s; > - return http_end($s); > + return http_end($s, %extra); > } > > sub http_start($;%) { > my ($request, %extra) = @_; > my $s; > > + my $port = $extra{SSL} ? 8443 : 8080; > + > eval { > local $SIG{ALRM} = sub { die "timeout\n" }; > local $SIG{PIPE} = sub { die "sigpipe\n" }; > @@ -852,10 +854,25 @@ sub http_start($;%) { > > $s = $extra{socket} || IO::Socket::INET->new( > Proto => 'tcp', > - PeerAddr => '127.0.0.1:' . port(8080) > + PeerAddr => '127.0.0.1:' . port($port), > + %extra > ) > or die "Can't connect to nginx: $!\n"; > > + if ($extra{SSL}) { > + require IO::Socket::SSL; > + IO::Socket::SSL->start_SSL( > + $s, > + SSL_verify_mode => > + IO::Socket::SSL::SSL_VERIFY_NONE(), > + %extra > + ) > + or die $IO::Socket::SSL::SSL_ERROR . "\n"; > + > + log_in("ssl cipher: " . $s->get_cipher()); > + log_in("ssl cert: " . $s->peer_certificate('issuer')); > + } > + > log_out($request); > $s->print($request); > > @@ -879,7 +896,7 @@ sub http_start($;%) { > } > > sub http_end($;%) { > - my ($s) = @_; > + my ($s, %extra) = @_; extra doesn't seem to be used > my $reply; > > eval { > @@ -890,6 +907,8 @@ sub http_end($;%) { > local $/; > $reply = $s->getline(); > > + $s->close(); > + > alarm(0); > }; > alarm(0); > diff --git a/ssl_certificate.t b/ssl_certificate.t > --- a/ssl_certificate.t > +++ b/ssl_certificate.t > @@ -17,29 +17,15 @@ use Socket qw/ CRLF /; > BEGIN { use FindBin; chdir($FindBin::Bin); } > > use lib 'lib'; > -use Test::Nginx; > +use Test::Nginx qw/ :DEFAULT http_end /; > > ############################################################################### > > select STDERR; $| = 1; > select STDOUT; $| = 1; > > -eval { > - require Net::SSLeay; > - Net::SSLeay::load_error_strings(); > - Net::SSLeay::SSLeay_add_ssl_algorithms(); > - Net::SSLeay::randomize(); > -}; > -plan(skip_all => 'Net::SSLeay not installed') if $@; > - > -eval { > - my $ctx = Net::SSLeay::CTX_new() or die; > - my $ssl = Net::SSLeay::new($ctx) or die; > - Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die; > -}; > -plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_ssl geo openssl:1.0.2/) > +my $t = Test::Nginx->new() > + ->has(qw/http http_ssl geo openssl:1.0.2 socket_ssl_sni/) > ->has_daemon('openssl'); > > $t->write_file_expand('nginx.conf', <<'EOF'); > @@ -67,6 +53,7 @@ http { > } > > add_header X-SSL $ssl_server_name:$ssl_session_reused; > + add_header X-SSL-Protocol $ssl_protocol; > ssl_session_cache shared:SSL:1m; > ssl_session_tickets on; > > @@ -177,60 +164,63 @@ like(get('password', 8083), qr/password/ > > # session reuse > > -my ($s, $ssl) = get('default', 8080); > -my $ses = Net::SSLeay::get_session($ssl); > - > -like(get('default', 8080, $ses), qr/default:r/, 'session reused'); > +my $s = session('default', 8080); > > TODO: { > -# ticket key name mismatch prevents session resumption > +local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay' > + if $Net::SSLeay::VERSION < 1.88 && test_tls13(); > +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' > + if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); > + > +like(get('default', 8080, $s), qr/default:r/, 'session reused'); > + > +TODO: { > +# automatic ticket ticket key name mismatch prevents session resumption "ticket" repetition (also a similar comment in stream_ssl_certificate.t isn't touched) > local $TODO = 'not yet' unless $t->has_version('1.23.2'); > > -like(get('default', 8081, $ses), qr/default:r/, 'session id context match'); > +like(get('default', 8081, $s), qr/default:r/, 'session id context match'); > > } > +} > > -like(get('default', 8082, $ses), qr/default:\./, 'session id context distinct'); > +like(get('default', 8082, $s), qr/default:\./, 'session id context distinct'); > > # errors > > -Net::SSLeay::ERR_clear_error(); > -get_ssl_socket('nx', 8084); > -ok(Net::SSLeay::ERR_peek_error(), 'no certificate'); > +ok(!get('nx', 8084), 'no certificate'); IIRC this was written so to ensure it is an SSL layer error and not some abrupt termination caused by unrelated reason. I don't object to simplify it though, if you think it is better. [..] -- Sergey Kandaurov From pluknet at nginx.com Thu May 11 14:39:32 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 11 May 2023 18:39:32 +0400 Subject: [PATCH 06 of 11] Tests: reworked mail SSL tests to use IO::Socket::SSL In-Reply-To: <20d603cd3cbeab891271.1681702290@vm-bsd.mdounin.ru> References: <20d603cd3cbeab891271.1681702290@vm-bsd.mdounin.ru> Message-ID: <7B021E84-DF45-4E33-B45B-F85EED930FB8@nginx.com> > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > # HG changeset patch > # User Maxim Dounin > # Date 1681702257 -10800 > # Mon Apr 17 06:30:57 2023 +0300 > # Node ID 20d603cd3cbeab89127108fe9cb6dffd0e9469e8 > # Parent a8e22a3212da945e9060d4233905eb6de1399d34 > Tests: reworked mail SSL tests to use IO::Socket::SSL. > > Relevant infrastructure is provided in Test::Nginx::IMAP (and also POP3 > and SMTP for completeness). This also ensures that SSL handshake and > various read operations are guarded with timeouts. > [..] > diff --git a/mail_ssl_conf_command.t b/mail_ssl_conf_command.t > --- a/mail_ssl_conf_command.t > +++ b/mail_ssl_conf_command.t > @@ -16,6 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin > > use lib 'lib'; > use Test::Nginx; > +use Test::Nginx::IMAP; > > ############################################################################### > > @@ -24,15 +25,8 @@ select STDOUT; $| = 1; > > local $SIG{PIPE} = 'IGNORE'; > > -eval { > - require Net::SSLeay; > - Net::SSLeay::load_error_strings(); > - Net::SSLeay::SSLeay_add_ssl_algorithms(); > - Net::SSLeay::randomize(); > -}; > -plan(skip_all => 'Net::SSLeay not installed') if $@; > - > -my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap openssl:1.0.2/) > +my $t = Test::Nginx->new() > + ->has(qw/mail mail_ssl imap openssl:1.0.2 socket_ssl_reused/) > ->has_daemon('openssl'); > > plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); > @@ -50,7 +44,7 @@ mail { > auth_http http://127.0.0.1:8080; # unused > > server { > - listen 127.0.0.1:8443 ssl; > + listen 127.0.0.1:8993 ssl; Please avoid using an upper half of 8000 .. 8999 range for TCP tests. The reason is a deficiency in automatic port selection used for parallel testing, see Test::Nginx::port(). Currently, ports are selected in the reversed order for TCP and UDP for a reason: another socket type for the same port is used as a lock. But that gives a race if you try to concurrently select the same port for both TCP and UDP. Currently, this is worked around by splitting the range: bottom half is used for TCP, upper half is used for UDP. Luckily, nginx retries bind/listen on NGX_EADDRINUSE up to 5 times, but this doesn't always work. [..] -- Sergey Kandaurov From pluknet at nginx.com Thu May 11 14:52:30 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 11 May 2023 18:52:30 +0400 Subject: [PATCH 0 of 2] QUIC datagram size updates In-Reply-To: References: Message-ID: <12F1B3C4-393F-436D-A257-62972D355044@nginx.com> > On 11 May 2023, at 10:26, Roman Arutyunyan wrote: > > - The first patch brings down max input datagram size from 65535 to 65527. > > - The second patch eliminates "quic_mtu" directive, which currently sets > max_udp_payload_size transport parameter value. According to RFC 9000, this > value is not related to MTU, but is effectively the max size of input buffer. Looks good. -- Sergey Kandaurov From maxim at nginx.com Thu May 11 15:21:30 2023 From: maxim at nginx.com (Maxim Konovalov) Date: Thu, 11 May 2023 08:21:30 -0700 Subject: [PATCH 2 of 2] QUIC: removed "quic_mtu" directive In-Reply-To: <20230511064000.d46pqu4utabxzefb@N00W24XTQX> References: <5db9c42c3d4bf862642b.1683786394@arut-laptop> <20230511064000.d46pqu4utabxzefb@N00W24XTQX> Message-ID: <727968d7-3295-652e-b1f4-c92040bbb3dc@nginx.com> On 10.05.2023 23:40, Roman Arutyunyan wrote: > Hi, > > On Thu, May 11, 2023 at 10:26:34AM +0400, Roman Arutyunyan wrote: >> # HG changeset patch >> # User Roman Arutyunyan >> # Date 1683783928 -14400 >> # Thu May 11 09:45:28 2023 +0400 >> # Branch quic >> # Node ID 5db9c42c3d4bf862642b85d8f508257a1ad9c694 >> # Parent d2cc7adb261edc92988411ac7e0c8059419c201d >> QUIC: removed "quic_mtu" directive. >> >> The directive used to set the value of the "max_udp_payload_size" transport >> parameter. According to RFC 9000, Section 18.2, the value specifies the size >> of buffer for reading incoming datagrams: >> >> This limit does act as an additional constraint on datagram size in >> the same way as the path MTU, but it is a property of the endpoint >> and not the path; see Section 14. It is expected that this is the >> space an endpoint dedicates to holding incoming packets. >> >> Current QUIC implementation uses the maximum possible buffer size (65527) for >> reading datagrams. > > [..] > > Plus README update: > [...] + nginx.org docs needs update too. -- Maxim Konovalov From arut at nginx.com Thu May 11 15:40:09 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Thu, 11 May 2023 19:40:09 +0400 Subject: [PATCH 3 of 3] QUIC: keep stream sockaddr and addr_text constant In-Reply-To: References: <43f0ceffa227a33e5c5c.1683030855@arut-laptop> <20230510174643.myyxjnrhw2oz5bjb@N00W24XTQX> Message-ID: <20230511154009.sexhyzxukukiqky7@N00W24XTQX> Hi, On Thu, May 11, 2023 at 02:26:41PM +0400, Sergey Kandaurov wrote: > > > On 10 May 2023, at 21:46, Roman Arutyunyan wrote: > > > > Hi, > > > > On Tue, May 02, 2023 at 04:34:15PM +0400, Roman Arutyunyan wrote: > >> # HG changeset patch > >> # User Roman Arutyunyan > >> # Date 1682679819 -14400 > >> # Fri Apr 28 15:03:39 2023 +0400 > >> # Branch quic > >> # Node ID 43f0ceffa227a33e5c5ceb35b77f9a1f86dd2481 > >> # Parent cdc41ec778ffae822fefce639e67f2f57e3667f0 > >> QUIC: keep stream sockaddr and addr_text constant. > >> > >> HTTP and Stream variables $remote_addr and $binary_remote_addr rely on > >> constant client address, particularly because they are cacheable. > >> However, QUIC client may migrate to a new address. While there's no perfect > >> way to handle this, the proposed solution is to copy client address to QUIC > >> stream at stream creation. Previously, the address was only referenced, which > >> could result in changing it while stream was active, which in turn would lead > >> to broken cached variables values, since address length is cached as well. > > > > While testing this, it was found that $remote_addr truncation happens at the > > QUIC level since the addr_text string is copied by value and retains the old > > length after migration. The new commit log: > > > > QUIC: keep stream sockaddr and addr_text constant. > > > > HTTP and Stream variables $remote_addr and $binary_remote_addr rely on > > constant client address, particularly because they are cacheable. > > However, QUIC client may migrate to a new address. While there's no perfect > > way to handle this, the proposed solution is to copy client address to QUIC > > stream at stream creation. > > > > The change also fixes truncated $remote_addr if migration happened while the > > stream was active. The reason is addr_text string was copied to stream by > > value. > > > > [..] > > > > All series looks good for me. Discussed patch #3 again. Decided to add the following, just to be on the safe side: diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -716,6 +716,7 @@ ngx_quic_create_stream(ngx_connection_t } else { addr_text.len = 0; + addr_text.data = NULL; } reusable = c->reusable; [..] -- Roman Arutyunyan From mdounin at mdounin.ru Thu May 11 22:30:41 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Fri, 12 May 2023 01:30:41 +0300 Subject: [PATCH] QUIC: style Message-ID: <7d67fe09bcad6bc7e375.1683844241@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1683820081 -10800 # Thu May 11 18:48:01 2023 +0300 # Branch quic # Node ID 7d67fe09bcad6bc7e375c4d889787b8b57017856 # Parent b9230e37b8a19e594d201dcc1e6dd8fc666feaf0 QUIC: style. diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -85,11 +85,11 @@ #define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session #define ngx_http_v3_get_module_loc_conf(c, module) \ - ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ module) #define ngx_http_v3_get_module_srv_conf(c, module) \ - ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ module) #define ngx_http_v3_finalize_connection(c, code, reason) \ 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 @@ -868,7 +868,8 @@ ngx_http_v3_parse_field_l(ngx_connection case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l"); if (b->pos == b->last) { return NGX_AGAIN; From xeioex at nginx.com Thu May 11 23:54:25 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 11 May 2023 23:54:25 +0000 Subject: [njs] Modules: added options to disable parts dependant on 3rd party libs. Message-ID: details: https://hg.nginx.org/njs/rev/1f84f3c34bb0 branches: changeset: 2116:1f84f3c34bb0 user: Dmitry Volyntsev date: Wed May 10 22:36:53 2023 -0700 description: Modules: added options to disable parts dependant on 3rd party libs. The following environment variables are added: NJS_OPENSSL, NJS_LIBXSLT, NJS_ZLIB. When a variable evaluates to "NO" the part of the module related to the corresponsing library is disabled. For example to disable libxslt related code: NJS_LIBXSLT=NO ./configure .. --add-module=/path/to/njs/module diffstat: nginx/config | 43 +++++++++++++++++++++++++++++++++++++------ nginx/ngx_js.c | 6 ++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diffs (89 lines): diff -r a140e71b0fbf -r 1f84f3c34bb0 nginx/config --- a/nginx/config Wed May 10 20:50:53 2023 -0700 +++ b/nginx/config Wed May 10 22:36:53 2023 -0700 @@ -1,13 +1,42 @@ ngx_addon_name="ngx_js_module" +NJS_OPENSSL=${NJS_OPENSSL:-YES} +NJS_LIBXSLT=${NJS_LIBXSLT:-YES} +NJS_ZLIB=${NJS_ZLIB:-YES} + NJS_DEPS="$ngx_addon_dir/ngx_js.h \ $ngx_addon_dir/ngx_js_fetch.h" NJS_SRCS="$ngx_addon_dir/ngx_js.c \ $ngx_addon_dir/ngx_js_fetch.c \ - $ngx_addon_dir/ngx_js_regex.c \ - $ngx_addon_dir/../external/njs_webcrypto_module.c - $ngx_addon_dir/../external/njs_zlib_module.c - $ngx_addon_dir/../external/njs_xml_module.c" + $ngx_addon_dir/ngx_js_regex.c" + +NJS_OPENSSL_LIB= +NJS_XSLT_LIB= +NJS_ZLIB_LIB= + +if [ $NJS_OPENSSL != NO ]; then + NJS_OPENSSL_LIB=OPENSSL + have=NJS_HAVE_OPENSSL . auto/have + NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/njs_webcrypto_module.c" + + echo " enabled webcrypto module" +fi + +if [ $NJS_LIBXSLT != NO ]; then + NJS_XSLT_LIB=LIBXSLT + have=NJS_HAVE_XML . auto/have + NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/njs_xml_module.c" + + echo " enabled xml module" +fi + +if [ $NJS_ZLIB != NO ]; then + NJS_ZLIB_LIB=ZLIB + have=NJS_HAVE_ZLIB . auto/have + NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/njs_zlib_module.c" + + echo " enabled zlib module" +fi if [ $HTTP != NO ]; then ngx_module_type=HTTP_AUX_FILTER @@ -15,7 +44,8 @@ if [ $HTTP != NO ]; then ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build" ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS" ngx_module_srcs="$ngx_addon_dir/ngx_http_js_module.c $NJS_SRCS" - ngx_module_libs="PCRE OPENSSL ZLIB LIBXSLT $ngx_addon_dir/../build/libnjs.a -lm" + ngx_module_libs="PCRE $NJS_OPENSSL_LIB $NJS_XSLT_LIB $NJS_ZLIB_LIB \ + $ngx_addon_dir/../build/libnjs.a -lm" . auto/module @@ -30,7 +60,8 @@ if [ $STREAM != NO ]; then ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build" ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS" ngx_module_srcs="$ngx_addon_dir/ngx_stream_js_module.c $NJS_SRCS" - ngx_module_libs="PCRE OPENSSL ZLIB LIBXSLT $ngx_addon_dir/../build/libnjs.a -lm" + ngx_module_libs="PCRE $NJS_OPENSSL_LIB $NJS_XSLT_LIB $NJS_ZLIB_LIB \ + $ngx_addon_dir/../build/libnjs.a -lm" . auto/module fi diff -r a140e71b0fbf -r 1f84f3c34bb0 nginx/ngx_js.c --- a/nginx/ngx_js.c Wed May 10 20:50:53 2023 -0700 +++ b/nginx/ngx_js.c Wed May 10 22:36:53 2023 -0700 @@ -88,9 +88,15 @@ static njs_external_t ngx_js_ext_core[] njs_module_t *njs_js_addon_modules[] = { +#ifdef NJS_HAVE_OPENSSL &njs_webcrypto_module, +#endif +#ifdef NJS_HAVE_XML &njs_xml_module, +#endif +#ifdef NJS_HAVE_ZLIB &njs_zlib_module, +#endif NULL, }; From jordanc.carter at outlook.com Fri May 12 00:11:34 2023 From: jordanc.carter at outlook.com (J Carter) Date: Fri, 12 May 2023 01:11:34 +0100 Subject: [PATCH] Asynchronous close event handling for single peer upstreams In-Reply-To: References: Message-ID: On Sun, 7 May 2023 21:55:19 +0100 J Carter wrote: > # HG changeset patch > # User jordanc.carter at outlook.com > # Date 1683491710 -3600 > # Sun May 07 21:35:10 2023 +0100 > # Node ID e1ec9971da677b763c7576c729576d6f906631ae > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > Asynchronous close event handling for single peer upstreams > > Prevents additional upstream tries when consecutive asynchronous close > errors are encountered for single peer upstreams utilizing keepalive > connections. > > This replaces the current behavior of unlimited retries. > > diff -r b71e69247483 -r e1ec9971da67 src/event/ngx_event_connect.h > --- a/src/event/ngx_event_connect.h Mon May 01 19:16:05 2023 > +0400 +++ b/src/event/ngx_event_connect.h Sun May 07 21:35:10 > 2023 +0100 @@ -17,6 +17,7 @@ > #define NGX_PEER_KEEPALIVE 1 > #define NGX_PEER_NEXT 2 > #define NGX_PEER_FAILED 4 > +#define NGX_PEER_ASYNC_FAILED 8 > > > typedef struct ngx_peer_connection_s ngx_peer_connection_t; > @@ -64,6 +65,7 @@ > unsigned transparent:1; > unsigned so_keepalive:1; > unsigned down:1; > + unsigned async_failed:1; > > /* ngx_connection_log_error_e */ > unsigned log_error:2; > diff -r b71e69247483 -r e1ec9971da67 src/http/ngx_http_upstream.c > --- a/src/http/ngx_http_upstream.c Mon May 01 19:16:05 2023 > +0400 +++ b/src/http/ngx_http_upstream.c Sun May 07 21:35:10 > 2023 +0100 @@ -4317,6 +4317,9 @@ > { > state = NGX_PEER_NEXT; > > + } else if (u->peer.cached && ft_type == > NGX_HTTP_UPSTREAM_FT_ERROR) { > + state = NGX_PEER_FAILED | NGX_PEER_ASYNC_FAILED; > + > } else { > state = NGX_PEER_FAILED; > } > @@ -4330,11 +4333,6 @@ > "upstream timed out"); > } > > - if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) { > - /* TODO: inform balancer instead */ > - u->peer.tries++; > - } > - > switch (ft_type) { > > case NGX_HTTP_UPSTREAM_FT_TIMEOUT: > diff -r b71e69247483 -r e1ec9971da67 > src/http/ngx_http_upstream_round_robin.c --- > a/src/http/ngx_http_upstream_round_robin.c Mon May 01 19:16:05 > 2023 +0400 +++ b/src/http/ngx_http_upstream_round_robin.c Sun > May 07 21:35:10 2023 +0100 @@ -623,6 +623,12 @@ > ngx_http_upstream_rr_peers_unlock(rrp->peers); > pc->tries = 0; > + > + if (state & NGX_PEER_ASYNC_FAILED && !pc->async_failed) { > + pc->async_failed = 1; > + pc->tries = 1; > + } > + > return; > } Hello, any opinions or suggestions for this patch? From jordanc.carter at outlook.com Fri May 12 02:37:52 2023 From: jordanc.carter at outlook.com (J Carter) Date: Fri, 12 May 2023 03:37:52 +0100 Subject: [PATCH] Added $http2_stream_id Message-ID: # HG changeset patch # User jordanc.carter at outlook.com # Date 1683858766 -3600 # Fri May 12 03:32:46 2023 +0100 # Node ID de1a1b4141e827984cbd0d2feb97f870c32ff289 # Parent b71e69247483631bd8fc79a47cc32b762625b1fb Added $http2_stream_id Useful for tracing multiplexed requests from client logs or pcaps captured between client and nginx, to nginx's own access logs. Also useful for matching multiplexed request's access log entries to debug level error logs - which is particularly difficult to do. diff --git a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c --- a/src/http/v2/ngx_http_v2_module.c +++ b/src/http/v2/ngx_http_v2_module.c @@ -15,6 +15,8 @@ static ngx_int_t ngx_http_v2_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v2_variable_stream_id(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_v2_module_init(ngx_cycle_t *cycle); @@ -213,6 +215,9 @@ { ngx_string("http2"), NULL, ngx_http_v2_variable, 0, 0, 0 }, + { ngx_string("http2_stream_id"), NULL, + ngx_http_v2_variable_stream_id, 0, 0, 0 }, + ngx_http_null_variable }; @@ -271,6 +276,32 @@ static ngx_int_t +ngx_http_v2_variable_stream_id(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + if (!r->stream) { + v->not_found = 1; + return NGX_OK; + } + + p = ngx_pnalloc(r->pool, NGX_INT32_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%i", r->stream->node->id) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t ngx_http_v2_module_init(ngx_cycle_t *cycle) { return NGX_OK; From osa at FreeBSD.org.ru Fri May 12 03:15:32 2023 From: osa at FreeBSD.org.ru (=?iso-8859-1?q?Sergey_A=2E_Osokin?=) Date: Fri, 12 May 2023 06:15:32 +0300 Subject: [PATCH] Use pipe as a delimiter for sed(1) to fix build for libnjs target Message-ID: <9d717a336e89a34ac92b.1683861332@mp2.macomnet.net> # HG changeset patch # User Sergey A. Osokin # Date 1683860927 -10800 # Fri May 12 06:08:47 2023 +0300 # Node ID 9d717a336e89a34ac92b87a6294a5f552dc56f74 # Parent 1f84f3c34bb08b3489040319aac5cd46ca172bec Use pipe as a delimiter for sed(1) to fix build for libnjs target. sed(1) command line utility may fail with the following error: sed: 1: "s, at EXTRA_LIBS@,-lm -L ...": bad in substitute command: '-' when a replacement for @EXTRA_LIBS@ contains a comma symbol. diff -r 1f84f3c34bb0 -r 9d717a336e89 auto/make --- a/auto/make Wed May 10 22:36:53 2023 -0700 +++ b/auto/make Fri May 12 06:08:47 2023 +0300 @@ -320,11 +320,11 @@ pc: $NJS_BUILD_DIR/njs.pc $NJS_BUILD_DIR/njs.pc: $NJS_BUILD_DIR/njs_auto_config.h - sed -e "s, at PREFIX@,$(pwd)/$NJS_BUILD_DIR," \\ - -e "s, at LIBDIR@,$(pwd)/$NJS_BUILD_DIR," \\ - -e "s, at CFLAGS@,-I$(pwd)/$NJS_BUILD_DIR -I$(pwd)/src," \\ - -e "s, at VERSION@,\$(NJS_VER)," \\ - -e "s, at EXTRA_LIBS@,-lm $NJS_LIBS $NJS_LIB_AUX_LIBS," \\ + sed -e "s|@PREFIX@|$(pwd)/$NJS_BUILD_DIR|" \\ + -e "s|@LIBDIR@|$(pwd)/$NJS_BUILD_DIR|" \\ + -e "s|@CFLAGS@|-I$(pwd)/$NJS_BUILD_DIR -I$(pwd)/src|" \\ + -e "s|@VERSION@|\$(NJS_VER)|" \\ + -e "s|@EXTRA_LIBS@|-lm $NJS_LIBS $NJS_LIB_AUX_LIBS|" \\ src/njs.pc.in > \$@ END From arut at nginx.com Fri May 12 05:34:07 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 12 May 2023 09:34:07 +0400 Subject: [PATCH] QUIC: style In-Reply-To: <7d67fe09bcad6bc7e375.1683844241@vm-bsd.mdounin.ru> References: <7d67fe09bcad6bc7e375.1683844241@vm-bsd.mdounin.ru> Message-ID: <20230512053407.xkyyy2v5a3cg4wdz@N00W24XTQX> On Fri, May 12, 2023 at 01:30:41AM +0300, Maxim Dounin wrote: > # HG changeset patch > # User Maxim Dounin > # Date 1683820081 -10800 > # Thu May 11 18:48:01 2023 +0300 > # Branch quic > # Node ID 7d67fe09bcad6bc7e375c4d889787b8b57017856 > # Parent b9230e37b8a19e594d201dcc1e6dd8fc666feaf0 > QUIC: style. > > diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h > --- a/src/http/v3/ngx_http_v3.h > +++ b/src/http/v3/ngx_http_v3.h > @@ -85,11 +85,11 @@ > #define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session > > #define ngx_http_v3_get_module_loc_conf(c, module) \ > - ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > module) > > #define ngx_http_v3_get_module_srv_conf(c, module) \ > - ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > module) > > #define ngx_http_v3_finalize_connection(c, code, reason) \ > 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 > @@ -868,7 +868,8 @@ ngx_http_v3_parse_field_l(ngx_connection > > case sw_start: > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); > + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > + "http3 parse field l"); > > if (b->pos == b->last) { > return NGX_AGAIN; > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks good From mdounin at mdounin.ru Fri May 12 12:56:40 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 12 May 2023 15:56:40 +0300 Subject: [PATCH] QUIC: style In-Reply-To: <20230512053407.xkyyy2v5a3cg4wdz@N00W24XTQX> References: <7d67fe09bcad6bc7e375.1683844241@vm-bsd.mdounin.ru> <20230512053407.xkyyy2v5a3cg4wdz@N00W24XTQX> Message-ID: Hello! On Fri, May 12, 2023 at 09:34:07AM +0400, Roman Arutyunyan wrote: > On Fri, May 12, 2023 at 01:30:41AM +0300, Maxim Dounin wrote: > > # HG changeset patch > > # User Maxim Dounin > > # Date 1683820081 -10800 > > # Thu May 11 18:48:01 2023 +0300 > > # Branch quic > > # Node ID 7d67fe09bcad6bc7e375c4d889787b8b57017856 > > # Parent b9230e37b8a19e594d201dcc1e6dd8fc666feaf0 > > QUIC: style. > > > > diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h > > --- a/src/http/v3/ngx_http_v3.h > > +++ b/src/http/v3/ngx_http_v3.h > > @@ -85,11 +85,11 @@ > > #define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session > > > > #define ngx_http_v3_get_module_loc_conf(c, module) \ > > - ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > > + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > > module) > > > > #define ngx_http_v3_get_module_srv_conf(c, module) \ > > - ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > > + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > > module) > > > > #define ngx_http_v3_finalize_connection(c, code, reason) \ > > 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 > > @@ -868,7 +868,8 @@ ngx_http_v3_parse_field_l(ngx_connection > > > > case sw_start: > > > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); > > + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > + "http3 parse field l"); > > > > if (b->pos == b->last) { > > return NGX_AGAIN; > > Looks good Thanks, pushed to http://mdounin.ru/hg/quic. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Fri May 12 13:33:51 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 12 May 2023 16:33:51 +0300 Subject: [PATCH] HTTP/3: removed "http3" parameter of "listen" directive In-Reply-To: References: Message-ID: Hello! On Thu, May 11, 2023 at 01:23:13PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1683796930 -14400 > # Thu May 11 13:22:10 2023 +0400 > # Branch quic > # Node ID f721a6689bd0a7278f362e67f323a5087919c0ac > # Parent 0d89203a863a34227d3c9e0ddcf07a670c0e78fc > HTTP/3: removed "http3" parameter of "listen" directive. > > The parameter has been deprecated since c851a2ed5ce8. > > diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c > --- a/src/http/modules/ngx_http_ssl_module.c > +++ b/src/http/modules/ngx_http_ssl_module.c > @@ -477,7 +477,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t > srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; > srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; > > - } else if (h3scf->enable || hc->addr_conf->http3) { > + } else if (h3scf->enable) { > srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; > srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; > > diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c > --- a/src/http/ngx_http.c > +++ b/src/http/ngx_http.c > @@ -1242,7 +1242,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, n > ngx_uint_t http2; > #endif > #if (NGX_HTTP_V3) > - ngx_uint_t http3; > ngx_uint_t quic; > #endif > > @@ -1287,7 +1286,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, n > protocols_prev |= addr[i].opt.http2 << 2; > #endif > #if (NGX_HTTP_V3) > - http3 = lsopt->http3 || addr[i].opt.http3; > quic = lsopt->quic || addr[i].opt.quic; > #endif > > @@ -1378,7 +1376,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, n > addr[i].opt.http2 = http2; > #endif > #if (NGX_HTTP_V3) > - addr[i].opt.http3 = http3; > addr[i].opt.quic = quic; > #endif > > @@ -1929,7 +1926,6 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h > addrs[i].conf.http2 = addr[i].opt.http2; > #endif > #if (NGX_HTTP_V3) > - addrs[i].conf.http3 = addr[i].opt.http3; > addrs[i].conf.quic = addr[i].opt.quic; > #endif > addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; > @@ -1998,7 +1994,6 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_ > addrs6[i].conf.http2 = addr[i].opt.http2; > #endif > #if (NGX_HTTP_V3) > - addrs6[i].conf.http3 = addr[i].opt.http3; > addrs6[i].conf.quic = addr[i].opt.quic; > #endif > addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; > diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c > --- a/src/http/ngx_http_core_module.c > +++ b/src/http/ngx_http_core_module.c > @@ -4186,23 +4186,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx > #endif > } > > - if (ngx_strcmp(value[n].data, "http3") == 0) { > -#if (NGX_HTTP_V3) > - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, > - "the \"http3\" parameter is deprecated, " > - "use \"quic\" parameter instead"); > - lsopt.quic = 1; > - lsopt.http3 = 1; > - lsopt.type = SOCK_DGRAM; > - continue; > -#else > - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > - "the \"http3\" parameter requires " > - "ngx_http_v3_module"); > - return NGX_CONF_ERROR; > -#endif > - } > - > if (ngx_strcmp(value[n].data, "quic") == 0) { > #if (NGX_HTTP_V3) > lsopt.quic = 1; > diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h > --- a/src/http/ngx_http_core_module.h > +++ b/src/http/ngx_http_core_module.h > @@ -75,7 +75,6 @@ typedef struct { > unsigned wildcard:1; > unsigned ssl:1; > unsigned http2:1; > - unsigned http3:1; > unsigned quic:1; > #if (NGX_HAVE_INET6) > unsigned ipv6only:1; > @@ -240,7 +239,6 @@ struct ngx_http_addr_conf_s { > > unsigned ssl:1; > unsigned http2:1; > - unsigned http3:1; > unsigned quic:1; > unsigned proxy_protocol:1; > }; > diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c > --- a/src/http/v3/ngx_http_v3_request.c > +++ b/src/http/v3/ngx_http_v3_request.c > @@ -1014,14 +1014,12 @@ ngx_http_v3_process_request_header(ngx_h > h3c = ngx_http_v3_get_session(c); > h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); > > - if (!r->http_connection->addr_conf->http3) { > - if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { > - ngx_log_error(NGX_LOG_INFO, c->log, 0, > - "client attempted to request the server name " > - "for which the negotiated protocol is disabled"); > - ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); > - return NGX_ERROR; > - } > + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { > + ngx_log_error(NGX_LOG_INFO, c->log, 0, > + "client attempted to request the server name " > + "for which the negotiated protocol is disabled"); > + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); > + return NGX_ERROR; > } > > if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { Looks good. -- Maxim Dounin http://mdounin.ru/ From jordanc.carter at outlook.com Sun May 14 03:51:58 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 14 May 2023 04:51:58 +0100 Subject: [PATCH] Added $realip_add_x_forwarded_for Message-ID: # HG changeset patch # User jordanc.carter at outlook.com # Date 1684035158 -3600 # Sun May 14 04:32:38 2023 +0100 # Node ID dad6e472ee0d97a738b117f6480987ef135c9e7f # Parent b71e69247483631bd8fc79a47cc32b762625b1fb Added $realip_add_x_forwarded_for Resolves Ticket #2127. Duplicates the functionality of proxy_add_x_forwarded_for, except the true source ip is appended and not the remote address extracted by the real IP module. In practice this is proxy_add_x_forwarded_for but $realip_remote_addr is used and not $remote_addr. This follows the same convention as $realip_remote_addr and $real_ip_remote_port, in that it is a drop in replacement for $proxy_add_x_forwarded_for that can be used in contexts that both do and do not have the real_ip directives, with the same results. An example configuration: server { listen 80; real_ip_header X-Forwarded-For; set_real_ip_from 127.0.0.1; location / { proxy_set_header X-Forwarded-For $realip_add_x_forwarded_for; proxy_set_header Remote $remote_addr; proxy_pass http://127.0.0.1:8080; } } server { listen 8080; add_header Echo-X-Forwarded_For $http_x_forwarded_for; add_header Echo-Remote $http_remote; return 200; } test with: curl -I --interface 127.0.0.1 -H "X-Forwarded-For: 10.0.0.1" localhost curl -I --interface 127.0.0.2 -H "X-Forwarded-For: 10.0.0.1" localhost diff --git a/src/http/modules/ngx_http_realip_module.c b/src/http/modules/ngx_http_realip_module.c --- a/src/http/modules/ngx_http_realip_module.c +++ b/src/http/modules/ngx_http_realip_module.c @@ -53,6 +53,8 @@ ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_realip_remote_port_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_realip_add_x_forwarded_for_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_command_t ngx_http_realip_commands[] = { @@ -122,6 +124,9 @@ { ngx_string("realip_remote_port"), NULL, ngx_http_realip_remote_port_variable, 0, 0, 0 }, + { ngx_string("realip_add_x_forwarded_for"), NULL, + ngx_http_realip_add_x_forwarded_for_variable, 0, 0, 0 }, + ngx_http_null_variable }; @@ -619,3 +624,55 @@ return NGX_OK; } + + +static ngx_int_t +ngx_http_realip_add_x_forwarded_for_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + size_t len; + u_char *p; + ngx_str_t *addr_text; + ngx_table_elt_t *h, *xfwd; + ngx_http_realip_ctx_t *ctx; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + len = 0; + + ctx = ngx_http_realip_get_module_ctx(r); + addr_text = ctx ? &ctx->addr_text : &r->connection->addr_text; + + xfwd = r->headers_in.x_forwarded_for; + + for (h = xfwd; h; h = h->next) { + len += h->value.len + sizeof(", ") - 1; + } + + if (len == 0) { + v->len = addr_text->len; + v->data = addr_text->data; + return NGX_OK; + } + + len += addr_text->len; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = len; + v->data = p; + + for (h = xfwd; h; h = h->next) { + p = ngx_copy(p, h->value.data, h->value.len); + *p++ = ','; *p++ = ' '; + } + + ngx_memcpy(p, addr_text->data, addr_text->len); + + return NGX_OK; +} From jordanc.carter at outlook.com Sun May 14 06:47:21 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 14 May 2023 07:47:21 +0100 Subject: [PATCH] Added $realip_add_x_forwarded_for In-Reply-To: References: Message-ID: Re-sending with non-malformed patch... # HG changeset patch # User jordanc.carter at outlook.com # Date 1684035158 -3600 # Sun May 14 04:32:38 2023 +0100 # Node ID dad6e472ee0d97a738b117f6480987ef135c9e7f # Parent b71e69247483631bd8fc79a47cc32b762625b1fb Added $realip_add_x_forwarded_for Resolves Ticket #2127. Duplicates the functionality of proxy_add_x_forwarded_for, except the true source ip is appended and not the remote address extracted by the real IP module. In practise this is proxy_add_x_forwarded_for but $realip_remote_addr is used and not $remote_addr. This follows the same convention as $realip_remote_addr and $real_ip_remote_port, in that it is a drop in replacement for $proxy_add_x_forwarded_for that can be used in contexts that both do and do not have the real_ip directives, with the same results. An example configuration: server { listen 80; real_ip_header X-Forwarded-For; set_real_ip_from 127.0.0.1; location / { proxy_set_header X-Forwarded-For $realip_add_x_forwarded_for; proxy_set_header Remote $remote_addr; proxy_pass http://127.0.0.1:8080; } } server { listen 8080; location / { add_header Echo-X-Forwarded_For $http_x_forwarded_for; add_header Remote $http_remote; return 200; } } test with: curl -I --interface 127.0.0.1 -H "X-Forwarded-For: 10.0.0.1" localhost curl -I --interface 127.0.0.2 -H "X-Forwarded-For: 10.0.0.1" localhost diff --git a/src/http/modules/ngx_http_realip_module.c b/src/http/modules/ngx_http_realip_module.c --- a/src/http/modules/ngx_http_realip_module.c +++ b/src/http/modules/ngx_http_realip_module.c @@ -53,6 +53,8 @@ ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_realip_remote_port_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_realip_add_x_forwarded_for_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_command_t ngx_http_realip_commands[] = { @@ -122,6 +124,9 @@ { ngx_string("realip_remote_port"), NULL, ngx_http_realip_remote_port_variable, 0, 0, 0 }, + { ngx_string("realip_add_x_forwarded_for"), NULL, + ngx_http_realip_add_x_forwarded_for_variable, 0, 0, 0 }, + ngx_http_null_variable }; @@ -619,3 +624,55 @@ return NGX_OK; } + + +static ngx_int_t +ngx_http_realip_add_x_forwarded_for_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + size_t len; + u_char *p; + ngx_str_t *addr_text; + ngx_table_elt_t *h, *xfwd; + ngx_http_realip_ctx_t *ctx; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + len = 0; + + ctx = ngx_http_realip_get_module_ctx(r); + addr_text = ctx ? &ctx->addr_text : &r->connection->addr_text; + + xfwd = r->headers_in.x_forwarded_for; + + for (h = xfwd; h; h = h->next) { + len += h->value.len + sizeof(", ") - 1; + } + + if (len == 0) { + v->len = addr_text->len; + v->data = addr_text->data; + return NGX_OK; + } + + len += addr_text->len; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = len; + v->data = p; + + for (h = xfwd; h; h = h->next) { + p = ngx_copy(p, h->value.data, h->value.len); + *p++ = ','; *p++ = ' '; + } + + ngx_memcpy(p, addr_text->data, addr_text->len); + + return NGX_OK; +} -------------- next part -------------- A non-text attachment was scrubbed... Name: hgexport.patch Type: application/octet-stream Size: 3515 bytes Desc: not available URL: From arut at nginx.com Sun May 14 13:38:51 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 14 May 2023 17:38:51 +0400 Subject: [PATCH 0 of 4] QUIC cleanup Message-ID: - The first patch removes QUIC from Stream. - After removing QUIC from Stream, the only thing left in Stream diff is UDP-specific rbtree initialization. After splitting UDP and QUIC implementations, rbtrees are now initialized separately. The second patch brings back single initialization. This is an optional non-cleanup patch. - The third patch removes server push from HTTP/3. - The fourth patch removes README. From arut at nginx.com Sun May 14 13:38:52 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 14 May 2023 17:38:52 +0400 Subject: [PATCH 1 of 4] Stream: removed QUIC support In-Reply-To: References: Message-ID: <113e2438dbd40a6c5a26.1684071532@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1684051535 -14400 # Sun May 14 12:05:35 2023 +0400 # Branch quic # Node ID 113e2438dbd40a6c5a2627ed98707c1418a10fd5 # Parent 8057e053480a49b7fbc626dc92d11f71ba4a387f Stream: removed QUIC support. diff --git a/README b/README --- a/README +++ b/README @@ -58,10 +58,9 @@ 2. Building from sources Refer to http://nginx.org/en/docs/configure.html for details. When configuring nginx, it's possible to enable QUIC and HTTP/3 - using the following new configuration options: + using the following new configuration option: --with-http_v3_module - enable QUIC and HTTP/3 - --with-stream_quic_module - enable QUIC in Stream A library that provides QUIC support is recommended to build nginx, there are several of those available on the market: @@ -105,9 +104,6 @@ 3. Configuration The HTTP "listen" directive got a new option "quic" which enables QUIC as client transport protocol instead of TCP. - The Stream "listen" directive got a new option "quic" which enables - QUIC as client transport protocol instead of TCP or plain UDP. - Along with "quic", it's also possible to specify "reuseport" option [8] to make it work properly with multiple workers. @@ -148,10 +144,6 @@ 3. Configuration The value of $http3 is "h3" for HTTP/3 connections, "hq" for hq connections, or an empty string otherwise. - In stream, an additional variable is available: $quic. - The value of $quic is "quic" if QUIC connection is used, - or an empty string otherwise. - Example configuration: http { @@ -190,7 +182,7 @@ 4. Directives Syntax: quic_retry on | off; Default: quic_retry off; - Context: http | stream, server + Context: http, server Enables the QUIC Address Validation feature. This includes: - sending a new token in a Retry packet or a NEW_TOKEN frame @@ -199,7 +191,7 @@ 4. Directives Syntax: quic_gso on | off; Default: quic_gso off; - Context: http | stream, server + Context: http, server Enables sending in optimized batch mode using segmentation offloading. Optimized sending is only supported on Linux featuring UDP_SEGMENT. @@ -207,7 +199,7 @@ 4. Directives Syntax: quic_host_key file; Default: - - Context: http | stream, server + Context: http, server Specifies a file with the secret key used to encrypt stateless reset and address validation tokens. By default, a randomly generated key is used. @@ -215,24 +207,12 @@ 4. Directives Syntax: quic_active_connection_id_limit number; Default: quic_active_connection_id_limit 2; - Context: http | stream, server + Context: http, server Sets the QUIC active_connection_id_limit transport parameter value. This is the maximum number of connection IDs we are willing to store. - Syntax: quic_timeout time; - Default: quic_timeout 60s; - Context: stream, server - - Defines a timeout used to negotiate the QUIC idle timeout. - In the http module, it is taken from the keepalive_timeout directive. - - - Syntax: quic_stream_buffer_size size; - Default: quic_stream_buffer_size 64k; - Context: stream, server - Syntax: http3_stream_buffer_size size; Default: http3_stream_buffer_size 64k; Context: http, server diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -1075,20 +1075,6 @@ if [ $STREAM != NO ]; then ngx_module_incs= - if [ $STREAM_QUIC = YES ]; then - USE_OPENSSL_QUIC=YES - have=NGX_STREAM_QUIC . auto/have - STREAM_SSL=YES - - ngx_module_name=ngx_stream_quic_module - ngx_module_deps=src/stream/ngx_stream_quic_module.h - ngx_module_srcs=src/stream/ngx_stream_quic_module.c - ngx_module_libs= - ngx_module_link=$STREAM_QUIC - - . auto/module - fi - if [ $STREAM_SSL = YES ]; then USE_OPENSSL=YES have=NGX_STREAM_SSL . auto/have diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -119,7 +119,6 @@ MAIL_SMTP=YES STREAM=NO STREAM_SSL=NO -STREAM_QUIC=NO STREAM_REALIP=NO STREAM_LIMIT_CONN=YES STREAM_ACCESS=YES @@ -324,7 +323,6 @@ use the \"--with-mail_ssl_module\" optio --with-stream) STREAM=YES ;; --with-stream=dynamic) STREAM=DYNAMIC ;; --with-stream_ssl_module) STREAM_SSL=YES ;; - --with-stream_quic_module) STREAM_QUIC=YES ;; --with-stream_realip_module) STREAM_REALIP=YES ;; --with-stream_geoip_module) STREAM_GEOIP=YES ;; --with-stream_geoip_module=dynamic) @@ -547,7 +545,6 @@ cat << END --with-stream enable TCP/UDP proxy module --with-stream=dynamic enable dynamic TCP/UDP proxy module --with-stream_ssl_module enable ngx_stream_ssl_module - --with-stream_quic_module enable ngx_stream_quic_module --with-stream_realip_module enable ngx_stream_realip_module --with-stream_geoip_module enable ngx_stream_geoip_module --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_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 @@ -518,22 +518,9 @@ ngx_stream_optimize_servers(ngx_conf_t * ls->reuseport = addr[i].opt.reuseport; #endif -#if (NGX_STREAM_QUIC) - - ls->quic = addr[i].opt.quic; - - if (ls->quic) { - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_quic_rbtree_insert_value); - } - -#endif - #if !(NGX_WIN32) - if (!ls->quic) { - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_udp_rbtree_insert_value); - } + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, + ngx_udp_rbtree_insert_value); #endif stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); @@ -594,9 +581,6 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx #if (NGX_STREAM_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; #endif -#if (NGX_STREAM_QUIC) - addrs[i].conf.quic = addr[i].opt.quic; -#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs[i].conf.addr_text = addr[i].opt.addr_text; } @@ -632,9 +616,6 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ng #if (NGX_STREAM_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; #endif -#if (NGX_STREAM_QUIC) - addrs6[i].conf.quic = addr[i].opt.quic; -#endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs6[i].conf.addr_text = addr[i].opt.addr_text; } 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 @@ -16,10 +16,6 @@ #include #endif -#if (NGX_STREAM_QUIC) -#include -#endif - typedef struct ngx_stream_session_s ngx_stream_session_t; @@ -55,7 +51,6 @@ typedef struct { unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; - unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -81,7 +76,6 @@ typedef struct { ngx_stream_conf_ctx_t *ctx; ngx_str_t addr_text; unsigned ssl:1; - unsigned quic:1; unsigned proxy_protocol:1; } ngx_stream_addr_conf_t; 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 @@ -760,29 +760,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, n #endif } - if (ngx_strcmp(value[i].data, "quic") == 0) { -#if (NGX_STREAM_QUIC) - ngx_stream_ssl_conf_t *sslcf; - - sslcf = ngx_stream_conf_get_module_srv_conf(cf, - ngx_stream_ssl_module); - - sslcf->listen = 1; - sslcf->file = cf->conf_file->file.name.data; - sslcf->line = cf->conf_file->line; - - ls->quic = 1; - ls->type = SOCK_DGRAM; - - continue; -#else - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the \"quic\" parameter requires " - "ngx_stream_quic_module"); - return NGX_CONF_ERROR; -#endif - } - if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { @@ -894,12 +871,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, n } #endif -#if (NGX_STREAM_SSL && NGX_STREAM_QUIC) - if (ls->ssl && ls->quic) { - return "\"ssl\" parameter is incompatible with \"quic\""; - } -#endif - if (ls->so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; } 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 @@ -129,10 +129,6 @@ ngx_stream_init_connection(ngx_connectio s->ssl = addr_conf->ssl; #endif -#if (NGX_STREAM_QUIC) - s->ssl |= addr_conf->quic; -#endif - if (c->buffer) { s->received += c->buffer->last - c->buffer->pos; } @@ -177,21 +173,6 @@ ngx_stream_init_connection(ngx_connectio s->start_sec = tp->sec; s->start_msec = tp->msec; -#if (NGX_STREAM_QUIC) - - if (addr_conf->quic) { - ngx_quic_conf_t *qcf; - - if (c->quic == NULL) { - qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, - ngx_stream_quic_module); - ngx_quic_run(c, qcf); - return; - } - } - -#endif - rev = c->read; rev->handler = ngx_stream_session_handler; diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -1772,21 +1772,6 @@ ngx_stream_proxy_process(ngx_stream_sess if (dst->type == SOCK_STREAM && pscf->half_close && src->read->eof && !u->half_closed && !dst->buffered) { - -#if (NGX_STREAM_QUIC) - if (dst->quic) { - - if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN) - != NGX_OK) - { - ngx_stream_proxy_finalize(s, - NGX_STREAM_INTERNAL_SERVER_ERROR); - return; - } - - } else -#endif - if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) { ngx_connection_error(c, ngx_socket_errno, ngx_shutdown_socket_n " failed"); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c deleted file mode 100644 --- a/src/stream/ngx_stream_quic_module.c +++ /dev/null @@ -1,343 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - * Copyright (C) Roman Arutyunyan - */ - - -#include -#include -#include - - -static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, - ngx_stream_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); -static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); -static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, - void *child); -static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); - - -static ngx_command_t ngx_stream_quic_commands[] = { - - { ngx_string("quic_timeout"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, timeout), - NULL }, - - { ngx_string("quic_stream_buffer_size"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, stream_buffer_size), - NULL }, - - { ngx_string("quic_retry"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, retry), - NULL }, - - { ngx_string("quic_gso"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, gso_enabled), - NULL }, - - { ngx_string("quic_host_key"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_stream_quic_host_key, - NGX_STREAM_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("quic_active_connection_id_limit"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, active_connection_id_limit), - NULL }, - - ngx_null_command -}; - - -static ngx_stream_module_t ngx_stream_quic_module_ctx = { - ngx_stream_quic_add_variables, /* preconfiguration */ - NULL, /* postconfiguration */ - - NULL, /* create main configuration */ - NULL, /* init main configuration */ - - ngx_stream_quic_create_srv_conf, /* create server configuration */ - ngx_stream_quic_merge_srv_conf, /* merge server configuration */ -}; - - -ngx_module_t ngx_stream_quic_module = { - NGX_MODULE_V1, - &ngx_stream_quic_module_ctx, /* module context */ - ngx_stream_quic_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 ngx_stream_variable_t ngx_stream_quic_vars[] = { - - { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 }, - - ngx_stream_null_variable -}; - -static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic"); - - -static ngx_int_t -ngx_stream_variable_quic(ngx_stream_session_t *s, - ngx_stream_variable_value_t *v, uintptr_t data) -{ - if (s->connection->quic) { - - v->len = 4; - v->valid = 1; - v->no_cacheable = 1; - v->not_found = 0; - v->data = (u_char *) "quic"; - return NGX_OK; - } - - v->not_found = 1; - - return NGX_OK; -} - - -static ngx_int_t -ngx_stream_quic_add_variables(ngx_conf_t *cf) -{ - ngx_stream_variable_t *var, *v; - - for (v = ngx_stream_quic_vars; v->name.len; v++) { - var = ngx_stream_add_variable(cf, &v->name, v->flags); - if (var == NULL) { - return NGX_ERROR; - } - - var->get_handler = v->get_handler; - var->data = v->data; - } - - return NGX_OK; -} - - -static void * -ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) -{ - ngx_quic_conf_t *conf; - - conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); - if (conf == NULL) { - return NULL; - } - - /* - * set by ngx_pcalloc(): - * - * conf->host_key = { 0, NULL } - * conf->stream_close_code = 0; - * conf->stream_reject_code_uni = 0; - * conf->stream_reject_code_bidi= 0; - */ - - conf->timeout = NGX_CONF_UNSET_MSEC; - conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; - conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; - conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; - - conf->retry = NGX_CONF_UNSET; - conf->gso_enabled = NGX_CONF_UNSET; - - conf->active_connection_id_limit = NGX_CONF_UNSET_UINT; - - return conf; -} - - -static char * -ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) -{ - ngx_quic_conf_t *prev = parent; - ngx_quic_conf_t *conf = child; - - ngx_stream_ssl_conf_t *scf; - - ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); - - ngx_conf_merge_size_value(conf->stream_buffer_size, - prev->stream_buffer_size, - 65536); - - ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi, - prev->max_concurrent_streams_bidi, 16); - - ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni, - prev->max_concurrent_streams_uni, 3); - - ngx_conf_merge_value(conf->retry, prev->retry, 0); - ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); - - ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); - - ngx_conf_merge_uint_value(conf->active_connection_id_limit, - conf->active_connection_id_limit, - 2); - - if (conf->host_key.len == 0) { - - conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; - conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); - if (conf->host_key.data == NULL) { - return NGX_CONF_ERROR; - } - - if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) - <= 0) - { - return NGX_CONF_ERROR; - } - } - - if (ngx_quic_derive_key(cf->log, "av_token_key", - &conf->host_key, &ngx_stream_quic_salt, - conf->av_token_key, NGX_QUIC_AV_KEY_LEN) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - if (ngx_quic_derive_key(cf->log, "sr_token_key", - &conf->host_key, &ngx_stream_quic_salt, - conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); - conf->ssl = &scf->ssl; - - return NGX_CONF_OK; -} - - -static char * -ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_quic_conf_t *qcf = conf; - - u_char *buf; - size_t size; - ssize_t n; - ngx_str_t *value; - ngx_file_t file; - ngx_file_info_t fi; - - if (qcf->host_key.len) { - return "is duplicate"; - } - - buf = NULL; -#if (NGX_SUPPRESS_WARN) - size = 0; -#endif - - value = cf->args->elts; - - if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&file, sizeof(ngx_file_t)); - file.name = value[1]; - file.log = cf->log; - - file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); - - if (file.fd == NGX_INVALID_FILE) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, - ngx_open_file_n " \"%V\" failed", &file.name); - return NGX_CONF_ERROR; - } - - if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, - ngx_fd_info_n " \"%V\" failed", &file.name); - goto failed; - } - - size = ngx_file_size(&fi); - - if (size == 0) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"%V\" zero key size", &file.name); - goto failed; - } - - buf = ngx_pnalloc(cf->pool, size); - if (buf == NULL) { - goto failed; - } - - n = ngx_read_file(&file, buf, size, 0); - - if (n == NGX_ERROR) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, - ngx_read_file_n " \"%V\" failed", &file.name); - goto failed; - } - - if ((size_t) n != size) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, - ngx_read_file_n " \"%V\" returned only " - "%z bytes instead of %uz", &file.name, n, size); - goto failed; - } - - qcf->host_key.data = buf; - qcf->host_key.len = n; - - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, - ngx_close_file_n " \"%V\" failed", &file.name); - } - - return NGX_CONF_OK; - -failed: - - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, - ngx_close_file_n " \"%V\" failed", &file.name); - } - - if (buf) { - ngx_explicit_memzero(buf, size); - } - - return NGX_CONF_ERROR; -} diff --git a/src/stream/ngx_stream_quic_module.h b/src/stream/ngx_stream_quic_module.h deleted file mode 100644 --- a/src/stream/ngx_stream_quic_module.h +++ /dev/null @@ -1,20 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_STREAM_QUIC_H_INCLUDED_ -#define _NGX_STREAM_QUIC_H_INCLUDED_ - - -#include -#include -#include - - -extern ngx_module_t ngx_stream_quic_module; - - -#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */ 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 @@ -9,10 +9,6 @@ #include #include -#if (NGX_QUIC_OPENSSL_COMPAT) -#include -#endif - typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -1199,10 +1195,7 @@ ngx_stream_ssl_conf_command_check(ngx_co static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf) { - ngx_uint_t i; - ngx_stream_listen_t *listen; ngx_stream_handler_pt *h; - ngx_stream_ssl_conf_t *scf; ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); @@ -1214,29 +1207,5 @@ ngx_stream_ssl_init(ngx_conf_t *cf) *h = ngx_stream_ssl_handler; - listen = cmcf->listen.elts; - - for (i = 0; i < cmcf->listen.nelts; i++) { - if (!listen[i].quic) { - continue; - } - - scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; - -#if (NGX_QUIC_OPENSSL_COMPAT) - if (ngx_quic_compat_init(cf, scf->ssl.ctx) != NGX_OK) { - return NGX_ERROR; - } -#endif - - if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "\"ssl_protocols\" must enable TLSv1.3 for " - "the \"listen ... quic\" directive in %s:%ui", - scf->file, scf->line); - return NGX_ERROR; - } - } - return NGX_OK; } diff --git a/src/stream/ngx_stream_write_filter_module.c b/src/stream/ngx_stream_write_filter_module.c --- a/src/stream/ngx_stream_write_filter_module.c +++ b/src/stream/ngx_stream_write_filter_module.c @@ -277,12 +277,7 @@ ngx_stream_write_filter(ngx_stream_sessi *out = chain; if (chain) { - if (c->shared -#if (NGX_STREAM_QUIC) - && c->quic == NULL -#endif - ) - { + if (c->shared) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "shared connection is busy"); return NGX_ERROR; From arut at nginx.com Sun May 14 13:38:53 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 14 May 2023 17:38:53 +0400 Subject: [PATCH 2 of 4] Common tree insert function for QUIC and UDP connections In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1684053011 -14400 # Sun May 14 12:30:11 2023 +0400 # Branch quic # Node ID adcc6d8acfd47c1344b121fceeb94fbcc3f8b5c0 # Parent 113e2438dbd40a6c5a2627ed98707c1418a10fd5 Common tree insert function for QUIC and UDP connections. Previously, ngx_udp_rbtree_insert_value() was used for plain UDP and ngx_quic_rbtree_insert_value() was used for QUIC. Because of this it was impossible to initialize connection tree in ngx_create_listening() since this function is not aware what kind of listening it creates. Now ngx_udp_rbtree_insert_value() is used for both QUIC and UDP. To make is possible, a generic key field is added to ngx_udp_connection_t. It keeps client address for UDP and connection ID for QUIC. diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -72,6 +72,10 @@ ngx_create_listening(ngx_conf_t *cf, str ngx_memcpy(ls->addr_text.data, text, len); +#if !(NGX_WIN32) + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value); +#endif + ls->fd = (ngx_socket_t) -1; ls->type = SOCK_STREAM; diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -417,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_n udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, + udp->key.len, udpt->key.len); if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -471,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection ngx_crc32_final(hash); udp->node.key = hash; + udp->key.data = (u_char *) c->sockaddr; + udp->key.len = c->socklen; cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h --- a/src/event/ngx_event_udp.h +++ b/src/event/ngx_event_udp.h @@ -27,6 +27,7 @@ struct ngx_udp_connection_s { ngx_rbtree_node_t node; ngx_connection_t *connection; ngx_buf_t *buffer; + ngx_str_t key; }; diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -111,8 +111,6 @@ struct ngx_quic_stream_s { void ngx_quic_recvmsg(ngx_event_t *ev); -void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -179,6 +179,7 @@ ngx_quic_listen(ngx_connection_t *c, ngx qsock->udp.connection = c; qsock->udp.node.key = ngx_crc32_long(id.data, id.len); + qsock->udp.key = id; ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c --- a/src/event/quic/ngx_event_quic_udp.c +++ b/src/event/quic/ngx_event_quic_udp.c @@ -365,59 +365,6 @@ ngx_quic_close_accepted_connection(ngx_c } -void -ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) -{ - ngx_int_t rc; - ngx_connection_t *c, *ct; - ngx_rbtree_node_t **p; - ngx_quic_socket_t *qsock, *qsockt; - - for ( ;; ) { - - if (node->key < temp->key) { - - p = &temp->left; - - } else if (node->key > temp->key) { - - p = &temp->right; - - } else { /* node->key == temp->key */ - - qsock = (ngx_quic_socket_t *) node; - c = qsock->udp.connection; - - qsockt = (ngx_quic_socket_t *) temp; - ct = qsockt->udp.connection; - - rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id, - qsock->sid.len, qsockt->sid.len); - - if (rc == 0 && c->listening->wildcard) { - rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, - ct->local_sockaddr, ct->local_socklen, 1); - } - - p = (rc < 0) ? &temp->left : &temp->right; - } - - if (*p == sentinel) { - break; - } - - temp = *p; - } - - *p = node; - node->parent = temp; - node->left = sentinel; - node->right = sentinel; - ngx_rbt_red(node); -} - - static ngx_connection_t * ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen) diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1883,14 +1883,7 @@ ngx_http_add_listening(ngx_conf_t *cf, n ls->wildcard = addr->opt.wildcard; #if (NGX_HTTP_V3) - ls->quic = addr->opt.quic; - - if (ls->quic) { - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_quic_rbtree_insert_value); - } - #endif return ls; 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 @@ -518,11 +518,6 @@ ngx_stream_optimize_servers(ngx_conf_t * ls->reuseport = addr[i].opt.reuseport; #endif -#if !(NGX_WIN32) - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_udp_rbtree_insert_value); -#endif - stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); if (stport == NULL) { return NGX_CONF_ERROR; From arut at nginx.com Sun May 14 13:38:54 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 14 May 2023 17:38:54 +0400 Subject: [PATCH 3 of 4] HTTP/3: removed server push support In-Reply-To: References: Message-ID: <49a8edf7bf31b7868139.1684071534@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1683871330 -14400 # Fri May 12 10:02:10 2023 +0400 # Branch quic # Node ID 49a8edf7bf31b78681399cd7e93a8516788607dd # Parent adcc6d8acfd47c1344b121fceeb94fbcc3f8b5c0 HTTP/3: removed server push support. diff --git a/README b/README --- a/README +++ b/README @@ -135,10 +135,7 @@ 3. Configuration http3 http3_hq http3_stream_buffer_size - http3_max_concurrent_pushes http3_max_concurrent_streams - http3_push - http3_push_preload In http, an additional variable is available: $http3. The value of $http3 is "h3" for HTTP/3 connections, @@ -226,13 +223,6 @@ 4. Directives - initial_max_stream_data_uni - Syntax: http3_max_concurrent_pushes number; - Default: http3_max_concurrent_pushes 10; - Context: http, server - - Limits the maximum number of concurrent push requests in a connection. - - Syntax: http3_max_concurrent_streams number; Default: http3_max_concurrent_streams 128; Context: http, server @@ -240,31 +230,6 @@ 4. Directives Sets the maximum number of concurrent HTTP/3 streams in a connection. - Syntax: http3_push uri | off; - Default: http3_push off; - Context: http, server, location - - Pre-emptively sends (pushes) a request to the specified uri along with - the response to the original request. Only relative URIs with absolute - path will be processed, for example: - - http3_push /static/css/main.css; - - The uri value can contain variables. - - Several http3_push directives can be specified on the same configuration - level. The off parameter cancels the effect of the http3_push directives - inherited from the previous configuration level. - - - Syntax: http3_push_preload on | off; - Default: http3_push_preload off; - Context: http, server, location - - Enables automatic conversion of preload links specified in the “Link” - response header fields into push requests. - - Syntax: http3 on | off; Default: http3 on; Context: http, server diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -30,11 +30,7 @@ ngx_http_v3_init_session(ngx_connection_ goto failed; } - h3c->max_push_id = (uint64_t) -1; - h3c->goaway_push_id = (uint64_t) -1; - ngx_queue_init(&h3c->blocked); - ngx_queue_init(&h3c->pushing); h3c->keepalive.log = c->log; h3c->keepalive.data = c; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -106,19 +106,11 @@ typedef struct { ngx_flag_t enable_hq; size_t max_table_capacity; ngx_uint_t max_blocked_streams; - ngx_uint_t max_concurrent_pushes; ngx_uint_t max_concurrent_streams; ngx_quic_conf_t quic; } ngx_http_v3_srv_conf_t; -typedef struct { - ngx_flag_t push_preload; - ngx_flag_t push; - ngx_array_t *pushes; -} ngx_http_v3_loc_conf_t; - - struct ngx_http_v3_parse_s { size_t header_limit; ngx_http_v3_parse_headers_t headers; @@ -136,11 +128,6 @@ struct ngx_http_v3_session_s { ngx_queue_t blocked; ngx_uint_t nblocked; - ngx_queue_t pushing; - ngx_uint_t npushing; - uint64_t next_push_id; - uint64_t max_push_id; - uint64_t goaway_push_id; uint64_t next_request_id; off_t total_bytes; diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -36,17 +36,6 @@ typedef struct { static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, - ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_create_push_request( - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, - const char *name, ngx_str_t *value); -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, - ngx_str_t *path, uint64_t push_id); static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, @@ -155,14 +144,6 @@ ngx_http_v3_header_filter(ngx_http_reque out = NULL; ll = &out; - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - && r->method != NGX_HTTP_HEAD) - { - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { - return NGX_ERROR; - } - } - len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { @@ -607,672 +588,6 @@ ngx_http_v3_header_filter(ngx_http_reque static ngx_int_t -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) -{ - u_char *start, *end, *last; - ngx_str_t path; - ngx_int_t rc; - ngx_uint_t i, push; - ngx_table_elt_t *h; - ngx_http_v3_loc_conf_t *h3lcf; - ngx_http_complex_value_t *pushes; - - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); - - if (h3lcf->pushes) { - pushes = h3lcf->pushes->elts; - - for (i = 0; i < h3lcf->pushes->nelts; i++) { - - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { - return NGX_ERROR; - } - - if (path.len == 0) { - continue; - } - - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { - continue; - } - - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - } - - if (!h3lcf->push_preload) { - return NGX_OK; - } - - for (h = r->headers_out.link; h; h = h->next) { - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 parse link: \"%V\"", &h->value); - - start = h->value.data; - end = h->value.data + h->value.len; - - next_link: - - while (start < end && *start == ' ') { start++; } - - if (start == end || *start++ != '<') { - continue; - } - - while (start < end && *start == ' ') { start++; } - - for (last = start; last < end && *last != '>'; last++) { - /* void */ - } - - if (last == start || last == end) { - continue; - } - - path.len = last - start; - path.data = start; - - start = last + 1; - - while (start < end && *start == ' ') { start++; } - - if (start == end) { - continue; - } - - if (*start == ',') { - start++; - goto next_link; - } - - if (*start++ != ';') { - continue; - } - - last = ngx_strlchr(start, end, ','); - - if (last == NULL) { - last = end; - } - - push = 0; - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 6 - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) - { - start += 6; - - if (start == last || *start == ' ' || *start == ';') { - push = 0; - break; - } - - goto next_param; - } - - if (last - start >= 11 - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) - { - start += 11; - - if (start == last || *start == ' ' || *start == ';') { - push = 1; - } - - goto next_param; - } - - if (last - start >= 4 - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) - { - start += 4; - - while (start < last && *start == ' ') { start++; } - - if (start == last || *start++ != '"') { - goto next_param; - } - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 7 - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) - { - start += 7; - - if (start < last && (*start == ' ' || *start == '"')) { - push = 1; - break; - } - } - - while (start < last && *start != ' ' && *start != '"') { - start++; - } - - if (start == last) { - break; - } - - if (*start == '"') { - break; - } - - start++; - } - } - - next_param: - - start = ngx_strlchr(start, last, ';'); - - if (start == NULL) { - break; - } - - start++; - } - - if (push) { - while (path.len && path.data[path.len - 1] == ' ') { - path.len--; - } - } - - if (push && path.len - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) - { - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - - if (last < end) { - start = last + 1; - goto next_link; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_chain_t ***ll) -{ - uint64_t push_id; - ngx_int_t rc; - ngx_chain_t *cl; - ngx_connection_t *c; - ngx_http_v3_session_t *h3c; - ngx_http_v3_srv_conf_t *h3scf; - - c = r->connection; - h3c = ngx_http_v3_get_session(c); - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", - path, h3c->npushing, h3scf->max_concurrent_pushes, - h3c->next_push_id, h3c->max_push_id); - - if (!ngx_path_separator(path->data[0])) { - ngx_log_error(NGX_LOG_WARN, c->log, 0, - "non-absolute path \"%V\" not pushed", path); - return NGX_DECLINED; - } - - if (h3c->max_push_id == (uint64_t) -1 - || h3c->next_push_id > h3c->max_push_id) - { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_push_id"); - return NGX_ABORT; - } - - if (h3c->goaway_push_id != (uint64_t) -1) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to goaway"); - return NGX_ABORT; - } - - if (h3c->npushing >= h3scf->max_concurrent_pushes) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_concurrent_pushes"); - return NGX_ABORT; - } - - if (r->headers_in.server.len == 0) { - return NGX_ABORT; - } - - push_id = h3c->next_push_id++; - - rc = ngx_http_v3_create_push_request(r, path, push_id); - if (rc != NGX_OK) { - return rc; - } - - cl = ngx_http_v3_create_push_promise(r, path, push_id); - if (cl == NULL) { - return NGX_ERROR; - } - - for (**ll = cl; **ll; *ll = &(**ll)->next); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, - uint64_t push_id) -{ - ngx_connection_t *c, *pc; - ngx_http_request_t *r; - ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc, *phc; - ngx_http_core_srv_conf_t *cscf; - - pc = pr->connection; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "http3 create push request id:%uL", push_id); - - c = ngx_http_v3_create_push_stream(pc, push_id); - if (c == NULL) { - return NGX_ABORT; - } - -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); -#endif - - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); - if (hc == NULL) { - ngx_http_close_connection(c); - return NGX_ERROR; - } - - phc = ngx_http_quic_get_connection(pc); - ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); - c->data = hc; - - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); - if (ctx == NULL) { - ngx_http_close_connection(c); - return NGX_ERROR; - } - - ctx->connection = c; - ctx->request = NULL; - ctx->current_request = NULL; - - c->log->handler = pc->log->handler; - c->log->data = ctx; - c->log->action = "processing pushed request headers"; - - c->log_error = NGX_ERROR_INFO; - - r = ngx_http_create_request(c); - if (r == NULL) { - ngx_http_close_connection(c); - return NGX_ERROR; - } - - c->data = r; - - ngx_str_set(&r->http_protocol, "HTTP/3.0"); - - r->http_version = NGX_HTTP_VERSION_30; - r->method_name = ngx_http_core_get_method; - r->method = NGX_HTTP_GET; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - r->header_in = ngx_create_temp_buf(r->pool, - cscf->client_header_buffer_size); - if (r->header_in == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, - sizeof(ngx_table_elt_t)) - != NGX_OK) - { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; - - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); - if (r->schema.data == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - r->schema.len = pr->schema.len; - - r->uri_start = ngx_pstrdup(r->pool, path); - if (r->uri_start == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - r->uri_end = r->uri_start + path->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; - } - - if (ngx_http_process_request_uri(r) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) - != NGX_OK) - { - return NGX_ERROR; - } - - if (pr->headers_in.accept_encoding) { - if (ngx_http_v3_set_push_header(r, "accept-encoding", - &pr->headers_in.accept_encoding->value) - != NGX_OK) - { - return NGX_ERROR; - } - } - - if (pr->headers_in.accept_language) { - if (ngx_http_v3_set_push_header(r, "accept-language", - &pr->headers_in.accept_language->value) - != NGX_OK) - { - return NGX_ERROR; - } - } - - if (pr->headers_in.user_agent) { - if (ngx_http_v3_set_push_header(r, "user-agent", - &pr->headers_in.user_agent->value) - != NGX_OK) - { - return NGX_ERROR; - } - } - - c->read->handler = ngx_http_v3_push_request_handler; - c->read->handler = ngx_http_v3_push_request_handler; - - ngx_post_event(c->read, &ngx_posted_events); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, - ngx_str_t *value) -{ - u_char *p; - ngx_table_elt_t *h; - ngx_http_header_t *hh; - ngx_http_core_main_conf_t *cmcf; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 push header \"%s\": \"%V\"", name, value); - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - p = ngx_pnalloc(r->pool, value->len + 1); - if (p == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - ngx_memcpy(p, value->data, value->len); - p[value->len] = '\0'; - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - h->key.data = (u_char *) name; - h->key.len = ngx_strlen(name); - h->hash = ngx_hash_key(h->key.data, h->key.len); - h->lowcase_key = (u_char *) name; - h->value.data = p; - h->value.len = value->len; - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static void -ngx_http_v3_push_request_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - ngx_http_request_t *r; - - c = ev->data; - r = c->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); - - ngx_http_process_request(r); -} - - -static ngx_chain_t * -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, - uint64_t push_id) -{ - size_t n, len; - ngx_buf_t *b; - ngx_chain_t *hl, *cl; - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(r->connection); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create push promise id:%uL", push_id); - - len = ngx_http_v3_encode_varlen_int(NULL, push_id); - - len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); - - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - NULL, r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - NULL, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - NULL, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, - r->headers_in.user_agent->value.len); - } - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); - - b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, - 0, 0, 0); - - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - r->headers_in.server.data, - r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - path->data, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - r->schema.data, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, - r->headers_in.accept_encoding->value.data, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, - r->headers_in.accept_language->value.data, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, - r->headers_in.user_agent->value.data, - r->headers_in.user_agent->value.len); - } - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - n = b->last - b->pos; - - h3c->payload_bytes += n; - - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) - + ngx_http_v3_encode_varlen_int(NULL, n); - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, - NGX_HTTP_V3_FRAME_PUSH_PROMISE); - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); - - hl = ngx_alloc_chain_link(r->pool); - if (hl == NULL) { - return NULL; - } - - hl->buf = b; - hl->next = cl; - - return hl; -} - - -static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -18,10 +18,6 @@ static char *ngx_http_v3_merge_srv_conf( void *child); static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); -static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, - void *child); -static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_v3_commands[] = { @@ -40,13 +36,6 @@ static ngx_command_t ngx_http_v3_comman offsetof(ngx_http_v3_srv_conf_t, enable_hq), NULL }, - { ngx_string("http3_max_concurrent_pushes"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), - NULL }, - { ngx_string("http3_max_concurrent_streams"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -54,20 +43,6 @@ static ngx_command_t ngx_http_v3_comman offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), NULL }, - { ngx_string("http3_push"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v3_push, - NGX_HTTP_LOC_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("http3_push_preload"), - 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_v3_loc_conf_t, push_preload), - NULL }, - { ngx_string("http3_stream_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -117,8 +92,8 @@ static ngx_http_module_t ngx_http_v3_mo ngx_http_v3_create_srv_conf, /* create server configuration */ ngx_http_v3_merge_srv_conf, /* merge server configuration */ - ngx_http_v3_create_loc_conf, /* create location configuration */ - ngx_http_v3_merge_loc_conf /* merge location configuration */ + NULL, /* create location configuration */ + NULL /* merge location configuration */ }; @@ -224,7 +199,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * h3scf->enable = NGX_CONF_UNSET; h3scf->enable_hq = NGX_CONF_UNSET; h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; - h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; @@ -255,9 +229,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); - ngx_conf_merge_uint_value(conf->max_concurrent_pushes, - prev->max_concurrent_pushes, 10); - ngx_conf_merge_uint_value(conf->max_concurrent_streams, prev->max_concurrent_streams, 128); @@ -416,102 +387,3 @@ failed: return NGX_CONF_ERROR; } - - -static void * -ngx_http_v3_create_loc_conf(ngx_conf_t *cf) -{ - ngx_http_v3_loc_conf_t *h3lcf; - - h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); - if (h3lcf == NULL) { - return NULL; - } - - /* - * set by ngx_pcalloc(): - * - * h3lcf->pushes = NULL; - */ - - h3lcf->push_preload = NGX_CONF_UNSET; - h3lcf->push = NGX_CONF_UNSET; - - return h3lcf; -} - - -static char * -ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) -{ - ngx_http_v3_loc_conf_t *prev = parent; - ngx_http_v3_loc_conf_t *conf = child; - - ngx_conf_merge_value(conf->push, prev->push, 1); - - if (conf->push && conf->pushes == NULL) { - conf->pushes = prev->pushes; - } - - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); - - return NGX_CONF_OK; -} - - -static char * -ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_v3_loc_conf_t *h3lcf = conf; - - ngx_str_t *value; - ngx_http_complex_value_t *cv; - ngx_http_compile_complex_value_t ccv; - - value = cf->args->elts; - - if (ngx_strcmp(value[1].data, "off") == 0) { - - if (h3lcf->pushes) { - return "\"off\" parameter cannot be used with URI"; - } - - if (h3lcf->push == 0) { - return "is duplicate"; - } - - h3lcf->push = 0; - return NGX_CONF_OK; - } - - if (h3lcf->push == 0) { - return "URI cannot be used with \"off\" parameter"; - } - - h3lcf->push = 1; - - if (h3lcf->pushes == NULL) { - h3lcf->pushes = ngx_array_create(cf->pool, 1, - sizeof(ngx_http_complex_value_t)); - if (h3lcf->pushes == NULL) { - return NGX_CONF_ERROR; - } - } - - cv = ngx_array_push(h3lcf->pushes); - if (cv == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); - - ccv.cf = cf; - ccv.value = &value[1]; - ccv.complex_value = cv; - - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -16,19 +16,10 @@ typedef struct { } ngx_http_v3_uni_stream_t; -typedef struct { - ngx_queue_t queue; - uint64_t id; - ngx_connection_t *connection; - ngx_uint_t *npushing; -} ngx_http_v3_push_t; - - static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); -static void ngx_http_v3_push_cleanup(void *data); static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); @@ -316,78 +307,6 @@ ngx_http_v3_uni_dummy_write_handler(ngx_ } -ngx_connection_t * -ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) -{ - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; - size_t n; - ngx_connection_t *sc; - ngx_pool_cleanup_t *cln; - ngx_http_v3_push_t *push; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 create push stream id:%uL", push_id); - - sc = ngx_quic_open_stream(c, 0); - if (sc == NULL) { - goto failed; - } - - p = buf; - p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); - n = p - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (sc->send(sc, buf, n) != (ssize_t) n) { - goto failed; - } - - cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); - if (cln == NULL) { - goto failed; - } - - h3c->npushing++; - - cln->handler = ngx_http_v3_push_cleanup; - - push = cln->data; - push->id = push_id; - push->connection = sc; - push->npushing = &h3c->npushing; - - ngx_queue_insert_tail(&h3c->pushing, &push->queue); - - return sc; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, - "failed to create push stream"); - if (sc) { - ngx_http_v3_close_uni_stream(sc); - } - - return NULL; -} - - -static void -ngx_http_v3_push_cleanup(void *data) -{ - ngx_http_v3_push_t *push = data; - - ngx_queue_remove(&push->queue); - (*push->npushing)--; -} - - static ngx_connection_t * ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) { @@ -696,19 +615,9 @@ failed: ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) { - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 MAX_PUSH_ID:%uL", max_push_id); - if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { - return NGX_HTTP_V3_ERR_ID_ERROR; - } - - h3c->max_push_id = max_push_id; - return NGX_OK; } @@ -716,14 +625,8 @@ ngx_http_v3_set_max_push_id(ngx_connecti ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) { - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); - h3c->goaway_push_id = push_id; - return NGX_OK; } @@ -731,40 +634,9 @@ ngx_http_v3_goaway(ngx_connection_t *c, ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) { - ngx_queue_t *q; - ngx_http_request_t *r; - ngx_http_v3_push_t *push; - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 CANCEL_PUSH:%uL", push_id); - if (push_id >= h3c->next_push_id) { - return NGX_HTTP_V3_ERR_ID_ERROR; - } - - for (q = ngx_queue_head(&h3c->pushing); - q != ngx_queue_sentinel(&h3c->pushing); - q = ngx_queue_next(q)) - { - push = (ngx_http_v3_push_t *) q; - - if (push->id != push_id) { - continue; - } - - r = push->connection->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 cancel push"); - - ngx_http_finalize_request(r, NGX_HTTP_CLOSE); - - break; - } - return NGX_OK; } diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h --- a/src/http/v3/ngx_http_v3_uni.h +++ b/src/http/v3/ngx_http_v3_uni.h @@ -17,8 +17,6 @@ void ngx_http_v3_init_uni_stream(ngx_connection_t *c); ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); -ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, - uint64_t push_id); ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id); ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); From arut at nginx.com Sun May 14 13:38:55 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 14 May 2023 17:38:55 +0400 Subject: [PATCH 4 of 4] Removed README In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1684045884 -14400 # Sun May 14 10:31:24 2023 +0400 # Branch quic # Node ID d8272b84031bea1940ef8a5b8e2f79ec6a2dcfc1 # Parent 49a8edf7bf31b78681399cd7e93a8516788607dd Removed README. diff --git a/README b/README deleted file mode 100644 --- a/README +++ /dev/null @@ -1,319 +0,0 @@ -Experimental QUIC support for nginx ------------------------------------ - -1. Introduction -2. Building from sources -3. Configuration -4. Directives -5. Clients -6. Troubleshooting -7. Contributing -8. Links - -1. Introduction - - This is an experimental QUIC [1] / HTTP/3 [2] support for nginx. - - The code is developed in a separate "quic" branch available - at https://hg.nginx.org/nginx-quic. Currently it is based - on nginx mainline 1.23.x. We merge new nginx releases into - this branch regularly. - - The project code base is under the same BSD license as nginx. - - The code is currently at a beta level of quality, however - there are several production deployments with it. - - NGINX Development Team is working on improving HTTP/3 support to - integrate it into the main NGINX codebase. Thus, expect further - updates of this code, including features, changes in behaviour, - bug fixes, and refactoring. NGINX Development team will be - grateful for any feedback and code submissions. - - Please contact NGINX Development Team via nginx-devel mailing list [3]. - - What works now: - - IETF QUIC version 1 is supported. Internet drafts are no longer supported. - - nginx should be able to respond to HTTP/3 requests over QUIC and - it should be possible to upload and download big files without errors. - - + The handshake completes successfully - + One endpoint can update keys and its peer responds correctly - + 0-RTT data is being received and acted on - + Connection is established using TLS Resume Ticket - + A handshake that includes a Retry packet completes successfully - + Stream data is being exchanged and ACK'ed - + An H3 transaction succeeded - + One or both endpoints insert entries into dynamic table and - subsequently reference them from header blocks - + Version Negotiation packet is sent to client with unknown version - + Lost packets are detected and retransmitted properly - + Clients may migrate to new address - -2. Building from sources - - The build is configured using the configure command. - Refer to http://nginx.org/en/docs/configure.html for details. - - When configuring nginx, it's possible to enable QUIC and HTTP/3 - using the following new configuration option: - - --with-http_v3_module - enable QUIC and HTTP/3 - - A library that provides QUIC support is recommended to build nginx, there - are several of those available on the market: - + BoringSSL [4] - + LibreSSL [5] - + QuicTLS [6] - - Alternatively, nginx can be configured with OpenSSL compatibility - layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is - enabled by default if native QUIC support is not detected. - 0-RTT is not supported in OpenSSL compatibility mode. - - Clone the NGINX QUIC repository - - $ hg clone -b quic https://hg.nginx.org/nginx-quic - $ cd nginx-quic - - Use the following command to configure nginx with BoringSSL [4] - - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../boringssl/include" \ - --with-ld-opt="-L../boringssl/build/ssl \ - -L../boringssl/build/crypto" - $ make - - Alternatively, nginx can be configured with QuicTLS [6] - - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../quictls/build/include" \ - --with-ld-opt="-L../quictls/build/lib" - - Alternatively, nginx can be configured with a modern version - of LibreSSL [7] - - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../libressl/build/include" \ - --with-ld-opt="-L../libressl/build/lib" - -3. Configuration - - The HTTP "listen" directive got a new option "quic" which enables - QUIC as client transport protocol instead of TCP. - - Along with "quic", it's also possible to specify "reuseport" - option [8] to make it work properly with multiple workers. - - To enable address validation: - - quic_retry on; - - To enable 0-RTT: - - ssl_early_data on; - - To enable GSO (Generic Segmentation Offloading): - - quic_gso on; - - To set host key for various tokens: - - quic_host_key ; - - QUIC requires TLSv1.3 protocol, which is enabled by the default - by "ssl_protocols" directive. - - By default, GSO Linux-specific optimization [10] is disabled. - Enable it in case a corresponding network interface is configured to - support GSO. - - A number of directives were added that configure HTTP/3: - - http3 - http3_hq - http3_stream_buffer_size - http3_max_concurrent_streams - - In http, an additional variable is available: $http3. - The value of $http3 is "h3" for HTTP/3 connections, - "hq" for hq connections, or an empty string otherwise. - -Example configuration: - - http { - log_format quic '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http3"'; - - access_log logs/access.log quic; - - server { - # for better compatibility it's recommended - # to use the same port for quic and https - listen 8443 quic reuseport; - listen 8443 ssl; - - ssl_certificate certs/example.com.crt; - ssl_certificate_key certs/example.com.key; - - location / { - # required for browsers to direct them into quic port - add_header Alt-Svc 'h3=":8443"; ma=86400'; - } - } - } - -4. Directives - - Syntax: quic_bpf on | off; - Default: quic_bpf off; - Context: main - - Enables routing of QUIC packets using eBPF. - When enabled, this allows to support QUIC connection migration. - The directive is only supported on Linux 5.7+. - - - Syntax: quic_retry on | off; - Default: quic_retry off; - Context: http, server - - Enables the QUIC Address Validation feature. This includes: - - sending a new token in a Retry packet or a NEW_TOKEN frame - - validating a token received in the Initial packet - - - Syntax: quic_gso on | off; - Default: quic_gso off; - Context: http, server - - Enables sending in optimized batch mode using segmentation offloading. - Optimized sending is only supported on Linux featuring UDP_SEGMENT. - - - Syntax: quic_host_key file; - Default: - - Context: http, server - - Specifies a file with the secret key used to encrypt stateless reset and - address validation tokens. By default, a randomly generated key is used. - - - Syntax: quic_active_connection_id_limit number; - Default: quic_active_connection_id_limit 2; - Context: http, server - - Sets the QUIC active_connection_id_limit transport parameter value. - This is the maximum number of connection IDs we are willing to store. - - - Syntax: http3_stream_buffer_size size; - Default: http3_stream_buffer_size 64k; - Context: http, server - - Sets buffer size for reading and writing of the QUIC STREAM payload. - The buffer size is used to calculate initial flow control limits - in the following QUIC transport parameters: - - initial_max_data - - initial_max_stream_data_bidi_local - - initial_max_stream_data_bidi_remote - - initial_max_stream_data_uni - - - Syntax: http3_max_concurrent_streams number; - Default: http3_max_concurrent_streams 128; - Context: http, server - - Sets the maximum number of concurrent HTTP/3 streams in a connection. - - - Syntax: http3 on | off; - Default: http3 on; - Context: http, server - - Enables HTTP/3 protocol negotiation. - - - Syntax: http3_hq on | off; - Default: http3_hq off; - Context: http, server - - Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests. - -5. Clients - - * Browsers - - Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1) - - Beware of strange issues: sometimes browser may decide to ignore QUIC - Cache clearing/restart might help. Always check access.log and - error.log to make sure the browser is using HTTP/3 and not TCP https. - - * Console clients - - Known to work: ngtcp2, firefox's neqo and chromium's console clients: - - $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html - - $ ./neqo-client https://127.0.0.1:8443/ - - $ chromium-build/out/my_build/quic_client http://example.com:8443 - - - In case everyhing is right, the access log should show something like: - - 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" - "nghttp3/ngtcp2 client" "quic" - - -6. Troubleshooting - - Here are some tips that may help to identify problems: - - + Ensure nginx is built with proper SSL library that supports QUIC - - + Ensure nginx is using the proper SSL library in runtime - (`nginx -V` shows what it's using) - - + Ensure a client is actually sending requests over QUIC - (see "Clients" section about browsers and cache) - - We recommend to start with simple console client like ngtcp2 - to ensure the server is configured properly before trying - with real browsers that may be very picky with certificates, - for example. - - + Build nginx with debug support [9] and check the debug log. - It should contain all details about connection and why it - failed. All related messages contain "quic " prefix and can - be easily filtered out. - - + For a deeper investigation, please enable additional debugging - in src/event/quic/ngx_event_quic_connection.h: - - #define NGX_QUIC_DEBUG_PACKETS - #define NGX_QUIC_DEBUG_FRAMES - #define NGX_QUIC_DEBUG_ALLOC - #define NGX_QUIC_DEBUG_CRYPTO - -7. Contributing - - Please refer to - http://nginx.org/en/docs/contributing_changes.html - -8. Links - - [1] https://datatracker.ietf.org/doc/html/rfc9000 - [2] https://datatracker.ietf.org/doc/html/rfc9114 - [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel - [4] https://boringssl.googlesource.com/boringssl/ - [5] https://www.libressl.org/ - [6] https://github.com/quictls/openssl - [7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0 - [8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen - [9] https://nginx.org/en/docs/debugging_log.html - [10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf From mdounin at mdounin.ru Sun May 14 14:40:43 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sun, 14 May 2023 17:40:43 +0300 Subject: [PATCH] Added $http2_stream_id In-Reply-To: References: Message-ID: Hello! On Fri, May 12, 2023 at 03:37:52AM +0100, J Carter wrote: > # HG changeset patch > # User jordanc.carter at outlook.com > # Date 1683858766 -3600 > # Fri May 12 03:32:46 2023 +0100 > # Node ID de1a1b4141e827984cbd0d2feb97f870c32ff289 > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > Added $http2_stream_id > > Useful for tracing multiplexed requests from client logs or pcaps > captured between client and nginx, to nginx's own access logs. > > Also useful for matching multiplexed request's access log entries to > debug level error logs - which is particularly difficult to do. Thanks for the patch, but I would rather not. Consider using $connection_requests variable to identify individual requests within a connection, or the $request_id variable to identify requests globally. These do no depend on the particular protocol used and can be universally used for both HTTP/1.x and HTTP/2. -- Maxim Dounin http://mdounin.ru/ From jordanc.carter at outlook.com Sun May 14 17:48:06 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 14 May 2023 18:48:06 +0100 Subject: [PATCH] Added $http2_stream_id In-Reply-To: References: Message-ID: Hello, On Sun, 14 May 2023 17:40:43 +0300 Maxim Dounin wrote: > Hello! > > On Fri, May 12, 2023 at 03:37:52AM +0100, J Carter wrote: > > > # HG changeset patch > > # User jordanc.carter at outlook.com > > # Date 1683858766 -3600 > > # Fri May 12 03:32:46 2023 +0100 > > # Node ID de1a1b4141e827984cbd0d2feb97f870c32ff289 > > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > > Added $http2_stream_id > > > > Useful for tracing multiplexed requests from client logs or pcaps > > captured between client and nginx, to nginx's own access logs. > > > > Also useful for matching multiplexed request's access log entries to > > debug level error logs - which is particularly difficult to do. > > Thanks for the patch, but I would rather not. > > Consider using $connection_requests variable to identify > individual requests within a connection, > or the $request_id > variable to identify requests globally. These do no depend on the > particular protocol used and can be universally used for both > HTTP/1.x and HTTP/2. > Thanks for the reply. I hadn't considered $connection_requests. Yes that would work fine for my use-case with some log processing ($connection_requests * 2 - 1) One thought does come to mind, although it won't effect my use-case - This may not work if server push is used as that would increment stream id, but presumably would not increment connection->requests (I'd need to check that though). From mdounin at mdounin.ru Sun May 14 18:52:40 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sun, 14 May 2023 21:52:40 +0300 Subject: [PATCH 04 of 11] Tests: fixed server_tokens tests for build names with spaces In-Reply-To: References: <605cab711606724e5879.1681702288@vm-bsd.mdounin.ru> Message-ID: Hello! On Thu, May 11, 2023 at 03:48:48PM +0400, Sergey Kandaurov wrote: > > > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > > > # HG changeset patch > > # User Maxim Dounin > > # Date 1681702253 -10800 > > # Mon Apr 17 06:30:53 2023 +0300 > > # Node ID 605cab711606724e5879e8a81d5d21797e5ddcfb > > # Parent f704912ed09f3494a815709710c3744b0adca50b > > Tests: fixed server_tokens tests for build names with spaces. > > > > Build names can contain spaces, and previously used pattern, "--build=(\S+)", > > failed to properly match such build names. Instead, now we simply test > > that some build name is provided in the Server header. Further, the > > in the Server header and error pages Updated, thanks. [...] -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Sun May 14 21:11:26 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 15 May 2023 00:11:26 +0300 Subject: [PATCH 06 of 11] Tests: reworked mail SSL tests to use IO::Socket::SSL In-Reply-To: <7B021E84-DF45-4E33-B45B-F85EED930FB8@nginx.com> References: <20d603cd3cbeab891271.1681702290@vm-bsd.mdounin.ru> <7B021E84-DF45-4E33-B45B-F85EED930FB8@nginx.com> Message-ID: Hello! On Thu, May 11, 2023 at 06:39:32PM +0400, Sergey Kandaurov wrote: > > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > > > # HG changeset patch > > # User Maxim Dounin > > # Date 1681702257 -10800 > > # Mon Apr 17 06:30:57 2023 +0300 > > # Node ID 20d603cd3cbeab89127108fe9cb6dffd0e9469e8 > > # Parent a8e22a3212da945e9060d4233905eb6de1399d34 > > Tests: reworked mail SSL tests to use IO::Socket::SSL. > > > > Relevant infrastructure is provided in Test::Nginx::IMAP (and also POP3 > > and SMTP for completeness). This also ensures that SSL handshake and > > various read operations are guarded with timeouts. > > > > [..] > > > diff --git a/mail_ssl_conf_command.t b/mail_ssl_conf_command.t > > --- a/mail_ssl_conf_command.t > > +++ b/mail_ssl_conf_command.t > > @@ -16,6 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin > > > > use lib 'lib'; > > use Test::Nginx; > > +use Test::Nginx::IMAP; > > > > ############################################################################### > > > > @@ -24,15 +25,8 @@ select STDOUT; $| = 1; > > > > local $SIG{PIPE} = 'IGNORE'; > > > > -eval { > > - require Net::SSLeay; > > - Net::SSLeay::load_error_strings(); > > - Net::SSLeay::SSLeay_add_ssl_algorithms(); > > - Net::SSLeay::randomize(); > > -}; > > -plan(skip_all => 'Net::SSLeay not installed') if $@; > > - > > -my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap openssl:1.0.2/) > > +my $t = Test::Nginx->new() > > + ->has(qw/mail mail_ssl imap openssl:1.0.2 socket_ssl_reused/) > > ->has_daemon('openssl'); > > > > plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); > > @@ -50,7 +44,7 @@ mail { > > auth_http http://127.0.0.1:8080; # unused > > > > server { > > - listen 127.0.0.1:8443 ssl; > > + listen 127.0.0.1:8993 ssl; > > Please avoid using an upper half of 8000 .. 8999 range for TCP tests. Still, correct port for IMAPS (IMAP over SSL) is 993, and proper mapping to the 8xxx range would be 8993. > The reason is a deficiency in automatic port selection used for > parallel testing, see Test::Nginx::port(). Currently, ports are > selected in the reversed order for TCP and UDP for a reason: > another socket type for the same port is used as a lock. > But that gives a race if you try to concurrently select the same > port for both TCP and UDP. > Currently, this is worked around by splitting the range: > bottom half is used for TCP, upper half is used for UDP. > > Luckily, nginx retries bind/listen on NGX_EADDRINUSE up to 5 times, > but this doesn't always work. I tend to think this shouldn't be a practical issue unless the same initial port number is used for both TCP and UDP tests. Commits 1236:93f749c1d5c5 and 1237:e4974af3fb12 suggests this was actually the case when this split was introduced. That is, failure to use proper port numbers matching the protocol being tested was the real reason for the issue observed with automatic port selection. And I would rather not use this issue as an excuse to not use proper port numbers. If that's will be demonstrated to be a practical problem even if initial port numbers are different, a better solution might be to allocate UDP ports in the 9xxx range. This will make it possible to use proper port numbers in all cases. -- Maxim Dounin http://mdounin.ru/ From jordanc.carter at outlook.com Sun May 14 22:59:35 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 14 May 2023 23:59:35 +0100 Subject: [PATCH] Added $http2_stream_id In-Reply-To: References: Message-ID: Hello, >On Sun, 14 May 2023 18:48:06 +0100 >J Carter wrote: > Hello, > > On Sun, 14 May 2023 17:40:43 +0300 > Maxim Dounin wrote: > > > Hello! > > > > On Fri, May 12, 2023 at 03:37:52AM +0100, J Carter wrote: > > > > > # HG changeset patch > > > # User jordanc.carter at outlook.com > > > # Date 1683858766 -3600 > > > # Fri May 12 03:32:46 2023 +0100 > > > # Node ID de1a1b4141e827984cbd0d2feb97f870c32ff289 > > > # Parent b71e69247483631bd8fc79a47cc32b762625b1fb > > > Added $http2_stream_id > > > > > > Useful for tracing multiplexed requests from client logs or pcaps > > > captured between client and nginx, to nginx's own access logs. > > > > > > Also useful for matching multiplexed request's access log entries > > > to debug level error logs - which is particularly difficult to do. > > > > Thanks for the patch, but I would rather not. > > > > Consider using $connection_requests variable to identify > > individual requests within a connection, > > or the $request_id > > variable to identify requests globally. These do no depend on the > > particular protocol used and can be universally used for both > > HTTP/1.x and HTTP/2. > > > > Thanks for the reply. > > I hadn't considered $connection_requests. Yes that would work fine > for my use-case with some log processing ($connection_requests * 2 - > 1) > > One thought does come to mind, although it won't effect my use-case - > This may not work if server push is used as that would increment > stream id, but presumably would not increment connection->requests > (I'd need to check that though). After some additional testing with $connection_requests it appears to not be suitable method of obtaining stream id in access_logs. The issue is 1) Stream id and connection->requests are incremented on stream / request initiation. 2) Access logs are written on request finalization. 3) New streams may be initiated at any time. 3) Requests are not necessarily finalized in initiation order. Therefore making any assumptions as to the stream id associated with a request from to the current value of connection->requests at finalization time is impossible. I'd ask that this patch is reconsidered. From arut at nginx.com Tue May 16 12:39:38 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Tue, 16 May 2023 16:39:38 +0400 Subject: [PATCH 2 of 3] HTTP/2: "http2" directive In-Reply-To: <71CADCEB-5BD1-4F67-AE85-581943918D5B@nginx.com> References: <735f9e501922e4b0a1b2.1675781431@arut-laptop> <20230209120234.rttyjviwir4sneed@N00W24XTQX> <20230209123359.i6fosl5r6tdhpvhv@N00W24XTQX> <71CADCEB-5BD1-4F67-AE85-581943918D5B@nginx.com> Message-ID: <20230516123938.rgrmdpern3i5hq5n@N00W24XTQX> Hi, On Thu, Feb 09, 2023 at 07:56:55PM +0400, Sergey Kandaurov wrote: > > > On 9 Feb 2023, at 16:33, Roman Arutyunyan wrote: > > > > Hi, > > > > On Thu, Feb 09, 2023 at 04:02:34PM +0400, Roman Arutyunyan wrote: > > > > [..] > > > >> 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 > >> @@ -318,12 +318,6 @@ ngx_http_init_connection(ngx_connection_ > >> rev->handler = ngx_http_wait_request_handler; > >> c->write->handler = ngx_http_empty_handler; > >> > >> -#if (NGX_HTTP_V2) > >> - if (hc->addr_conf->http2) { > >> - rev->handler = ngx_http_v2_init; > >> - } > >> -#endif > >> - > >> #if (NGX_HTTP_V3) > >> if (hc->addr_conf->quic) { > >> ngx_http_v3_init_stream(c); > >> @@ -383,6 +377,9 @@ ngx_http_wait_request_handler(ngx_event_ > >> ngx_buf_t *b; > >> ngx_connection_t *c; > >> ngx_http_connection_t *hc; > >> +#if (NGX_HTTP_V2) > >> + ngx_http_v2_srv_conf_t *h2scf; > >> +#endif > >> ngx_http_core_srv_conf_t *cscf; > >> > >> c = rev->data; > >> @@ -429,6 +426,8 @@ ngx_http_wait_request_handler(ngx_event_ > >> b->end = b->last + size; > >> } > >> > >> + size = b->end - b->last; > >> + > >> n = c->recv(c, b->last, size); > >> > >> if (n == NGX_AGAIN) { > >> @@ -443,12 +442,16 @@ ngx_http_wait_request_handler(ngx_event_ > >> return; > >> } > >> > >> - /* > >> - * We are trying to not hold c->buffer's memory for an idle connection. > >> - */ > >> - > >> - if (ngx_pfree(c->pool, b->start) == NGX_OK) { > >> - b->start = NULL; > >> + if (b->pos == b->last) { > >> + > >> + /* > >> + * We are trying to not hold c->buffer's memory for an > >> + * idle connection. > >> + */ > >> + > >> + if (ngx_pfree(c->pool, b->start) == NGX_OK) { > >> + b->start = NULL; > >> + } > >> } > >> > >> return; > >> @@ -489,10 +492,34 @@ ngx_http_wait_request_handler(ngx_event_ > >> } > >> } > >> > >> + ngx_reusable_connection(c, 0); > >> + > >> +#if (NGX_HTTP_V2) > >> + > >> + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); > >> + > >> + if (!c->ssl && (h2scf->enable || hc->addr_conf->http2)) { > > > > And one more fix for compilation with HTTP/2, but without SSL: > > > > 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 > > @@ -498,8 +498,12 @@ ngx_http_wait_request_handler(ngx_event_ > > > > h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); > > > > - if (!c->ssl && (h2scf->enable || hc->addr_conf->http2)) { > > - > > + if ((h2scf->enable || hc->addr_conf->http2) > > +#if (NGX_HTTP_SSL) > > + && !c->ssl > > +#endif > > + ) > > + { > > size = ngx_min(sizeof(NGX_HTTP_V2_PREFACE) - 1, > > (size_t) (b->last - b->pos)); > > > > I think this test needs to be replaced with !hc->ssl. > Otherwise, it would allow to establish (and keep) h2c on ssl-enabled > sockets, which we likely do not want to allow. After a series of discussions we decided to go with !hc->ssl. As a result, any non-SSL connection on an SSL port will trigger the HTTP/1 error page. Previous attempt to trigger the HTTP/2 error in case client request is recognized as HTTP/2, is discarded since the situation is unlikely. [..] -- Roman Arutyunyan -------------- next part -------------- # HG changeset patch # User Roman Arutyunyan # Date 1684240208 -14400 # Tue May 16 16:30:08 2023 +0400 # Branch quic # Node ID 4dcd2b42c23973815a6b8a7f54bbd1460c314c93 # Parent d8272b84031bea1940ef8a5b8e2f79ec6a2dcfc1 HTTP/2: "http2" directive. The directive enables HTTP/2 in the current server. The previous way to enable HTTP/2 via "listen ... http2" is now deprecated. The new approach allows to share HTTP/2 and HTTP/0.9-1.1 on the same port. For SSL connections, HTTP/2 is now selected by ALPN callback based on whether the protocol is enabled in the virtual server chosen by SNI. This however only works since OpenSSL 1.0.2h, where ALPN callback is invoked after SNI callback. For older versions of OpenSSL, HTTP/2 is enabled based on the default virtual server configuration. For plain TCP connections, HTTP/2 is now auto-detected by HTTP/2 preface, if HTTP/2 is enabled in the default virtual server. If preface is not matched, HTTP/0.9-1.1 is assumed. diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -435,6 +435,9 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t #if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif +#if (NGX_HTTP_V2) + ngx_http_v2_srv_conf_t *h2scf; +#endif #if (NGX_HTTP_V3) ngx_http_v3_srv_conf_t *h3scf; #endif @@ -456,12 +459,6 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t hc = c->data; #endif -#if (NGX_HTTP_V2) - if (hc->addr_conf->http2) { - srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; - srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; - } else -#endif #if (NGX_HTTP_V3) if (hc->addr_conf->quic) { @@ -488,8 +485,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t } else #endif { - srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; - srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; +#if (NGX_HTTP_V2) + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2scf->enable || hc->addr_conf->http2) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; + + } else +#endif + { + srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; + } } if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen, diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4176,6 +4176,11 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx if (ngx_strcmp(value[n].data, "http2") == 0) { #if (NGX_HTTP_V2) + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"listen ... http2\" directive " + "is deprecated, use " + "the \"http2\" directive instead"); + lsopt.http2 = 1; continue; #else 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 @@ -318,12 +318,6 @@ ngx_http_init_connection(ngx_connection_ rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; -#if (NGX_HTTP_V2) - if (hc->addr_conf->http2) { - rev->handler = ngx_http_v2_init; - } -#endif - #if (NGX_HTTP_V3) if (hc->addr_conf->quic) { ngx_http_v3_init_stream(c); @@ -383,6 +377,9 @@ ngx_http_wait_request_handler(ngx_event_ ngx_buf_t *b; ngx_connection_t *c; ngx_http_connection_t *hc; +#if (NGX_HTTP_V2) + ngx_http_v2_srv_conf_t *h2scf; +#endif ngx_http_core_srv_conf_t *cscf; c = rev->data; @@ -429,6 +426,8 @@ ngx_http_wait_request_handler(ngx_event_ b->end = b->last + size; } + size = b->end - b->last; + n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { @@ -443,12 +442,16 @@ ngx_http_wait_request_handler(ngx_event_ return; } - /* - * We are trying to not hold c->buffer's memory for an idle connection. - */ - - if (ngx_pfree(c->pool, b->start) == NGX_OK) { - b->start = NULL; + if (b->pos == b->last) { + + /* + * We are trying to not hold c->buffer's memory for an + * idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } } return; @@ -489,10 +492,34 @@ ngx_http_wait_request_handler(ngx_event_ } } + ngx_reusable_connection(c, 0); + +#if (NGX_HTTP_V2) + + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (!hc->ssl && (h2scf->enable || hc->addr_conf->http2)) { + + size = ngx_min(sizeof(NGX_HTTP_V2_PREFACE) - 1, + (size_t) (b->last - b->pos)); + + if (ngx_memcmp(b->pos, NGX_HTTP_V2_PREFACE, size) == 0) { + + if (size == sizeof(NGX_HTTP_V2_PREFACE) - 1) { + ngx_http_v2_init(rev); + return; + } + + c->log->action = "waiting for request"; + ngx_post_event(rev, &ngx_posted_events); + return; + } + } + +#endif + c->log->action = "reading client request line"; - ngx_reusable_connection(c, 0); - c->data = ngx_http_create_request(c); if (c->data == NULL) { ngx_http_close_connection(c); @@ -808,13 +835,16 @@ ngx_http_ssl_handshake_handler(ngx_conne #if (NGX_HTTP_V2 \ && defined TLSEXT_TYPE_application_layer_protocol_negotiation) { - unsigned int len; - const unsigned char *data; - ngx_http_connection_t *hc; + unsigned int len; + const unsigned char *data; + ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; hc = c->data; - if (hc->addr_conf->http2) { + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2scf->enable || hc->addr_conf->http2) { SSL_get0_alpn_selected(c->ssl->connection, &data, &len); diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -63,8 +63,6 @@ static void ngx_http_v2_handle_connectio static void ngx_http_v2_lingering_close(ngx_connection_t *c); static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev); -static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, - u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, @@ -232,6 +230,7 @@ static ngx_http_v2_parse_header_t ngx_h void ngx_http_v2_init(ngx_event_t *rev) { + u_char *p, *end; ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; @@ -314,8 +313,7 @@ ngx_http_v2_init(ngx_event_t *rev) return; } - h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol - : ngx_http_v2_state_preface; + h2c->state.handler = ngx_http_v2_state_preface; ngx_queue_init(&h2c->waiting); ngx_queue_init(&h2c->dependencies); @@ -333,7 +331,23 @@ ngx_http_v2_init(ngx_event_t *rev) } c->idle = 1; - ngx_reusable_connection(c, 0); + + if (c->buffer) { + p = c->buffer->pos; + end = c->buffer->last; + + do { + p = h2c->state.handler(h2c, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + h2c->total_bytes += p - c->buffer->pos; + c->buffer->pos = p; + } ngx_http_v2_read_handler(rev); } @@ -847,31 +861,10 @@ ngx_http_v2_lingering_close_handler(ngx_ static u_char * -ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, - u_char *end) -{ - ngx_log_t *log; - - log = h2c->connection->log; - log->action = "reading PROXY protocol"; - - pos = ngx_proxy_protocol_read(h2c->connection, pos, end); - - log->action = "processing HTTP/2 connection"; - - if (pos == NULL) { - return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); - } - - return ngx_http_v2_state_preface(h2c, pos, end); -} - - -static u_char * ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - static const u_char preface[] = "PRI * HTTP/2.0\r\n"; + static const u_char preface[] = NGX_HTTP_V2_PREFACE_START; if ((size_t) (end - pos) < sizeof(preface) - 1) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_preface); @@ -892,7 +885,7 @@ static u_char * ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - static const u_char preface[] = "\r\nSM\r\n\r\n"; + static const u_char preface[] = NGX_HTTP_V2_PREFACE_END; if ((size_t) (end - pos) < sizeof(preface) - 1) { return ngx_http_v2_state_save(h2c, pos, end, @@ -3943,10 +3936,22 @@ static void ngx_http_v2_run_request(ngx_http_request_t *r) { ngx_connection_t *fc; + ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_connection_t *h2c; fc = r->connection; + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); + + if (!h2scf->enable && !r->http_connection->addr_conf->http2) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + goto failed; + } + if (ngx_http_v2_construct_request_line(r) != NGX_OK) { goto failed; } diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -64,6 +64,16 @@ typedef u_char *(*ngx_http_v2_handler_pt typedef struct { + ngx_flag_t enable; + size_t pool_size; + ngx_uint_t concurrent_streams; + ngx_uint_t concurrent_pushes; + size_t preread_size; + ngx_uint_t streams_index_mask; +} ngx_http_v2_srv_conf_t; + + +typedef struct { ngx_str_t name; ngx_str_t value; } ngx_http_v2_header_t; @@ -408,9 +418,17 @@ ngx_int_t ngx_http_v2_table_size(ngx_htt #define NGX_HTTP_V2_USER_AGENT_INDEX 58 #define NGX_HTTP_V2_VARY_INDEX 59 +#define NGX_HTTP_V2_PREFACE_START "PRI * HTTP/2.0\r\n" +#define NGX_HTTP_V2_PREFACE_END "\r\nSM\r\n\r\n" +#define NGX_HTTP_V2_PREFACE NGX_HTTP_V2_PREFACE_START \ + NGX_HTTP_V2_PREFACE_END + u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_uint_t lower); +extern ngx_module_t ngx_http_v2_module; + + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --git a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c --- a/src/http/v2/ngx_http_v2_module.c +++ b/src/http/v2/ngx_http_v2_module.c @@ -75,6 +75,13 @@ static ngx_conf_post_t ngx_http_v2_chun static ngx_command_t ngx_http_v2_commands[] = { + { ngx_string("http2"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, enable), + NULL }, + { ngx_string("http2_recv_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -314,6 +321,8 @@ ngx_http_v2_create_srv_conf(ngx_conf_t * return NULL; } + h2scf->enable = NGX_CONF_UNSET; + h2scf->pool_size = NGX_CONF_UNSET_SIZE; h2scf->concurrent_streams = NGX_CONF_UNSET_UINT; @@ -333,6 +342,8 @@ ngx_http_v2_merge_srv_conf(ngx_conf_t *c ngx_http_v2_srv_conf_t *prev = parent; ngx_http_v2_srv_conf_t *conf = child; + ngx_conf_merge_value(conf->enable, prev->enable, 0); + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); ngx_conf_merge_uint_value(conf->concurrent_streams, diff --git a/src/http/v2/ngx_http_v2_module.h b/src/http/v2/ngx_http_v2_module.h --- a/src/http/v2/ngx_http_v2_module.h +++ b/src/http/v2/ngx_http_v2_module.h @@ -21,15 +21,6 @@ typedef struct { typedef struct { - size_t pool_size; - ngx_uint_t concurrent_streams; - ngx_uint_t concurrent_pushes; - size_t preread_size; - ngx_uint_t streams_index_mask; -} ngx_http_v2_srv_conf_t; - - -typedef struct { size_t chunk_size; ngx_flag_t push_preload; @@ -39,7 +30,4 @@ typedef struct { } ngx_http_v2_loc_conf_t; -extern ngx_module_t ngx_http_v2_module; - - #endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */ From xeioex at nginx.com Wed May 17 04:01:44 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 17 May 2023 04:01:44 +0000 Subject: [njs] Removed unneeded variable after fd956d2a25a3. Message-ID: details: https://hg.nginx.org/njs/rev/ce29debb0b3d branches: changeset: 2117:ce29debb0b3d user: Dmitry Volyntsev date: Tue May 16 20:58:23 2023 -0700 description: Removed unneeded variable after fd956d2a25a3. diffstat: external/njs_query_string_module.c | 6 ------ 1 files changed, 0 insertions(+), 6 deletions(-) diffs (37 lines): diff -r 1f84f3c34bb0 -r ce29debb0b3d external/njs_query_string_module.c --- a/external/njs_query_string_module.c Wed May 10 22:36:53 2023 -0700 +++ b/external/njs_query_string_module.c Tue May 16 20:58:23 2023 -0700 @@ -124,7 +124,6 @@ njs_query_string_decode(njs_vm_t *vm, nj size_t size) { u_char *dst; - size_t length; uint32_t cp; njs_int_t ret; njs_chb_t chain; @@ -156,7 +155,6 @@ njs_query_string_decode(njs_vm_t *vm, nj njs_utf8_decode_init(&ctx); cp = 0; - length = 0; p = start; end = p + size; @@ -190,8 +188,6 @@ njs_query_string_decode(njs_vm_t *vm, nj } njs_chb_written(&chain, njs_utf8_encode(dst, cp) - dst); - - length++; } if (njs_slow_path(cp == NJS_UNICODE_CONTINUE)) { @@ -202,8 +198,6 @@ njs_query_string_decode(njs_vm_t *vm, nj njs_chb_written(&chain, njs_utf8_encode(dst, NJS_UNICODE_REPLACEMENT) - dst); - - length++; } ret = njs_vm_value_string_create_chb(vm, value, &chain); From xeioex at nginx.com Wed May 17 04:01:45 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 17 May 2023 04:01:45 +0000 Subject: [njs] Always use a sharp (#) symbol as the sed separator. Message-ID: details: https://hg.nginx.org/njs/rev/71fa2f4b6adb branches: changeset: 2118:71fa2f4b6adb user: "Sergey A. Osokin" date: Fri May 12 19:38:29 2023 -0400 description: Always use a sharp (#) symbol as the sed separator. sed(1) command line utility may fail with the following error: sed: 1: "s, at EXTRA_LIBS@,-lm -L ...": bad in substitute command: '-' when a replacement for @EXTRA_LIBS@ contains a comma symbol. diffstat: auto/libxml2 | 2 +- auto/make | 14 +++++++------- auto/options | 4 ++-- auto/zlib | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diffs (83 lines): diff -r ce29debb0b3d -r 71fa2f4b6adb auto/libxml2 --- a/auto/libxml2 Tue May 16 20:58:23 2023 -0700 +++ b/auto/libxml2 Fri May 12 19:38:29 2023 -0400 @@ -26,7 +26,7 @@ if [ $NJS_LIBXML2 = YES ]; then # pkg-config njs_feature="libxml2 via pkg-config" - njs_feature_incs=`pkg-config libxml-2.0 --cflags | sed -n -e 's/.*-I *\([^ ][^ ]*\).*/\1/p'` + njs_feature_incs=`pkg-config libxml-2.0 --cflags | sed -n -e 's#.*-I *\([^ ][^ ]*\).*#\1#p'` njs_feature_libs=`pkg-config libxml-2.0 --libs` . auto/feature diff -r ce29debb0b3d -r 71fa2f4b6adb auto/make --- a/auto/make Tue May 16 20:58:23 2023 -0700 +++ b/auto/make Fri May 12 19:38:29 2023 -0400 @@ -54,7 +54,7 @@ NJS_STATIC_LINK = ${AR} -r -c NJS_LINK = ${CC} ${NJS_LD_OPT} NJS_CFLAGS = ${NJS_CFLAGS} ${NJS_CC_OPT} ${CFLAGS} -NJS_VER = $(grep NJS_VERSION src/njs.h | sed -e 's/.*"\(.*\)".*/\1/') +NJS_VER = $(grep NJS_VERSION src/njs.h | sed -e 's#.*"\(.*\)".*#\1#') NJS_TYPES_VER = \$(NJS_VER) NPM = npm @@ -275,7 +275,7 @@ cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/ts/package.json: $njs_ts_deps cp -fr ts $NJS_BUILD_DIR/ cp LICENSE $NJS_BUILD_DIR/ts/ - sed 's/__VERSION__/"\$(NJS_TYPES_VER)"/' \\ + sed 's#__VERSION__#"\$(NJS_TYPES_VER)"#' \\ ts/package.json > $NJS_BUILD_DIR/ts/package.json $NJS_BUILD_DIR/ts/node_modules: $NJS_BUILD_DIR/ts/package.json @@ -320,11 +320,11 @@ cat << END >> $NJS_MAKEFILE pc: $NJS_BUILD_DIR/njs.pc $NJS_BUILD_DIR/njs.pc: $NJS_BUILD_DIR/njs_auto_config.h - sed -e "s, at PREFIX@,$(pwd)/$NJS_BUILD_DIR," \\ - -e "s, at LIBDIR@,$(pwd)/$NJS_BUILD_DIR," \\ - -e "s, at CFLAGS@,-I$(pwd)/$NJS_BUILD_DIR -I$(pwd)/src," \\ - -e "s, at VERSION@,\$(NJS_VER)," \\ - -e "s, at EXTRA_LIBS@,-lm $NJS_LIBS $NJS_LIB_AUX_LIBS," \\ + sed -e "s#@PREFIX@#$(pwd)/$NJS_BUILD_DIR#" \\ + -e "s#@LIBDIR@#$(pwd)/$NJS_BUILD_DIR#" \\ + -e "s#@CFLAGS@#-I$(pwd)/$NJS_BUILD_DIR -I$(pwd)/src#" \\ + -e "s#@VERSION@#\$(NJS_VER)#" \\ + -e "s#@EXTRA_LIBS@#-lm $NJS_LIBS $NJS_LIB_AUX_LIBS#" \\ src/njs.pc.in > \$@ END diff -r ce29debb0b3d -r 71fa2f4b6adb auto/options --- a/auto/options Tue May 16 20:58:23 2023 -0700 +++ b/auto/options Fri May 12 19:38:29 2023 -0400 @@ -28,7 +28,7 @@ NJS_CONFIGURE_OPTIONS= for njs_option do case "$njs_option" in - -*=*) value=`echo "$njs_option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;; + -*=*) value=`echo "$njs_option" | sed -e 's#[-_a-zA-Z0-9]*=##'` ;; *) value="" ;; esac @@ -70,7 +70,7 @@ do ;; esac - njs_opt=`echo $njs_option | sed -e "s/\(--[^=]*=\)\(.* .*\)/\1'\2'/"` + njs_opt=`echo $njs_option | sed -e "s#\(--[^=]*=\)\(.* .*\)#\1'\2'#"` NJS_CONFIGURE_OPTIONS="$NJS_CONFIGURE_OPTIONS $njs_opt" diff -r ce29debb0b3d -r 71fa2f4b6adb auto/zlib --- a/auto/zlib Tue May 16 20:58:23 2023 -0700 +++ b/auto/zlib Fri May 12 19:38:29 2023 -0400 @@ -26,7 +26,7 @@ if [ $NJS_ZLIB = YES ]; then # pkg-config njs_feature="zlib via pkg-config" - njs_feature_incs=`pkg-config zlib --cflags | sed -n -e 's/.*-I *\([^ ][^ ]*\).*/\1/p'` + njs_feature_incs=`pkg-config zlib --cflags | sed -n -e 's#.*-I *\([^ ][^ ]*\).*#\1#p'` njs_feature_libs=`pkg-config zlib --libs` . auto/feature From xeioex at nginx.com Wed May 17 23:53:10 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 17 May 2023 23:53:10 +0000 Subject: [njs] Fixed evaluation of computed property names with function expressions. Message-ID: details: https://hg.nginx.org/njs/rev/884100020b1b branches: changeset: 2119:884100020b1b user: Dmitry Volyntsev date: Wed May 17 00:39:45 2023 -0700 description: Fixed evaluation of computed property names with function expressions. Previously, while evaluating a property name expression with a function expression as the right side, the evaluation modified the value used to compute the property name in-place. The in-place modification changes values not intended to be changed. The issue was introduced in 74d30c2d70f3 (0.7.8). This fixes #640 issue on Github. diffstat: src/njs_generator.c | 13 +++++++++++++ src/test/njs_unit_test.c | 3 +++ 2 files changed, 16 insertions(+), 0 deletions(-) diffs (43 lines): diff -r 71fa2f4b6adb -r 884100020b1b src/njs_generator.c --- a/src/njs_generator.c Fri May 12 19:38:29 2023 -0400 +++ b/src/njs_generator.c Wed May 17 00:39:45 2023 -0700 @@ -3247,6 +3247,12 @@ njs_generate_assignment_end(njs_vm_t *vm njs_generate_code(generator, njs_vmcode_2addr_t, to_prop_key, NJS_VMCODE_TO_PROPERTY_KEY, 2, property); + prop_index = njs_generate_temp_index_get(vm, generator, + property); + if (njs_slow_path(prop_index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + to_prop_key->src = property->index; to_prop_key->dst = prop_index; @@ -3277,6 +3283,13 @@ njs_generate_assignment_end(njs_vm_t *vm prop_set->object = object->index; prop_set->property = prop_index; + if (prop_index != property->index) { + ret = njs_generate_index_release(vm, generator, prop_index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + node->index = expr->index; node->temporary = expr->temporary; diff -r 71fa2f4b6adb -r 884100020b1b src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 12 19:38:29 2023 -0400 +++ b/src/test/njs_unit_test.c Wed May 17 00:39:45 2023 -0700 @@ -3922,6 +3922,9 @@ static njs_unit_test_t njs_test[] = { njs_str("var named = Symbol('xxx'); ({[named]: () => {}})[named].name"), njs_str("[xxx]") }, + { njs_str("var obj = {}; ({[obj](){}}); typeof obj"), + njs_str("object") }, + { njs_str("var called = false;" "({" " [{toString(){ if (called) throw 'OOps'; called = true; return 'a'}}](){}" From xeioex at nginx.com Wed May 17 23:53:12 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 17 May 2023 23:53:12 +0000 Subject: [njs] Fixed implicit name for a function expression declared in arrays. Message-ID: details: https://hg.nginx.org/njs/rev/af41742d63a2 branches: changeset: 2120:af41742d63a2 user: Dmitry Volyntsev date: Wed May 17 00:39:56 2023 -0700 description: Fixed implicit name for a function expression declared in arrays. diffstat: src/njs_generator.c | 9 ++++++--- src/test/njs_unit_test.c | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diffs (32 lines): diff -r 884100020b1b -r af41742d63a2 src/njs_generator.c --- a/src/njs_generator.c Wed May 17 00:39:45 2023 -0700 +++ b/src/njs_generator.c Wed May 17 00:39:56 2023 -0700 @@ -3235,9 +3235,12 @@ njs_generate_assignment_end(njs_vm_t *vm switch (lvalue->token_type) { case NJS_TOKEN_PROPERTY_INIT: - if ((expr->token_type == NJS_TOKEN_FUNCTION - || expr->token_type == NJS_TOKEN_FUNCTION_EXPRESSION - || expr->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION)) + if ((object->token_type == NJS_TOKEN_OBJECT + || (object->token_type == NJS_TOKEN_OBJECT_VALUE + && object->u.object->token_type == NJS_TOKEN_OBJECT)) + && (expr->token_type == NJS_TOKEN_FUNCTION + || expr->token_type == NJS_TOKEN_FUNCTION_EXPRESSION + || expr->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION)) { if (property->token_type == NJS_TOKEN_STRING) { njs_value_assign(&expr->u.value.data.u.lambda->name, diff -r 884100020b1b -r af41742d63a2 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Wed May 17 00:39:45 2023 -0700 +++ b/src/test/njs_unit_test.c Wed May 17 00:39:56 2023 -0700 @@ -3925,6 +3925,9 @@ static njs_unit_test_t njs_test[] = { njs_str("var obj = {}; ({[obj](){}}); typeof obj"), njs_str("object") }, + { njs_str("[function(){}][0].name"), + njs_str("") }, + { njs_str("var called = false;" "({" " [{toString(){ if (called) throw 'OOps'; called = true; return 'a'}}](){}" From xeioex at nginx.com Thu May 18 04:51:28 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 18 May 2023 04:51:28 +0000 Subject: [njs] Change: removed deprecated r.requestBody and r.responseBody. Message-ID: details: https://hg.nginx.org/njs/rev/ce344efc8b46 branches: changeset: 2121:ce344efc8b46 user: Dmitry Volyntsev date: Wed May 17 17:11:41 2023 -0700 description: Change: removed deprecated r.requestBody and r.responseBody. Both properties were deprecated since 0.5.0. diffstat: nginx/ngx_http_js_module.c | 26 -------------------------- 1 files changed, 0 insertions(+), 26 deletions(-) diffs (57 lines): diff -r af41742d63a2 -r ce344efc8b46 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Wed May 17 00:39:56 2023 -0700 +++ b/nginx/ngx_http_js_module.c Wed May 17 17:11:41 2023 -0700 @@ -603,15 +603,6 @@ static njs_external_t ngx_http_js_ext_r { .flags = NJS_EXTERN_PROPERTY, - .name.string = njs_str("requestBody"), - .u.property = { - .handler = ngx_http_js_ext_get_request_body, - .magic32 = NGX_JS_STRING | NGX_JS_DEPRECATED, - } - }, - - { - .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("requestBuffer"), .u.property = { .handler = ngx_http_js_ext_get_request_body, @@ -631,15 +622,6 @@ static njs_external_t ngx_http_js_ext_r { .flags = NJS_EXTERN_PROPERTY, - .name.string = njs_str("responseBody"), - .u.property = { - .handler = ngx_http_js_ext_get_response_body, - .magic32 = NGX_JS_STRING | NGX_JS_DEPRECATED, - } - }, - - { - .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("responseBuffer"), .u.property = { .handler = ngx_http_js_ext_get_response_body, @@ -2604,10 +2586,6 @@ ngx_http_js_ext_get_request_body(njs_vm_ ngx_http_js_ctx_t *ctx; ngx_http_request_t *r; - if (njs_vm_prop_magic32(prop) & NGX_JS_DEPRECATED) { - njs_deprecated(vm, "r.requestBody"); - } - r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); if (r == NULL) { njs_value_undefined_set(retval); @@ -3455,10 +3433,6 @@ ngx_http_js_ext_get_response_body(njs_vm ngx_http_js_ctx_t *ctx; ngx_http_request_t *r; - if (njs_vm_prop_magic32(prop) & NGX_JS_DEPRECATED) { - njs_deprecated(vm, "r.responseBody"); - } - r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); if (r == NULL) { njs_value_undefined_set(retval); From xeioex at nginx.com Thu May 18 04:51:30 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 18 May 2023 04:51:30 +0000 Subject: [njs] Modules: introduced global nginx properties. Message-ID: details: https://hg.nginx.org/njs/rev/25b55a064e42 branches: changeset: 2122:25b55a064e42 user: Dmitry Volyntsev date: Wed May 17 21:16:19 2023 -0700 description: Modules: introduced global nginx properties. The following properties were introduced: ngx.build - an optional nginx build name, corresponds to --build=name argument of configure script, by default is "". ngx.conf_file_path - the file path to current nginx configuration file. ngx.error_log_path - the file path to current error log file. ngx.prefix - the directory that keeps server files. ngx.version - the nginx version as a string, for example: "1.25.0". ngx.version_number - the nginx version as a number, for example: 1025000. diffstat: nginx/ngx_js.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 121 insertions(+), 0 deletions(-) diffs (169 lines): diff -r ce344efc8b46 -r 25b55a064e42 nginx/ngx_js.c --- a/nginx/ngx_js.c Wed May 17 17:11:41 2023 -0700 +++ b/nginx/ngx_js.c Wed May 17 21:16:19 2023 -0700 @@ -12,8 +12,20 @@ #include "ngx_js_fetch.h" +static njs_int_t ngx_js_ext_build(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +static njs_int_t ngx_js_ext_conf_file_path(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); static njs_int_t ngx_js_ext_conf_prefix(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +static njs_int_t ngx_js_ext_error_log_path(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t ngx_js_ext_prefix(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +static njs_int_t ngx_js_ext_version(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static void ngx_js_cleanup_vm(void *data); @@ -26,7 +38,26 @@ static njs_external_t ngx_js_ext_core[] { .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("build"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_build, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("conf_file_path"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_conf_file_path, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("conf_prefix"), + .enumerable = 1, .u.property = { .handler = ngx_js_ext_conf_prefix, } @@ -43,6 +74,15 @@ static njs_external_t ngx_js_ext_core[] }, { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("error_log_path"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_error_log_path, + } + }, + + { .flags = NJS_EXTERN_METHOD, .name.string = njs_str("fetch"), .writable = 1, @@ -76,6 +116,35 @@ static njs_external_t ngx_js_ext_core[] { .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("prefix"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_prefix, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("version"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_version, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("version_number"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_ext_constant, + .magic32 = nginx_version, + .magic16 = NGX_JS_NUMBER, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("WARN"), .u.property = { .handler = ngx_js_ext_constant, @@ -339,6 +408,31 @@ ngx_js_ext_flags(njs_vm_t *vm, njs_objec njs_int_t +ngx_js_ext_build(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + return njs_vm_value_string_set(vm, retval, +#ifdef NGX_BUILD + (u_char *) NGX_BUILD, + njs_strlen(NGX_BUILD) +#else + (u_char *) "", + 0 +#endif + ); +} + + +njs_int_t +ngx_js_ext_conf_file_path(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + return njs_vm_value_string_set(vm, retval, ngx_cycle->conf_file.data, + ngx_cycle->conf_file.len); +} + + +njs_int_t ngx_js_ext_conf_prefix(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { @@ -348,6 +442,33 @@ ngx_js_ext_conf_prefix(njs_vm_t *vm, njs njs_int_t +ngx_js_ext_error_log_path(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + return njs_vm_value_string_set(vm, retval, ngx_cycle->error_log.data, + ngx_cycle->error_log.len); +} + + +njs_int_t +ngx_js_ext_prefix(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + return njs_vm_value_string_set(vm, retval, ngx_cycle->prefix.data, + ngx_cycle->prefix.len); +} + + +njs_int_t +ngx_js_ext_version(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + return njs_vm_value_string_set(vm, retval, (u_char *) NGINX_VERSION, + njs_strlen(NGINX_VERSION)); +} + + +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) { From mdounin at mdounin.ru Thu May 18 15:16:25 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 18 May 2023 18:16:25 +0300 Subject: [PATCH 10 of 11] Tests: reworked http SSL tests to use IO::Socket::SSL In-Reply-To: <3EA12B7C-3EA3-4BFB-8C2A-9C13238BFD08@nginx.com> References: <2aaba5bbc0366bffe1f4.1681702294@vm-bsd.mdounin.ru> <3EA12B7C-3EA3-4BFB-8C2A-9C13238BFD08@nginx.com> Message-ID: Hello! On Thu, May 11, 2023 at 06:27:12PM +0400, Sergey Kandaurov wrote: > > On 17 Apr 2023, at 07:31, Maxim Dounin wrote: > > > > # HG changeset patch > > # User Maxim Dounin > > # Date 1681702264 -10800 > > # Mon Apr 17 06:31:04 2023 +0300 > > # Node ID 2aaba5bbc0366bffe1f468105b1185cd48efbc93 > > # Parent 90913cb36b512c45cd9a171cbb4320b12ff24b48 > > Tests: reworked http SSL tests to use IO::Socket::SSL. > > > > Relevant infrastructure is provided in Test::Nginx http() functions. > > This also ensures that SSL handshake and various read and write operations > > are guarded with timeouts. > > > > The ssl_sni_reneg.t test uses IO::Socket::SSL::_get_ssl_object() to access > > the Net::SSLeay object directly and trigger renegotation. While > > not exactly correct, this seems to be good enough for tests. > > > > Similarly, IO::Socket::SSL::_get_ssl_object() is used in ssl_stapling.t, > > since SSL_ocsp_staple_callback is called with the socket instead of the > > Net::SSLeay object. > > > > Similarly, IO::Socket::SSL::_get_ssl_object() is used in ssl_verify_client.t, > > since there seems to be no way to obtain CA list with IO::Socket::SSL. > > This is one of the reasons why it was written in Net::SSLeay. > The intention was to avoid using undocumented _get_ssl_object. Yep, these three tests required use of the undocumented (or, rather, explicitly documented to be an internal interface) _get_ssl_object(). Other tests, however, don't require use of any internal methods. For these three tests, the possible options would be: - Use _get_ssl_object() despite being an internal method. - Preserve the existing Net::SSLeay-based custom code in these tests, and make sure it properly handles SIGPIPE and other errors (existing code seems to try, but fail at least in some cases). Given that _get_ssl_object() works fine in all know versions of IO::Socket::SSL, using _get_ssl_object() seems to be the best available option. > The resulting code throughout the series is sometimes hard to read now. > Still, if you believe it is better to rewrite it in IO::Socket:SSL, > I'm ok with it. You can treat this as a positive review. > See minor comments below and in other patches. I can't say it's harder to read than the existing code. The net effect is: 59 files changed, 1015 insertions(+), 1609 deletions(-) and a lot more effort saved for fixing the custom code to properly handle SIGPIPE and other errors. > > Notable change to http() request interface is that http_end() now closes > > the socket. This is to make sure that SSL connections are properly > > closed and SSL sessions are not removed from the IO::Socket::SSL session > > cache. This affected access_log.t, which was modified accordingly. > > > > diff --git a/access_log.t b/access_log.t > > --- a/access_log.t > > +++ b/access_log.t > > @@ -161,11 +161,11 @@ http_get('/varlog?logname=0'); > > http_get('/varlog?logname=filename'); > > > > my $s = http('', start => 1); > > -http_get('/addr', socket => $s); > > my $addr = $s->sockhost(); > > my $port = $s->sockport(); > > my $saddr = $s->peerhost(); > > my $sport = $s->peerport(); > > +http_get('/addr', socket => $s); > > > > http_get('/binary'); > > > > diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm > > --- a/lib/Test/Nginx.pm > > +++ b/lib/Test/Nginx.pm > > @@ -838,13 +838,15 @@ sub http($;%) { > > my $s = http_start($request, %extra); > > > > return $s if $extra{start} or !defined $s; > > - return http_end($s); > > + return http_end($s, %extra); > > } > > > > sub http_start($;%) { > > my ($request, %extra) = @_; > > my $s; > > > > + my $port = $extra{SSL} ? 8443 : 8080; > > + > > eval { > > local $SIG{ALRM} = sub { die "timeout\n" }; > > local $SIG{PIPE} = sub { die "sigpipe\n" }; > > @@ -852,10 +854,25 @@ sub http_start($;%) { > > > > $s = $extra{socket} || IO::Socket::INET->new( > > Proto => 'tcp', > > - PeerAddr => '127.0.0.1:' . port(8080) > > + PeerAddr => '127.0.0.1:' . port($port), > > + %extra > > ) > > or die "Can't connect to nginx: $!\n"; > > > > + if ($extra{SSL}) { > > + require IO::Socket::SSL; > > + IO::Socket::SSL->start_SSL( > > + $s, > > + SSL_verify_mode => > > + IO::Socket::SSL::SSL_VERIFY_NONE(), > > + %extra > > + ) > > + or die $IO::Socket::SSL::SSL_ERROR . "\n"; > > + > > + log_in("ssl cipher: " . $s->get_cipher()); > > + log_in("ssl cert: " . $s->peer_certificate('issuer')); > > + } > > + > > log_out($request); > > $s->print($request); > > > > @@ -879,7 +896,7 @@ sub http_start($;%) { > > } > > > > sub http_end($;%) { > > - my ($s) = @_; > > + my ($s, %extra) = @_; > > extra doesn't seem to be used Yep, thanks, removed. It's a leftover from an earlier versions of the patch, which used to introduce $extra{noclose} to disable explicit closing of the socket (instead, access_log.t was modified). > > > my $reply; > > > > eval { > > @@ -890,6 +907,8 @@ sub http_end($;%) { > > local $/; > > $reply = $s->getline(); > > > > + $s->close(); > > + > > alarm(0); > > }; > > alarm(0); > > diff --git a/ssl_certificate.t b/ssl_certificate.t > > --- a/ssl_certificate.t > > +++ b/ssl_certificate.t > > @@ -17,29 +17,15 @@ use Socket qw/ CRLF /; > > BEGIN { use FindBin; chdir($FindBin::Bin); } > > > > use lib 'lib'; > > -use Test::Nginx; > > +use Test::Nginx qw/ :DEFAULT http_end /; > > > > ############################################################################### > > > > select STDERR; $| = 1; > > select STDOUT; $| = 1; > > > > -eval { > > - require Net::SSLeay; > > - Net::SSLeay::load_error_strings(); > > - Net::SSLeay::SSLeay_add_ssl_algorithms(); > > - Net::SSLeay::randomize(); > > -}; > > -plan(skip_all => 'Net::SSLeay not installed') if $@; > > - > > -eval { > > - my $ctx = Net::SSLeay::CTX_new() or die; > > - my $ssl = Net::SSLeay::new($ctx) or die; > > - Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die; > > -}; > > -plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; > > - > > -my $t = Test::Nginx->new()->has(qw/http http_ssl geo openssl:1.0.2/) > > +my $t = Test::Nginx->new() > > + ->has(qw/http http_ssl geo openssl:1.0.2 socket_ssl_sni/) > > ->has_daemon('openssl'); > > > > $t->write_file_expand('nginx.conf', <<'EOF'); > > @@ -67,6 +53,7 @@ http { > > } > > > > add_header X-SSL $ssl_server_name:$ssl_session_reused; > > + add_header X-SSL-Protocol $ssl_protocol; > > ssl_session_cache shared:SSL:1m; > > ssl_session_tickets on; > > > > @@ -177,60 +164,63 @@ like(get('password', 8083), qr/password/ > > > > # session reuse > > > > -my ($s, $ssl) = get('default', 8080); > > -my $ses = Net::SSLeay::get_session($ssl); > > - > > -like(get('default', 8080, $ses), qr/default:r/, 'session reused'); > > +my $s = session('default', 8080); > > > > TODO: { > > -# ticket key name mismatch prevents session resumption > > +local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay' > > + if $Net::SSLeay::VERSION < 1.88 && test_tls13(); > > +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' > > + if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); > > + > > +like(get('default', 8080, $s), qr/default:r/, 'session reused'); > > + > > +TODO: { > > +# automatic ticket ticket key name mismatch prevents session resumption > > "ticket" repetition > > (also a similar comment in stream_ssl_certificate.t isn't touched) Thanks, overlooked this, restored the original comment. (I tend to think the comment needs to be rewritten to properly clarify that session resumption in the test in question only works when both server{} blocks use the same session ticket keys, and therefore requires either ticket keys explicitly set or ssl_session_cache and nginx 1.23.2+, but it certainly shouldn't be in this patch.) > > local $TODO = 'not yet' unless $t->has_version('1.23.2'); > > > > -like(get('default', 8081, $ses), qr/default:r/, 'session id context match'); > > +like(get('default', 8081, $s), qr/default:r/, 'session id context match'); > > > > } > > +} > > > > -like(get('default', 8082, $ses), qr/default:\./, 'session id context distinct'); > > +like(get('default', 8082, $s), qr/default:\./, 'session id context distinct'); > > > > # errors > > > > -Net::SSLeay::ERR_clear_error(); > > -get_ssl_socket('nx', 8084); > > -ok(Net::SSLeay::ERR_peek_error(), 'no certificate'); > > +ok(!get('nx', 8084), 'no certificate'); > > IIRC this was written so to ensure it is an SSL layer error > and not some abrupt termination caused by unrelated reason. > I don't object to simplify it though, if you think it is better. Yes, understood. It is not trivial to distinguish SSL and non-SSL errors here, and I tend to think it doesn't worth the effort, and just checking for an error is good enough. Thanks for the review, pushed to http://mdounin.ru/hg/nginx-tests. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu May 18 16:09:54 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 18 May 2023 19:09:54 +0300 Subject: [PATCH] Tests: HTTP/2 tests with error_page and return In-Reply-To: References: <90aaa942972884dcd67b.1682952385@enoparse.local> Message-ID: Hello! On Thu, May 11, 2023 at 03:31:25PM +0400, Sergey Kandaurov wrote: > > > On 3 May 2023, at 01:45, Maxim Dounin wrote: > > > > Hello! > > > > On Tue, May 02, 2023 at 05:10:55PM +0400, Sergey Kandaurov wrote: > > > >>> On 2 May 2023, at 00:59, Maxim Dounin wrote: > >>> > >>> On Mon, May 01, 2023 at 06:46:25PM +0400, Sergey Kandaurov wrote: > >>> > >>>> # HG changeset patch > >>>> # User Sergey Kandaurov > >>>> # Date 1682952238 -14400 > >>>> # Mon May 01 18:43:58 2023 +0400 > >>>> # Node ID 90aaa942972884dcd67b6744fde39a154fec5d13 > >>>> # Parent 36a4563f7f005184547575f5ac4f22ef53a59c72 > >>>> Tests: HTTP/2 tests with error_page and return. > >>>> > >>>> diff --git a/h2_error_page.t b/h2_error_page.t > >>>> new file mode 100644 > >>>> --- /dev/null > >>>> +++ b/h2_error_page.t > >>>> @@ -0,0 +1,88 @@ > >>>> +#!/usr/bin/perl > >>>> + > >>>> +# (C) Sergey Kandaurov > >>>> +# (C) Nginx, Inc. > >>>> + > >>>> +# Tests for HTTP/2 protocol with error_page directive. > >>>> + > >>>> +############################################################################### > >>>> + > >>>> +use warnings; > >>>> +use strict; > >>>> + > >>>> +use Test::More; > >>>> + > >>>> +BEGIN { use FindBin; chdir($FindBin::Bin); } > >>>> + > >>>> +use lib 'lib'; > >>>> +use Test::Nginx; > >>>> +use Test::Nginx::HTTP2; > >>>> + > >>>> +############################################################################### > >>>> + > >>>> +select STDERR; $| = 1; > >>>> +select STDOUT; $| = 1; > >>>> + > >>>> +my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(2) > >>>> + ->write_file_expand('nginx.conf', <<'EOF'); > >>>> + > >>>> +%%TEST_GLOBALS%% > >>>> + > >>>> +daemon off; > >>>> + > >>>> +events { > >>>> +} > >>>> + > >>>> +http { > >>>> + %%TEST_GLOBALS_HTTP%% > >>>> + > >>>> + server { > >>>> + listen 127.0.0.1:8080 http2; > >>>> + server_name localhost; > >>>> + > >>>> + lingering_close off; > >>>> + > >>>> + error_page 400 = /close; > >>>> + > >>>> + location / { } > >>>> + > >>>> + location /close { > >>>> + return 444; > >>>> + } > >>>> + } > >>>> +} > >>>> + > >>>> +EOF > >>>> + > >>>> +$t->run(); > >>>> + > >>>> +############################################################################### > >>>> + > >>>> +my ($sid, $frames, $frame); > >>>> + > >>>> +# tests for socket leak with "return 444" in error_page > >>>> + > >>>> +# ticket #274 > >>>> + > >>>> +my $s1 = Test::Nginx::HTTP2->new(); > >>>> +$sid = $s1->new_stream({ headers => [ > >>>> + { name => ':method', value => 'GET' }, > >>>> + { name => ':path', value => '/' }, > >>>> + { name => ':authority', value => 'localhost' }]}); > >>>> +$frames = $s1->read(all => [{ type => 'RST_STREAM' }]); > >>>> + > >>>> +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; > >>>> +is($frame->{sid}, $sid, 'error 400 return 444 - missing header'); > >>> > >>> This clearly needs details about the header being missed, as well > >>> as expected and observed behaviour, not just the ticket number. > >> > >> The description is provided in associated commit logs, a proper > >> source to seek for details, tagged with appropriate ticket numbers. > >> A brief description what happens here is given above. > > > > Even assuming commits are readily available (they are not in > > most cases), commit logs and even the code changes are not enough > > to see what actually missed here: that is, it worth to mention > > lack of mandatory ":scheme" pseudo-header. > > Sure, I don't mind to add extra comments > if that provides further explanation. > > > > > Also, it might be important to mention why the test is expected to > > fail without the fix (and if it's expected to fail), and why it > > succeeds with the fix. Note that the tickets in question are > > about connection being left open, and not about RST_STREAM not > > being sent. > > Well, the connection is expected to be kept. Well, not really. You are testing a configuration with "return 444;", and this implies that the _connection_ is to be closed, not just a particular request or a stream. Further, the connection is expected to be reset when using reset_timedout_connection (https://nginx.org/r/reset_timedout_connection). This does not seem to happen now for HTTP/2 connections for some reason though (but used to happen at least with SPDY, see https://trac.nginx.org/nginx/ticket/590). This probably needs to be looked into and fixed. [...] -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu May 18 21:29:50 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 19 May 2023 00:29:50 +0300 Subject: [PATCH 1 of 4] Stream: removed QUIC support In-Reply-To: <113e2438dbd40a6c5a26.1684071532@arut-laptop> References: <113e2438dbd40a6c5a26.1684071532@arut-laptop> Message-ID: Hello! On Sun, May 14, 2023 at 05:38:52PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1684051535 -14400 > # Sun May 14 12:05:35 2023 +0400 > # Branch quic > # Node ID 113e2438dbd40a6c5a2627ed98707c1418a10fd5 > # Parent 8057e053480a49b7fbc626dc92d11f71ba4a387f > Stream: removed QUIC support. > > diff --git a/README b/README > --- a/README > +++ b/README > @@ -58,10 +58,9 @@ 2. Building from sources > Refer to http://nginx.org/en/docs/configure.html for details. > > When configuring nginx, it's possible to enable QUIC and HTTP/3 > - using the following new configuration options: > + using the following new configuration option: > > --with-http_v3_module - enable QUIC and HTTP/3 > - --with-stream_quic_module - enable QUIC in Stream > > A library that provides QUIC support is recommended to build nginx, there > are several of those available on the market: > @@ -105,9 +104,6 @@ 3. Configuration > The HTTP "listen" directive got a new option "quic" which enables > QUIC as client transport protocol instead of TCP. > > - The Stream "listen" directive got a new option "quic" which enables > - QUIC as client transport protocol instead of TCP or plain UDP. > - > Along with "quic", it's also possible to specify "reuseport" > option [8] to make it work properly with multiple workers. > > @@ -148,10 +144,6 @@ 3. Configuration > The value of $http3 is "h3" for HTTP/3 connections, > "hq" for hq connections, or an empty string otherwise. > > - In stream, an additional variable is available: $quic. > - The value of $quic is "quic" if QUIC connection is used, > - or an empty string otherwise. > - > Example configuration: > > http { > @@ -190,7 +182,7 @@ 4. Directives > > Syntax: quic_retry on | off; > Default: quic_retry off; > - Context: http | stream, server > + Context: http, server > > Enables the QUIC Address Validation feature. This includes: > - sending a new token in a Retry packet or a NEW_TOKEN frame > @@ -199,7 +191,7 @@ 4. Directives > > Syntax: quic_gso on | off; > Default: quic_gso off; > - Context: http | stream, server > + Context: http, server > > Enables sending in optimized batch mode using segmentation offloading. > Optimized sending is only supported on Linux featuring UDP_SEGMENT. > @@ -207,7 +199,7 @@ 4. Directives > > Syntax: quic_host_key file; > Default: - > - Context: http | stream, server > + Context: http, server > > Specifies a file with the secret key used to encrypt stateless reset and > address validation tokens. By default, a randomly generated key is used. > @@ -215,24 +207,12 @@ 4. Directives > > Syntax: quic_active_connection_id_limit number; > Default: quic_active_connection_id_limit 2; > - Context: http | stream, server > + Context: http, server > > Sets the QUIC active_connection_id_limit transport parameter value. > This is the maximum number of connection IDs we are willing to store. > > > - Syntax: quic_timeout time; > - Default: quic_timeout 60s; > - Context: stream, server > - > - Defines a timeout used to negotiate the QUIC idle timeout. > - In the http module, it is taken from the keepalive_timeout directive. > - > - > - Syntax: quic_stream_buffer_size size; > - Default: quic_stream_buffer_size 64k; > - Context: stream, server > - > Syntax: http3_stream_buffer_size size; > Default: http3_stream_buffer_size 64k; > Context: http, server > diff --git a/auto/modules b/auto/modules > --- a/auto/modules > +++ b/auto/modules > @@ -1075,20 +1075,6 @@ if [ $STREAM != NO ]; then > > ngx_module_incs= > > - if [ $STREAM_QUIC = YES ]; then > - USE_OPENSSL_QUIC=YES > - have=NGX_STREAM_QUIC . auto/have > - STREAM_SSL=YES > - > - ngx_module_name=ngx_stream_quic_module > - ngx_module_deps=src/stream/ngx_stream_quic_module.h > - ngx_module_srcs=src/stream/ngx_stream_quic_module.c > - ngx_module_libs= > - ngx_module_link=$STREAM_QUIC > - > - . auto/module > - fi > - > if [ $STREAM_SSL = YES ]; then > USE_OPENSSL=YES > have=NGX_STREAM_SSL . auto/have > diff --git a/auto/options b/auto/options > --- a/auto/options > +++ b/auto/options > @@ -119,7 +119,6 @@ MAIL_SMTP=YES > > STREAM=NO > STREAM_SSL=NO > -STREAM_QUIC=NO > STREAM_REALIP=NO > STREAM_LIMIT_CONN=YES > STREAM_ACCESS=YES > @@ -324,7 +323,6 @@ use the \"--with-mail_ssl_module\" optio > --with-stream) STREAM=YES ;; > --with-stream=dynamic) STREAM=DYNAMIC ;; > --with-stream_ssl_module) STREAM_SSL=YES ;; > - --with-stream_quic_module) STREAM_QUIC=YES ;; > --with-stream_realip_module) STREAM_REALIP=YES ;; > --with-stream_geoip_module) STREAM_GEOIP=YES ;; > --with-stream_geoip_module=dynamic) > @@ -547,7 +545,6 @@ cat << END > --with-stream enable TCP/UDP proxy module > --with-stream=dynamic enable dynamic TCP/UDP proxy module > --with-stream_ssl_module enable ngx_stream_ssl_module > - --with-stream_quic_module enable ngx_stream_quic_module > --with-stream_realip_module enable ngx_stream_realip_module > --with-stream_geoip_module enable ngx_stream_geoip_module > --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_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 > @@ -518,22 +518,9 @@ ngx_stream_optimize_servers(ngx_conf_t * > ls->reuseport = addr[i].opt.reuseport; > #endif > > -#if (NGX_STREAM_QUIC) > - > - ls->quic = addr[i].opt.quic; > - > - if (ls->quic) { > - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, > - ngx_quic_rbtree_insert_value); > - } > - > -#endif > - > #if !(NGX_WIN32) > - if (!ls->quic) { > - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, > - ngx_udp_rbtree_insert_value); > - } > + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, > + ngx_udp_rbtree_insert_value); > #endif > > stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); > @@ -594,9 +581,6 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx > #if (NGX_STREAM_SSL) > addrs[i].conf.ssl = addr[i].opt.ssl; > #endif > -#if (NGX_STREAM_QUIC) > - addrs[i].conf.quic = addr[i].opt.quic; > -#endif > addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; > addrs[i].conf.addr_text = addr[i].opt.addr_text; > } > @@ -632,9 +616,6 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ng > #if (NGX_STREAM_SSL) > addrs6[i].conf.ssl = addr[i].opt.ssl; > #endif > -#if (NGX_STREAM_QUIC) > - addrs6[i].conf.quic = addr[i].opt.quic; > -#endif > addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; > addrs6[i].conf.addr_text = addr[i].opt.addr_text; > } > 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 > @@ -16,10 +16,6 @@ > #include > #endif > > -#if (NGX_STREAM_QUIC) > -#include > -#endif > - > > typedef struct ngx_stream_session_s ngx_stream_session_t; > > @@ -55,7 +51,6 @@ typedef struct { > unsigned bind:1; > unsigned wildcard:1; > unsigned ssl:1; > - unsigned quic:1; > #if (NGX_HAVE_INET6) > unsigned ipv6only:1; > #endif > @@ -81,7 +76,6 @@ typedef struct { > ngx_stream_conf_ctx_t *ctx; > ngx_str_t addr_text; > unsigned ssl:1; > - unsigned quic:1; > unsigned proxy_protocol:1; > } ngx_stream_addr_conf_t; > > 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 > @@ -760,29 +760,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, n > #endif > } > > - if (ngx_strcmp(value[i].data, "quic") == 0) { > -#if (NGX_STREAM_QUIC) > - ngx_stream_ssl_conf_t *sslcf; > - > - sslcf = ngx_stream_conf_get_module_srv_conf(cf, > - ngx_stream_ssl_module); > - > - sslcf->listen = 1; > - sslcf->file = cf->conf_file->file.name.data; > - sslcf->line = cf->conf_file->line; > - > - ls->quic = 1; > - ls->type = SOCK_DGRAM; > - > - continue; > -#else > - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > - "the \"quic\" parameter requires " > - "ngx_stream_quic_module"); > - return NGX_CONF_ERROR; > -#endif > - } > - > if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { > > if (ngx_strcmp(&value[i].data[13], "on") == 0) { > @@ -894,12 +871,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, n > } > #endif > > -#if (NGX_STREAM_SSL && NGX_STREAM_QUIC) > - if (ls->ssl && ls->quic) { > - return "\"ssl\" parameter is incompatible with \"quic\""; > - } > -#endif > - > if (ls->so_keepalive) { > return "\"so_keepalive\" parameter is incompatible with \"udp\""; > } > 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 > @@ -129,10 +129,6 @@ ngx_stream_init_connection(ngx_connectio > s->ssl = addr_conf->ssl; > #endif > > -#if (NGX_STREAM_QUIC) > - s->ssl |= addr_conf->quic; > -#endif > - > if (c->buffer) { > s->received += c->buffer->last - c->buffer->pos; > } > @@ -177,21 +173,6 @@ ngx_stream_init_connection(ngx_connectio > s->start_sec = tp->sec; > s->start_msec = tp->msec; > > -#if (NGX_STREAM_QUIC) > - > - if (addr_conf->quic) { > - ngx_quic_conf_t *qcf; > - > - if (c->quic == NULL) { > - qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, > - ngx_stream_quic_module); > - ngx_quic_run(c, qcf); > - return; > - } > - } > - > -#endif > - > rev = c->read; > rev->handler = ngx_stream_session_handler; > > diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c > --- a/src/stream/ngx_stream_proxy_module.c > +++ b/src/stream/ngx_stream_proxy_module.c > @@ -1772,21 +1772,6 @@ ngx_stream_proxy_process(ngx_stream_sess > if (dst->type == SOCK_STREAM && pscf->half_close > && src->read->eof && !u->half_closed && !dst->buffered) > { > - > -#if (NGX_STREAM_QUIC) > - if (dst->quic) { > - > - if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN) > - != NGX_OK) > - { > - ngx_stream_proxy_finalize(s, > - NGX_STREAM_INTERNAL_SERVER_ERROR); > - return; > - } > - > - } else > -#endif > - > if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) { > ngx_connection_error(c, ngx_socket_errno, > ngx_shutdown_socket_n " failed"); > diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c > deleted file mode 100644 > --- a/src/stream/ngx_stream_quic_module.c > +++ /dev/null > @@ -1,343 +0,0 @@ > - > -/* > - * Copyright (C) Nginx, Inc. > - * Copyright (C) Roman Arutyunyan > - */ > - > - > -#include > -#include > -#include > - > - > -static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, > - ngx_stream_variable_value_t *v, uintptr_t data); > -static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); > -static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); > -static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, > - void *child); > -static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, > - void *conf); > - > - > -static ngx_command_t ngx_stream_quic_commands[] = { > - > - { ngx_string("quic_timeout"), > - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, > - ngx_conf_set_msec_slot, > - NGX_STREAM_SRV_CONF_OFFSET, > - offsetof(ngx_quic_conf_t, timeout), > - NULL }, > - > - { ngx_string("quic_stream_buffer_size"), > - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, > - ngx_conf_set_size_slot, > - NGX_STREAM_SRV_CONF_OFFSET, > - offsetof(ngx_quic_conf_t, stream_buffer_size), > - NULL }, > - > - { ngx_string("quic_retry"), > - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, > - ngx_conf_set_flag_slot, > - NGX_STREAM_SRV_CONF_OFFSET, > - offsetof(ngx_quic_conf_t, retry), > - NULL }, > - > - { ngx_string("quic_gso"), > - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, > - ngx_conf_set_flag_slot, > - NGX_STREAM_SRV_CONF_OFFSET, > - offsetof(ngx_quic_conf_t, gso_enabled), > - NULL }, > - > - { ngx_string("quic_host_key"), > - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, > - ngx_stream_quic_host_key, > - NGX_STREAM_SRV_CONF_OFFSET, > - 0, > - NULL }, > - > - { ngx_string("quic_active_connection_id_limit"), > - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, > - ngx_conf_set_num_slot, > - NGX_STREAM_SRV_CONF_OFFSET, > - offsetof(ngx_quic_conf_t, active_connection_id_limit), > - NULL }, > - > - ngx_null_command > -}; > - > - > -static ngx_stream_module_t ngx_stream_quic_module_ctx = { > - ngx_stream_quic_add_variables, /* preconfiguration */ > - NULL, /* postconfiguration */ > - > - NULL, /* create main configuration */ > - NULL, /* init main configuration */ > - > - ngx_stream_quic_create_srv_conf, /* create server configuration */ > - ngx_stream_quic_merge_srv_conf, /* merge server configuration */ > -}; > - > - > -ngx_module_t ngx_stream_quic_module = { > - NGX_MODULE_V1, > - &ngx_stream_quic_module_ctx, /* module context */ > - ngx_stream_quic_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 ngx_stream_variable_t ngx_stream_quic_vars[] = { > - > - { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 }, > - > - ngx_stream_null_variable > -}; > - > -static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic"); > - > - > -static ngx_int_t > -ngx_stream_variable_quic(ngx_stream_session_t *s, > - ngx_stream_variable_value_t *v, uintptr_t data) > -{ > - if (s->connection->quic) { > - > - v->len = 4; > - v->valid = 1; > - v->no_cacheable = 1; > - v->not_found = 0; > - v->data = (u_char *) "quic"; > - return NGX_OK; > - } > - > - v->not_found = 1; > - > - return NGX_OK; > -} > - > - > -static ngx_int_t > -ngx_stream_quic_add_variables(ngx_conf_t *cf) > -{ > - ngx_stream_variable_t *var, *v; > - > - for (v = ngx_stream_quic_vars; v->name.len; v++) { > - var = ngx_stream_add_variable(cf, &v->name, v->flags); > - if (var == NULL) { > - return NGX_ERROR; > - } > - > - var->get_handler = v->get_handler; > - var->data = v->data; > - } > - > - return NGX_OK; > -} > - > - > -static void * > -ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) > -{ > - ngx_quic_conf_t *conf; > - > - conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); > - if (conf == NULL) { > - return NULL; > - } > - > - /* > - * set by ngx_pcalloc(): > - * > - * conf->host_key = { 0, NULL } > - * conf->stream_close_code = 0; > - * conf->stream_reject_code_uni = 0; > - * conf->stream_reject_code_bidi= 0; > - */ > - > - conf->timeout = NGX_CONF_UNSET_MSEC; > - conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; > - conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; > - conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; > - > - conf->retry = NGX_CONF_UNSET; > - conf->gso_enabled = NGX_CONF_UNSET; > - > - conf->active_connection_id_limit = NGX_CONF_UNSET_UINT; > - > - return conf; > -} > - > - > -static char * > -ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) > -{ > - ngx_quic_conf_t *prev = parent; > - ngx_quic_conf_t *conf = child; > - > - ngx_stream_ssl_conf_t *scf; > - > - ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); > - > - ngx_conf_merge_size_value(conf->stream_buffer_size, > - prev->stream_buffer_size, > - 65536); > - > - ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi, > - prev->max_concurrent_streams_bidi, 16); > - > - ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni, > - prev->max_concurrent_streams_uni, 3); > - > - ngx_conf_merge_value(conf->retry, prev->retry, 0); > - ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); > - > - ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); > - > - ngx_conf_merge_uint_value(conf->active_connection_id_limit, > - conf->active_connection_id_limit, > - 2); > - > - if (conf->host_key.len == 0) { > - > - conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; > - conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); > - if (conf->host_key.data == NULL) { > - return NGX_CONF_ERROR; > - } > - > - if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) > - <= 0) > - { > - return NGX_CONF_ERROR; > - } > - } > - > - if (ngx_quic_derive_key(cf->log, "av_token_key", > - &conf->host_key, &ngx_stream_quic_salt, > - conf->av_token_key, NGX_QUIC_AV_KEY_LEN) > - != NGX_OK) > - { > - return NGX_CONF_ERROR; > - } > - > - if (ngx_quic_derive_key(cf->log, "sr_token_key", > - &conf->host_key, &ngx_stream_quic_salt, > - conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) > - != NGX_OK) > - { > - return NGX_CONF_ERROR; > - } > - > - scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); > - conf->ssl = &scf->ssl; > - > - return NGX_CONF_OK; > -} > - > - > -static char * > -ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) > -{ > - ngx_quic_conf_t *qcf = conf; > - > - u_char *buf; > - size_t size; > - ssize_t n; > - ngx_str_t *value; > - ngx_file_t file; > - ngx_file_info_t fi; > - > - if (qcf->host_key.len) { > - return "is duplicate"; > - } > - > - buf = NULL; > -#if (NGX_SUPPRESS_WARN) > - size = 0; > -#endif > - > - value = cf->args->elts; > - > - if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { > - return NGX_CONF_ERROR; > - } > - > - ngx_memzero(&file, sizeof(ngx_file_t)); > - file.name = value[1]; > - file.log = cf->log; > - > - file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); > - > - if (file.fd == NGX_INVALID_FILE) { > - ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, > - ngx_open_file_n " \"%V\" failed", &file.name); > - return NGX_CONF_ERROR; > - } > - > - if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { > - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, > - ngx_fd_info_n " \"%V\" failed", &file.name); > - goto failed; > - } > - > - size = ngx_file_size(&fi); > - > - if (size == 0) { > - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > - "\"%V\" zero key size", &file.name); > - goto failed; > - } > - > - buf = ngx_pnalloc(cf->pool, size); > - if (buf == NULL) { > - goto failed; > - } > - > - n = ngx_read_file(&file, buf, size, 0); > - > - if (n == NGX_ERROR) { > - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, > - ngx_read_file_n " \"%V\" failed", &file.name); > - goto failed; > - } > - > - if ((size_t) n != size) { > - ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, > - ngx_read_file_n " \"%V\" returned only " > - "%z bytes instead of %uz", &file.name, n, size); > - goto failed; > - } > - > - qcf->host_key.data = buf; > - qcf->host_key.len = n; > - > - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { > - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, > - ngx_close_file_n " \"%V\" failed", &file.name); > - } > - > - return NGX_CONF_OK; > - > -failed: > - > - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { > - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, > - ngx_close_file_n " \"%V\" failed", &file.name); > - } > - > - if (buf) { > - ngx_explicit_memzero(buf, size); > - } > - > - return NGX_CONF_ERROR; > -} > diff --git a/src/stream/ngx_stream_quic_module.h b/src/stream/ngx_stream_quic_module.h > deleted file mode 100644 > --- a/src/stream/ngx_stream_quic_module.h > +++ /dev/null > @@ -1,20 +0,0 @@ > - > -/* > - * Copyright (C) Roman Arutyunyan > - * Copyright (C) Nginx, Inc. > - */ > - > - > -#ifndef _NGX_STREAM_QUIC_H_INCLUDED_ > -#define _NGX_STREAM_QUIC_H_INCLUDED_ > - > - > -#include > -#include > -#include > - > - > -extern ngx_module_t ngx_stream_quic_module; > - > - > -#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */ > 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 > @@ -9,10 +9,6 @@ > #include > #include > > -#if (NGX_QUIC_OPENSSL_COMPAT) > -#include > -#endif > - > > typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, > ngx_pool_t *pool, ngx_str_t *s); > @@ -1199,10 +1195,7 @@ ngx_stream_ssl_conf_command_check(ngx_co > static ngx_int_t > ngx_stream_ssl_init(ngx_conf_t *cf) > { > - ngx_uint_t i; > - ngx_stream_listen_t *listen; > ngx_stream_handler_pt *h; > - ngx_stream_ssl_conf_t *scf; > ngx_stream_core_main_conf_t *cmcf; > > cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); > @@ -1214,29 +1207,5 @@ ngx_stream_ssl_init(ngx_conf_t *cf) > > *h = ngx_stream_ssl_handler; > > - listen = cmcf->listen.elts; > - > - for (i = 0; i < cmcf->listen.nelts; i++) { > - if (!listen[i].quic) { > - continue; > - } > - > - scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; > - > -#if (NGX_QUIC_OPENSSL_COMPAT) > - if (ngx_quic_compat_init(cf, scf->ssl.ctx) != NGX_OK) { > - return NGX_ERROR; > - } > -#endif > - > - if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > - "\"ssl_protocols\" must enable TLSv1.3 for " > - "the \"listen ... quic\" directive in %s:%ui", > - scf->file, scf->line); > - return NGX_ERROR; > - } > - } > - > return NGX_OK; > } > diff --git a/src/stream/ngx_stream_write_filter_module.c b/src/stream/ngx_stream_write_filter_module.c > --- a/src/stream/ngx_stream_write_filter_module.c > +++ b/src/stream/ngx_stream_write_filter_module.c > @@ -277,12 +277,7 @@ ngx_stream_write_filter(ngx_stream_sessi > *out = chain; > > if (chain) { > - if (c->shared > -#if (NGX_STREAM_QUIC) > - && c->quic == NULL > -#endif > - ) > - { > + if (c->shared) { > ngx_log_error(NGX_LOG_ALERT, c->log, 0, > "shared connection is busy"); > return NGX_ERROR; Looks good. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu May 18 21:30:05 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 19 May 2023 00:30:05 +0300 Subject: [PATCH 2 of 4] Common tree insert function for QUIC and UDP connections In-Reply-To: References: Message-ID: Hello! On Sun, May 14, 2023 at 05:38:53PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1684053011 -14400 > # Sun May 14 12:30:11 2023 +0400 > # Branch quic > # Node ID adcc6d8acfd47c1344b121fceeb94fbcc3f8b5c0 > # Parent 113e2438dbd40a6c5a2627ed98707c1418a10fd5 > Common tree insert function for QUIC and UDP connections. > > Previously, ngx_udp_rbtree_insert_value() was used for plain UDP and > ngx_quic_rbtree_insert_value() was used for QUIC. Because of this it was > impossible to initialize connection tree in ngx_create_listening() since > this function is not aware what kind of listening it creates. > > Now ngx_udp_rbtree_insert_value() is used for both QUIC and UDP. To make > is possible, a generic key field is added to ngx_udp_connection_t. It keeps > client address for UDP and connection ID for QUIC. > > diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c > --- a/src/core/ngx_connection.c > +++ b/src/core/ngx_connection.c > @@ -72,6 +72,10 @@ ngx_create_listening(ngx_conf_t *cf, str > > ngx_memcpy(ls->addr_text.data, text, len); > > +#if !(NGX_WIN32) > + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value); > +#endif > + > ls->fd = (ngx_socket_t) -1; > ls->type = SOCK_STREAM; > > diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c > --- a/src/event/ngx_event_udp.c > +++ b/src/event/ngx_event_udp.c > @@ -417,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_n > udpt = (ngx_udp_connection_t *) temp; > ct = udpt->connection; > > - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, > - ct->sockaddr, ct->socklen, 1); > + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, > + udp->key.len, udpt->key.len); > > if (rc == 0 && c->listening->wildcard) { > rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, > @@ -471,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection > ngx_crc32_final(hash); > > udp->node.key = hash; > + udp->key.data = (u_char *) c->sockaddr; > + udp->key.len = c->socklen; > > cln = ngx_pool_cleanup_add(c->pool, 0); > if (cln == NULL) { > diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h > --- a/src/event/ngx_event_udp.h > +++ b/src/event/ngx_event_udp.h > @@ -27,6 +27,7 @@ struct ngx_udp_connection_s { > ngx_rbtree_node_t node; > ngx_connection_t *connection; > ngx_buf_t *buffer; > + ngx_str_t key; > }; > > > diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h > --- a/src/event/quic/ngx_event_quic.h > +++ b/src/event/quic/ngx_event_quic.h > @@ -111,8 +111,6 @@ struct ngx_quic_stream_s { > > > void ngx_quic_recvmsg(ngx_event_t *ev); > -void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, > - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); > void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); > ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); > void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, > diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c > --- a/src/event/quic/ngx_event_quic_socket.c > +++ b/src/event/quic/ngx_event_quic_socket.c > @@ -179,6 +179,7 @@ ngx_quic_listen(ngx_connection_t *c, ngx > > qsock->udp.connection = c; > qsock->udp.node.key = ngx_crc32_long(id.data, id.len); > + qsock->udp.key = id; > > ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); > > diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c > --- a/src/event/quic/ngx_event_quic_udp.c > +++ b/src/event/quic/ngx_event_quic_udp.c > @@ -365,59 +365,6 @@ ngx_quic_close_accepted_connection(ngx_c > } > > > -void > -ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, > - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) > -{ > - ngx_int_t rc; > - ngx_connection_t *c, *ct; > - ngx_rbtree_node_t **p; > - ngx_quic_socket_t *qsock, *qsockt; > - > - for ( ;; ) { > - > - if (node->key < temp->key) { > - > - p = &temp->left; > - > - } else if (node->key > temp->key) { > - > - p = &temp->right; > - > - } else { /* node->key == temp->key */ > - > - qsock = (ngx_quic_socket_t *) node; > - c = qsock->udp.connection; > - > - qsockt = (ngx_quic_socket_t *) temp; > - ct = qsockt->udp.connection; > - > - rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id, > - qsock->sid.len, qsockt->sid.len); > - > - if (rc == 0 && c->listening->wildcard) { > - rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, > - ct->local_sockaddr, ct->local_socklen, 1); > - } > - > - p = (rc < 0) ? &temp->left : &temp->right; > - } > - > - if (*p == sentinel) { > - break; > - } > - > - temp = *p; > - } > - > - *p = node; > - node->parent = temp; > - node->left = sentinel; > - node->right = sentinel; > - ngx_rbt_red(node); > -} > - > - > static ngx_connection_t * > ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, > struct sockaddr *local_sockaddr, socklen_t local_socklen) > diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c > --- a/src/http/ngx_http.c > +++ b/src/http/ngx_http.c > @@ -1883,14 +1883,7 @@ ngx_http_add_listening(ngx_conf_t *cf, n > ls->wildcard = addr->opt.wildcard; > > #if (NGX_HTTP_V3) > - > ls->quic = addr->opt.quic; > - > - if (ls->quic) { > - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, > - ngx_quic_rbtree_insert_value); > - } > - > #endif > > return ls; > 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 > @@ -518,11 +518,6 @@ ngx_stream_optimize_servers(ngx_conf_t * > ls->reuseport = addr[i].opt.reuseport; > #endif > > -#if !(NGX_WIN32) > - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, > - ngx_udp_rbtree_insert_value); > -#endif > - > stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); > if (stport == NULL) { > return NGX_CONF_ERROR; Looks good. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu May 18 21:41:32 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 19 May 2023 00:41:32 +0300 Subject: [PATCH 3 of 4] HTTP/3: removed server push support In-Reply-To: <49a8edf7bf31b7868139.1684071534@arut-laptop> References: <49a8edf7bf31b7868139.1684071534@arut-laptop> Message-ID: Hello! On Sun, May 14, 2023 at 05:38:54PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1683871330 -14400 > # Fri May 12 10:02:10 2023 +0400 > # Branch quic > # Node ID 49a8edf7bf31b78681399cd7e93a8516788607dd > # Parent adcc6d8acfd47c1344b121fceeb94fbcc3f8b5c0 > HTTP/3: removed server push support. > > diff --git a/README b/README > --- a/README > +++ b/README > @@ -135,10 +135,7 @@ 3. Configuration > http3 > http3_hq > http3_stream_buffer_size > - http3_max_concurrent_pushes > http3_max_concurrent_streams > - http3_push > - http3_push_preload > > In http, an additional variable is available: $http3. > The value of $http3 is "h3" for HTTP/3 connections, > @@ -226,13 +223,6 @@ 4. Directives > - initial_max_stream_data_uni > > > - Syntax: http3_max_concurrent_pushes number; > - Default: http3_max_concurrent_pushes 10; > - Context: http, server > - > - Limits the maximum number of concurrent push requests in a connection. > - > - > Syntax: http3_max_concurrent_streams number; > Default: http3_max_concurrent_streams 128; > Context: http, server > @@ -240,31 +230,6 @@ 4. Directives > Sets the maximum number of concurrent HTTP/3 streams in a connection. > > > - Syntax: http3_push uri | off; > - Default: http3_push off; > - Context: http, server, location > - > - Pre-emptively sends (pushes) a request to the specified uri along with > - the response to the original request. Only relative URIs with absolute > - path will be processed, for example: > - > - http3_push /static/css/main.css; > - > - The uri value can contain variables. > - > - Several http3_push directives can be specified on the same configuration > - level. The off parameter cancels the effect of the http3_push directives > - inherited from the previous configuration level. > - > - > - Syntax: http3_push_preload on | off; > - Default: http3_push_preload off; > - Context: http, server, location > - > - Enables automatic conversion of preload links specified in the “Link” > - response header fields into push requests. > - > - > Syntax: http3 on | off; > Default: http3 on; > Context: http, server > diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c > --- a/src/http/v3/ngx_http_v3.c > +++ b/src/http/v3/ngx_http_v3.c > @@ -30,11 +30,7 @@ ngx_http_v3_init_session(ngx_connection_ > goto failed; > } > > - h3c->max_push_id = (uint64_t) -1; > - h3c->goaway_push_id = (uint64_t) -1; > - > ngx_queue_init(&h3c->blocked); > - ngx_queue_init(&h3c->pushing); > > h3c->keepalive.log = c->log; > h3c->keepalive.data = c; > diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h > --- a/src/http/v3/ngx_http_v3.h > +++ b/src/http/v3/ngx_http_v3.h > @@ -106,19 +106,11 @@ typedef struct { > ngx_flag_t enable_hq; > size_t max_table_capacity; > ngx_uint_t max_blocked_streams; > - ngx_uint_t max_concurrent_pushes; > ngx_uint_t max_concurrent_streams; > ngx_quic_conf_t quic; > } ngx_http_v3_srv_conf_t; > > > -typedef struct { > - ngx_flag_t push_preload; > - ngx_flag_t push; > - ngx_array_t *pushes; > -} ngx_http_v3_loc_conf_t; > - > - > struct ngx_http_v3_parse_s { > size_t header_limit; > ngx_http_v3_parse_headers_t headers; > @@ -136,11 +128,6 @@ struct ngx_http_v3_session_s { > ngx_queue_t blocked; > ngx_uint_t nblocked; > > - ngx_queue_t pushing; > - ngx_uint_t npushing; > - uint64_t next_push_id; > - uint64_t max_push_id; > - uint64_t goaway_push_id; > uint64_t next_request_id; > > off_t total_bytes; > diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c > --- a/src/http/v3/ngx_http_v3_filter_module.c > +++ b/src/http/v3/ngx_http_v3_filter_module.c > @@ -36,17 +36,6 @@ typedef struct { > > > static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); > -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, > - ngx_chain_t ***out); > -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, > - ngx_str_t *path, ngx_chain_t ***out); > -static ngx_int_t ngx_http_v3_create_push_request( > - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); > -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, > - const char *name, ngx_str_t *value); > -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); > -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, > - ngx_str_t *path, uint64_t push_id); > static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, > ngx_chain_t *in); > static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, > @@ -155,14 +144,6 @@ ngx_http_v3_header_filter(ngx_http_reque > out = NULL; > ll = &out; > > - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 > - && r->method != NGX_HTTP_HEAD) > - { > - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { > - return NGX_ERROR; > - } > - } > - > len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); > > if (r->headers_out.status == NGX_HTTP_OK) { > @@ -607,672 +588,6 @@ ngx_http_v3_header_filter(ngx_http_reque > > > static ngx_int_t > -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) > -{ > - u_char *start, *end, *last; > - ngx_str_t path; > - ngx_int_t rc; > - ngx_uint_t i, push; > - ngx_table_elt_t *h; > - ngx_http_v3_loc_conf_t *h3lcf; > - ngx_http_complex_value_t *pushes; > - > - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); > - > - if (h3lcf->pushes) { > - pushes = h3lcf->pushes->elts; > - > - for (i = 0; i < h3lcf->pushes->nelts; i++) { > - > - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { > - return NGX_ERROR; > - } > - > - if (path.len == 0) { > - continue; > - } > - > - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { > - continue; > - } > - > - rc = ngx_http_v3_push_resource(r, &path, out); > - > - if (rc == NGX_ERROR) { > - return NGX_ERROR; > - } > - > - if (rc == NGX_ABORT) { > - return NGX_OK; > - } > - > - /* NGX_OK, NGX_DECLINED */ > - } > - } > - > - if (!h3lcf->push_preload) { > - return NGX_OK; > - } > - > - for (h = r->headers_out.link; h; h = h->next) { > - > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > - "http3 parse link: \"%V\"", &h->value); > - > - start = h->value.data; > - end = h->value.data + h->value.len; > - > - next_link: > - > - while (start < end && *start == ' ') { start++; } > - > - if (start == end || *start++ != '<') { > - continue; > - } > - > - while (start < end && *start == ' ') { start++; } > - > - for (last = start; last < end && *last != '>'; last++) { > - /* void */ > - } > - > - if (last == start || last == end) { > - continue; > - } > - > - path.len = last - start; > - path.data = start; > - > - start = last + 1; > - > - while (start < end && *start == ' ') { start++; } > - > - if (start == end) { > - continue; > - } > - > - if (*start == ',') { > - start++; > - goto next_link; > - } > - > - if (*start++ != ';') { > - continue; > - } > - > - last = ngx_strlchr(start, end, ','); > - > - if (last == NULL) { > - last = end; > - } > - > - push = 0; > - > - for ( ;; ) { > - > - while (start < last && *start == ' ') { start++; } > - > - if (last - start >= 6 > - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) > - { > - start += 6; > - > - if (start == last || *start == ' ' || *start == ';') { > - push = 0; > - break; > - } > - > - goto next_param; > - } > - > - if (last - start >= 11 > - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) > - { > - start += 11; > - > - if (start == last || *start == ' ' || *start == ';') { > - push = 1; > - } > - > - goto next_param; > - } > - > - if (last - start >= 4 > - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) > - { > - start += 4; > - > - while (start < last && *start == ' ') { start++; } > - > - if (start == last || *start++ != '"') { > - goto next_param; > - } > - > - for ( ;; ) { > - > - while (start < last && *start == ' ') { start++; } > - > - if (last - start >= 7 > - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) > - { > - start += 7; > - > - if (start < last && (*start == ' ' || *start == '"')) { > - push = 1; > - break; > - } > - } > - > - while (start < last && *start != ' ' && *start != '"') { > - start++; > - } > - > - if (start == last) { > - break; > - } > - > - if (*start == '"') { > - break; > - } > - > - start++; > - } > - } > - > - next_param: > - > - start = ngx_strlchr(start, last, ';'); > - > - if (start == NULL) { > - break; > - } > - > - start++; > - } > - > - if (push) { > - while (path.len && path.data[path.len - 1] == ' ') { > - path.len--; > - } > - } > - > - if (push && path.len > - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) > - { > - rc = ngx_http_v3_push_resource(r, &path, out); > - > - if (rc == NGX_ERROR) { > - return NGX_ERROR; > - } > - > - if (rc == NGX_ABORT) { > - return NGX_OK; > - } > - > - /* NGX_OK, NGX_DECLINED */ > - } > - > - if (last < end) { > - start = last + 1; > - goto next_link; > - } > - } > - > - return NGX_OK; > -} > - > - > -static ngx_int_t > -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, > - ngx_chain_t ***ll) > -{ > - uint64_t push_id; > - ngx_int_t rc; > - ngx_chain_t *cl; > - ngx_connection_t *c; > - ngx_http_v3_session_t *h3c; > - ngx_http_v3_srv_conf_t *h3scf; > - > - c = r->connection; > - h3c = ngx_http_v3_get_session(c); > - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); > - > - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", > - path, h3c->npushing, h3scf->max_concurrent_pushes, > - h3c->next_push_id, h3c->max_push_id); > - > - if (!ngx_path_separator(path->data[0])) { > - ngx_log_error(NGX_LOG_WARN, c->log, 0, > - "non-absolute path \"%V\" not pushed", path); > - return NGX_DECLINED; > - } > - > - if (h3c->max_push_id == (uint64_t) -1 > - || h3c->next_push_id > h3c->max_push_id) > - { > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 abort pushes due to max_push_id"); > - return NGX_ABORT; > - } > - > - if (h3c->goaway_push_id != (uint64_t) -1) { > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 abort pushes due to goaway"); > - return NGX_ABORT; > - } > - > - if (h3c->npushing >= h3scf->max_concurrent_pushes) { > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 abort pushes due to max_concurrent_pushes"); > - return NGX_ABORT; > - } > - > - if (r->headers_in.server.len == 0) { > - return NGX_ABORT; > - } > - > - push_id = h3c->next_push_id++; > - > - rc = ngx_http_v3_create_push_request(r, path, push_id); > - if (rc != NGX_OK) { > - return rc; > - } > - > - cl = ngx_http_v3_create_push_promise(r, path, push_id); > - if (cl == NULL) { > - return NGX_ERROR; > - } > - > - for (**ll = cl; **ll; *ll = &(**ll)->next); > - > - return NGX_OK; > -} > - > - > -static ngx_int_t > -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, > - uint64_t push_id) > -{ > - ngx_connection_t *c, *pc; > - ngx_http_request_t *r; > - ngx_http_log_ctx_t *ctx; > - ngx_http_connection_t *hc, *phc; > - ngx_http_core_srv_conf_t *cscf; > - > - pc = pr->connection; > - > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, > - "http3 create push request id:%uL", push_id); > - > - c = ngx_http_v3_create_push_stream(pc, push_id); > - if (c == NULL) { > - return NGX_ABORT; > - } > - > -#if (NGX_STAT_STUB) > - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); > -#endif > - > - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); > - if (hc == NULL) { > - ngx_http_close_connection(c); > - return NGX_ERROR; > - } > - > - phc = ngx_http_quic_get_connection(pc); > - ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); > - c->data = hc; > - > - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); > - if (ctx == NULL) { > - ngx_http_close_connection(c); > - return NGX_ERROR; > - } > - > - ctx->connection = c; > - ctx->request = NULL; > - ctx->current_request = NULL; > - > - c->log->handler = pc->log->handler; > - c->log->data = ctx; > - c->log->action = "processing pushed request headers"; > - > - c->log_error = NGX_ERROR_INFO; > - > - r = ngx_http_create_request(c); > - if (r == NULL) { > - ngx_http_close_connection(c); > - return NGX_ERROR; > - } > - > - c->data = r; > - > - ngx_str_set(&r->http_protocol, "HTTP/3.0"); > - > - r->http_version = NGX_HTTP_VERSION_30; > - r->method_name = ngx_http_core_get_method; > - r->method = NGX_HTTP_GET; > - > - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); > - > - r->header_in = ngx_create_temp_buf(r->pool, > - cscf->client_header_buffer_size); > - if (r->header_in == NULL) { > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > - return NGX_ERROR; > - } > - > - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, > - sizeof(ngx_table_elt_t)) > - != NGX_OK) > - { > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > - return NGX_ERROR; > - } > - > - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; > - > - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); > - if (r->schema.data == NULL) { > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > - return NGX_ERROR; > - } > - > - r->schema.len = pr->schema.len; > - > - r->uri_start = ngx_pstrdup(r->pool, path); > - if (r->uri_start == NULL) { > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > - return NGX_ERROR; > - } > - > - r->uri_end = r->uri_start + path->len; > - > - if (ngx_http_parse_uri(r) != NGX_OK) { > - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); > - return NGX_ERROR; > - } > - > - if (ngx_http_process_request_uri(r) != NGX_OK) { > - return NGX_ERROR; > - } > - > - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) > - != NGX_OK) > - { > - return NGX_ERROR; > - } > - > - if (pr->headers_in.accept_encoding) { > - if (ngx_http_v3_set_push_header(r, "accept-encoding", > - &pr->headers_in.accept_encoding->value) > - != NGX_OK) > - { > - return NGX_ERROR; > - } > - } > - > - if (pr->headers_in.accept_language) { > - if (ngx_http_v3_set_push_header(r, "accept-language", > - &pr->headers_in.accept_language->value) > - != NGX_OK) > - { > - return NGX_ERROR; > - } > - } > - > - if (pr->headers_in.user_agent) { > - if (ngx_http_v3_set_push_header(r, "user-agent", > - &pr->headers_in.user_agent->value) > - != NGX_OK) > - { > - return NGX_ERROR; > - } > - } > - > - c->read->handler = ngx_http_v3_push_request_handler; > - c->read->handler = ngx_http_v3_push_request_handler; > - > - ngx_post_event(c->read, &ngx_posted_events); > - > - return NGX_OK; > -} > - > - > -static ngx_int_t > -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, > - ngx_str_t *value) > -{ > - u_char *p; > - ngx_table_elt_t *h; > - ngx_http_header_t *hh; > - ngx_http_core_main_conf_t *cmcf; > - > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > - "http3 push header \"%s\": \"%V\"", name, value); > - > - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); > - > - p = ngx_pnalloc(r->pool, value->len + 1); > - if (p == NULL) { > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > - return NGX_ERROR; > - } > - > - ngx_memcpy(p, value->data, value->len); > - p[value->len] = '\0'; > - > - h = ngx_list_push(&r->headers_in.headers); > - if (h == NULL) { > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > - return NGX_ERROR; > - } > - > - h->key.data = (u_char *) name; > - h->key.len = ngx_strlen(name); > - h->hash = ngx_hash_key(h->key.data, h->key.len); > - h->lowcase_key = (u_char *) name; > - h->value.data = p; > - h->value.len = value->len; > - > - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, > - h->lowcase_key, h->key.len); > - > - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { > - return NGX_ERROR; > - } > - > - return NGX_OK; > -} > - > - > -static void > -ngx_http_v3_push_request_handler(ngx_event_t *ev) > -{ > - ngx_connection_t *c; > - ngx_http_request_t *r; > - > - c = ev->data; > - r = c->data; > - > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); > - > - ngx_http_process_request(r); > -} > - > - > -static ngx_chain_t * > -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, > - uint64_t push_id) > -{ > - size_t n, len; > - ngx_buf_t *b; > - ngx_chain_t *hl, *cl; > - ngx_http_v3_session_t *h3c; > - > - h3c = ngx_http_v3_get_session(r->connection); > - > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > - "http3 create push promise id:%uL", push_id); > - > - len = ngx_http_v3_encode_varlen_int(NULL, push_id); > - > - len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); > - > - len += ngx_http_v3_encode_field_ri(NULL, 0, > - NGX_HTTP_V3_HEADER_METHOD_GET); > - > - len += ngx_http_v3_encode_field_lri(NULL, 0, > - NGX_HTTP_V3_HEADER_AUTHORITY, > - NULL, r->headers_in.server.len); > - > - if (path->len == 1 && path->data[0] == '/') { > - len += ngx_http_v3_encode_field_ri(NULL, 0, > - NGX_HTTP_V3_HEADER_PATH_ROOT); > - > - } else { > - len += ngx_http_v3_encode_field_lri(NULL, 0, > - NGX_HTTP_V3_HEADER_PATH_ROOT, > - NULL, path->len); > - } > - > - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { > - len += ngx_http_v3_encode_field_ri(NULL, 0, > - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); > - > - } else if (r->schema.len == 4 > - && ngx_strncmp(r->schema.data, "http", 4) == 0) > - { > - len += ngx_http_v3_encode_field_ri(NULL, 0, > - NGX_HTTP_V3_HEADER_SCHEME_HTTP); > - > - } else { > - len += ngx_http_v3_encode_field_lri(NULL, 0, > - NGX_HTTP_V3_HEADER_SCHEME_HTTP, > - NULL, r->schema.len); > - } > - > - if (r->headers_in.accept_encoding) { > - len += ngx_http_v3_encode_field_lri(NULL, 0, > - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, > - r->headers_in.accept_encoding->value.len); > - } > - > - if (r->headers_in.accept_language) { > - len += ngx_http_v3_encode_field_lri(NULL, 0, > - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, > - r->headers_in.accept_language->value.len); > - } > - > - if (r->headers_in.user_agent) { > - len += ngx_http_v3_encode_field_lri(NULL, 0, > - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, > - r->headers_in.user_agent->value.len); > - } > - > - b = ngx_create_temp_buf(r->pool, len); > - if (b == NULL) { > - return NULL; > - } > - > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); > - > - b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, > - 0, 0, 0); > - > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > - NGX_HTTP_V3_HEADER_METHOD_GET); > - > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > - NGX_HTTP_V3_HEADER_AUTHORITY, > - r->headers_in.server.data, > - r->headers_in.server.len); > - > - if (path->len == 1 && path->data[0] == '/') { > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > - NGX_HTTP_V3_HEADER_PATH_ROOT); > - > - } else { > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > - NGX_HTTP_V3_HEADER_PATH_ROOT, > - path->data, path->len); > - } > - > - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); > - > - } else if (r->schema.len == 4 > - && ngx_strncmp(r->schema.data, "http", 4) == 0) > - { > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > - NGX_HTTP_V3_HEADER_SCHEME_HTTP); > - > - } else { > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > - NGX_HTTP_V3_HEADER_SCHEME_HTTP, > - r->schema.data, r->schema.len); > - } > - > - if (r->headers_in.accept_encoding) { > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, > - r->headers_in.accept_encoding->value.data, > - r->headers_in.accept_encoding->value.len); > - } > - > - if (r->headers_in.accept_language) { > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, > - r->headers_in.accept_language->value.data, > - r->headers_in.accept_language->value.len); > - } > - > - if (r->headers_in.user_agent) { > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > - NGX_HTTP_V3_HEADER_USER_AGENT, > - r->headers_in.user_agent->value.data, > - r->headers_in.user_agent->value.len); > - } > - > - cl = ngx_alloc_chain_link(r->pool); > - if (cl == NULL) { > - return NULL; > - } > - > - cl->buf = b; > - cl->next = NULL; > - > - n = b->last - b->pos; > - > - h3c->payload_bytes += n; > - > - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) > - + ngx_http_v3_encode_varlen_int(NULL, n); > - > - b = ngx_create_temp_buf(r->pool, len); > - if (b == NULL) { > - return NULL; > - } > - > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, > - NGX_HTTP_V3_FRAME_PUSH_PROMISE); > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); > - > - hl = ngx_alloc_chain_link(r->pool); > - if (hl == NULL) { > - return NULL; > - } > - > - hl->buf = b; > - hl->next = cl; > - > - return hl; > -} > - > - > -static ngx_int_t > ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) > { > u_char *chunk; > diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c > --- a/src/http/v3/ngx_http_v3_module.c > +++ b/src/http/v3/ngx_http_v3_module.c > @@ -18,10 +18,6 @@ static char *ngx_http_v3_merge_srv_conf( > void *child); > static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, > void *conf); > -static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); > -static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, > - void *child); > -static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); > > > static ngx_command_t ngx_http_v3_commands[] = { > @@ -40,13 +36,6 @@ static ngx_command_t ngx_http_v3_comman > offsetof(ngx_http_v3_srv_conf_t, enable_hq), > NULL }, > > - { ngx_string("http3_max_concurrent_pushes"), > - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > - ngx_conf_set_num_slot, > - NGX_HTTP_SRV_CONF_OFFSET, > - offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), > - NULL }, > - > { ngx_string("http3_max_concurrent_streams"), > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > ngx_conf_set_num_slot, > @@ -54,20 +43,6 @@ static ngx_command_t ngx_http_v3_comman > offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), > NULL }, > > - { ngx_string("http3_push"), > - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, > - ngx_http_v3_push, > - NGX_HTTP_LOC_CONF_OFFSET, > - 0, > - NULL }, > - > - { ngx_string("http3_push_preload"), > - 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_v3_loc_conf_t, push_preload), > - NULL }, > - > { ngx_string("http3_stream_buffer_size"), > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > ngx_conf_set_size_slot, > @@ -117,8 +92,8 @@ static ngx_http_module_t ngx_http_v3_mo > ngx_http_v3_create_srv_conf, /* create server configuration */ > ngx_http_v3_merge_srv_conf, /* merge server configuration */ > > - ngx_http_v3_create_loc_conf, /* create location configuration */ > - ngx_http_v3_merge_loc_conf /* merge location configuration */ > + NULL, /* create location configuration */ > + NULL /* merge location configuration */ > }; > > > @@ -224,7 +199,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * > h3scf->enable = NGX_CONF_UNSET; > h3scf->enable_hq = NGX_CONF_UNSET; > h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; > - h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; > h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; > > h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; > @@ -255,9 +229,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > > ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); > > - ngx_conf_merge_uint_value(conf->max_concurrent_pushes, > - prev->max_concurrent_pushes, 10); > - > ngx_conf_merge_uint_value(conf->max_concurrent_streams, > prev->max_concurrent_streams, 128); > > @@ -416,102 +387,3 @@ failed: > > return NGX_CONF_ERROR; > } > - > - > -static void * > -ngx_http_v3_create_loc_conf(ngx_conf_t *cf) > -{ > - ngx_http_v3_loc_conf_t *h3lcf; > - > - h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); > - if (h3lcf == NULL) { > - return NULL; > - } > - > - /* > - * set by ngx_pcalloc(): > - * > - * h3lcf->pushes = NULL; > - */ > - > - h3lcf->push_preload = NGX_CONF_UNSET; > - h3lcf->push = NGX_CONF_UNSET; > - > - return h3lcf; > -} > - > - > -static char * > -ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) > -{ > - ngx_http_v3_loc_conf_t *prev = parent; > - ngx_http_v3_loc_conf_t *conf = child; > - > - ngx_conf_merge_value(conf->push, prev->push, 1); > - > - if (conf->push && conf->pushes == NULL) { > - conf->pushes = prev->pushes; > - } > - > - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); > - > - return NGX_CONF_OK; > -} > - > - > -static char * > -ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) > -{ > - ngx_http_v3_loc_conf_t *h3lcf = conf; > - > - ngx_str_t *value; > - ngx_http_complex_value_t *cv; > - ngx_http_compile_complex_value_t ccv; > - > - value = cf->args->elts; > - > - if (ngx_strcmp(value[1].data, "off") == 0) { > - > - if (h3lcf->pushes) { > - return "\"off\" parameter cannot be used with URI"; > - } > - > - if (h3lcf->push == 0) { > - return "is duplicate"; > - } > - > - h3lcf->push = 0; > - return NGX_CONF_OK; > - } > - > - if (h3lcf->push == 0) { > - return "URI cannot be used with \"off\" parameter"; > - } > - > - h3lcf->push = 1; > - > - if (h3lcf->pushes == NULL) { > - h3lcf->pushes = ngx_array_create(cf->pool, 1, > - sizeof(ngx_http_complex_value_t)); > - if (h3lcf->pushes == NULL) { > - return NGX_CONF_ERROR; > - } > - } > - > - cv = ngx_array_push(h3lcf->pushes); > - if (cv == NULL) { > - return NGX_CONF_ERROR; > - } > - > - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); > - > - ccv.cf = cf; > - ccv.value = &value[1]; > - ccv.complex_value = cv; > - > - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { > - return NGX_CONF_ERROR; > - } > - > - return NGX_CONF_OK; > -} > diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c > --- a/src/http/v3/ngx_http_v3_uni.c > +++ b/src/http/v3/ngx_http_v3_uni.c > @@ -16,19 +16,10 @@ typedef struct { > } ngx_http_v3_uni_stream_t; > > > -typedef struct { > - ngx_queue_t queue; > - uint64_t id; > - ngx_connection_t *connection; > - ngx_uint_t *npushing; > -} ngx_http_v3_push_t; > - > - > static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); > static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); > static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); > static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); > -static void ngx_http_v3_push_cleanup(void *data); > static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, > ngx_uint_t type); > > @@ -316,78 +307,6 @@ ngx_http_v3_uni_dummy_write_handler(ngx_ > } > > > -ngx_connection_t * > -ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) > -{ > - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; > - size_t n; > - ngx_connection_t *sc; > - ngx_pool_cleanup_t *cln; > - ngx_http_v3_push_t *push; > - ngx_http_v3_session_t *h3c; > - > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 create push stream id:%uL", push_id); > - > - sc = ngx_quic_open_stream(c, 0); > - if (sc == NULL) { > - goto failed; > - } > - > - p = buf; > - p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); > - p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); > - n = p - buf; > - > - h3c = ngx_http_v3_get_session(c); > - h3c->total_bytes += n; > - > - if (sc->send(sc, buf, n) != (ssize_t) n) { > - goto failed; > - } > - > - cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); > - if (cln == NULL) { > - goto failed; > - } > - > - h3c->npushing++; > - > - cln->handler = ngx_http_v3_push_cleanup; > - > - push = cln->data; > - push->id = push_id; > - push->connection = sc; > - push->npushing = &h3c->npushing; > - > - ngx_queue_insert_tail(&h3c->pushing, &push->queue); > - > - return sc; > - > -failed: > - > - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); > - > - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, > - "failed to create push stream"); > - if (sc) { > - ngx_http_v3_close_uni_stream(sc); > - } > - > - return NULL; > -} > - > - > -static void > -ngx_http_v3_push_cleanup(void *data) > -{ > - ngx_http_v3_push_t *push = data; > - > - ngx_queue_remove(&push->queue); > - (*push->npushing)--; > -} > - > - > static ngx_connection_t * > ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) > { > @@ -696,19 +615,9 @@ failed: > ngx_int_t > ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) > { > - ngx_http_v3_session_t *h3c; > - > - h3c = ngx_http_v3_get_session(c); > - > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > "http3 MAX_PUSH_ID:%uL", max_push_id); > > - if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { > - return NGX_HTTP_V3_ERR_ID_ERROR; > - } > - > - h3c->max_push_id = max_push_id; > - > return NGX_OK; > } > > @@ -716,14 +625,8 @@ ngx_http_v3_set_max_push_id(ngx_connecti > ngx_int_t > ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) > { > - ngx_http_v3_session_t *h3c; > - > - h3c = ngx_http_v3_get_session(c); > - > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); > > - h3c->goaway_push_id = push_id; > - > return NGX_OK; > } Shouldn't we simply skip MAX_PUSH_ID and GOAWAY frames somewhere at ngx_http_v3_parse_control()? > @@ -731,40 +634,9 @@ ngx_http_v3_goaway(ngx_connection_t *c, > ngx_int_t > ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) > { > - ngx_queue_t *q; > - ngx_http_request_t *r; > - ngx_http_v3_push_t *push; > - ngx_http_v3_session_t *h3c; > - > - h3c = ngx_http_v3_get_session(c); > - > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > "http3 CANCEL_PUSH:%uL", push_id); > > - if (push_id >= h3c->next_push_id) { > - return NGX_HTTP_V3_ERR_ID_ERROR; > - } > - > - for (q = ngx_queue_head(&h3c->pushing); > - q != ngx_queue_sentinel(&h3c->pushing); > - q = ngx_queue_next(q)) > - { > - push = (ngx_http_v3_push_t *) q; > - > - if (push->id != push_id) { > - continue; > - } > - > - r = push->connection->data; > - > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > - "http3 cancel push"); > - > - ngx_http_finalize_request(r, NGX_HTTP_CLOSE); > - > - break; > - } > - > return NGX_OK; > } > And CANCEL_PUSH probably worth an explicit error (https://www.rfc-editor.org/rfc/rfc9114.html#name-cancel_push): : If a server receives a CANCEL_PUSH frame for a push ID that has : not yet been mentioned by a PUSH_PROMISE frame, this MUST be : treated as a connection error of type H3_ID_ERROR. Since no pushes are expected to appear on the connection, returning NGX_HTTP_V3_ERR_ID_ERROR seems to be correct option. Similarly to the above, handling this at ngx_http_v3_parse_control() might be easier. > diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h > --- a/src/http/v3/ngx_http_v3_uni.h > +++ b/src/http/v3/ngx_http_v3_uni.h > @@ -17,8 +17,6 @@ > void ngx_http_v3_init_uni_stream(ngx_connection_t *c); > ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); > > -ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, > - uint64_t push_id); > ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, > uint64_t max_push_id); > ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); Otherwise looks good. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu May 18 21:41:58 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 19 May 2023 00:41:58 +0300 Subject: [PATCH 4 of 4] Removed README In-Reply-To: References: Message-ID: Hello! On Sun, May 14, 2023 at 05:38:55PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1684045884 -14400 > # Sun May 14 10:31:24 2023 +0400 > # Branch quic > # Node ID d8272b84031bea1940ef8a5b8e2f79ec6a2dcfc1 > # Parent 49a8edf7bf31b78681399cd7e93a8516788607dd > Removed README. > > diff --git a/README b/README > deleted file mode 100644 > --- a/README > +++ /dev/null > @@ -1,319 +0,0 @@ > -Experimental QUIC support for nginx > ------------------------------------ > - > -1. Introduction > -2. Building from sources > -3. Configuration > -4. Directives > -5. Clients > -6. Troubleshooting > -7. Contributing > -8. Links > - > -1. Introduction > - > - This is an experimental QUIC [1] / HTTP/3 [2] support for nginx. > - > - The code is developed in a separate "quic" branch available > - at https://hg.nginx.org/nginx-quic. Currently it is based > - on nginx mainline 1.23.x. We merge new nginx releases into > - this branch regularly. > - > - The project code base is under the same BSD license as nginx. > - > - The code is currently at a beta level of quality, however > - there are several production deployments with it. > - > - NGINX Development Team is working on improving HTTP/3 support to > - integrate it into the main NGINX codebase. Thus, expect further > - updates of this code, including features, changes in behaviour, > - bug fixes, and refactoring. NGINX Development team will be > - grateful for any feedback and code submissions. > - > - Please contact NGINX Development Team via nginx-devel mailing list [3]. > - > - What works now: > - > - IETF QUIC version 1 is supported. Internet drafts are no longer supported. > - > - nginx should be able to respond to HTTP/3 requests over QUIC and > - it should be possible to upload and download big files without errors. > - > - + The handshake completes successfully > - + One endpoint can update keys and its peer responds correctly > - + 0-RTT data is being received and acted on > - + Connection is established using TLS Resume Ticket > - + A handshake that includes a Retry packet completes successfully > - + Stream data is being exchanged and ACK'ed > - + An H3 transaction succeeded > - + One or both endpoints insert entries into dynamic table and > - subsequently reference them from header blocks > - + Version Negotiation packet is sent to client with unknown version > - + Lost packets are detected and retransmitted properly > - + Clients may migrate to new address > - > -2. Building from sources > - > - The build is configured using the configure command. > - Refer to http://nginx.org/en/docs/configure.html for details. > - > - When configuring nginx, it's possible to enable QUIC and HTTP/3 > - using the following new configuration option: > - > - --with-http_v3_module - enable QUIC and HTTP/3 > - > - A library that provides QUIC support is recommended to build nginx, there > - are several of those available on the market: > - + BoringSSL [4] > - + LibreSSL [5] > - + QuicTLS [6] > - > - Alternatively, nginx can be configured with OpenSSL compatibility > - layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is > - enabled by default if native QUIC support is not detected. > - 0-RTT is not supported in OpenSSL compatibility mode. > - > - Clone the NGINX QUIC repository > - > - $ hg clone -b quic https://hg.nginx.org/nginx-quic > - $ cd nginx-quic > - > - Use the following command to configure nginx with BoringSSL [4] > - > - $ ./auto/configure --with-debug --with-http_v3_module \ > - --with-cc-opt="-I../boringssl/include" \ > - --with-ld-opt="-L../boringssl/build/ssl \ > - -L../boringssl/build/crypto" > - $ make > - > - Alternatively, nginx can be configured with QuicTLS [6] > - > - $ ./auto/configure --with-debug --with-http_v3_module \ > - --with-cc-opt="-I../quictls/build/include" \ > - --with-ld-opt="-L../quictls/build/lib" > - > - Alternatively, nginx can be configured with a modern version > - of LibreSSL [7] > - > - $ ./auto/configure --with-debug --with-http_v3_module \ > - --with-cc-opt="-I../libressl/build/include" \ > - --with-ld-opt="-L../libressl/build/lib" > - > -3. Configuration > - > - The HTTP "listen" directive got a new option "quic" which enables > - QUIC as client transport protocol instead of TCP. > - > - Along with "quic", it's also possible to specify "reuseport" > - option [8] to make it work properly with multiple workers. > - > - To enable address validation: > - > - quic_retry on; > - > - To enable 0-RTT: > - > - ssl_early_data on; > - > - To enable GSO (Generic Segmentation Offloading): > - > - quic_gso on; > - > - To set host key for various tokens: > - > - quic_host_key ; > - > - QUIC requires TLSv1.3 protocol, which is enabled by the default > - by "ssl_protocols" directive. > - > - By default, GSO Linux-specific optimization [10] is disabled. > - Enable it in case a corresponding network interface is configured to > - support GSO. > - > - A number of directives were added that configure HTTP/3: > - > - http3 > - http3_hq > - http3_stream_buffer_size > - http3_max_concurrent_streams > - > - In http, an additional variable is available: $http3. > - The value of $http3 is "h3" for HTTP/3 connections, > - "hq" for hq connections, or an empty string otherwise. > - > -Example configuration: > - > - http { > - log_format quic '$remote_addr - $remote_user [$time_local] ' > - '"$request" $status $body_bytes_sent ' > - '"$http_referer" "$http_user_agent" "$http3"'; > - > - access_log logs/access.log quic; > - > - server { > - # for better compatibility it's recommended > - # to use the same port for quic and https > - listen 8443 quic reuseport; > - listen 8443 ssl; > - > - ssl_certificate certs/example.com.crt; > - ssl_certificate_key certs/example.com.key; > - > - location / { > - # required for browsers to direct them into quic port > - add_header Alt-Svc 'h3=":8443"; ma=86400'; > - } > - } > - } > - > -4. Directives > - > - Syntax: quic_bpf on | off; > - Default: quic_bpf off; > - Context: main > - > - Enables routing of QUIC packets using eBPF. > - When enabled, this allows to support QUIC connection migration. > - The directive is only supported on Linux 5.7+. > - > - > - Syntax: quic_retry on | off; > - Default: quic_retry off; > - Context: http, server > - > - Enables the QUIC Address Validation feature. This includes: > - - sending a new token in a Retry packet or a NEW_TOKEN frame > - - validating a token received in the Initial packet > - > - > - Syntax: quic_gso on | off; > - Default: quic_gso off; > - Context: http, server > - > - Enables sending in optimized batch mode using segmentation offloading. > - Optimized sending is only supported on Linux featuring UDP_SEGMENT. > - > - > - Syntax: quic_host_key file; > - Default: - > - Context: http, server > - > - Specifies a file with the secret key used to encrypt stateless reset and > - address validation tokens. By default, a randomly generated key is used. > - > - > - Syntax: quic_active_connection_id_limit number; > - Default: quic_active_connection_id_limit 2; > - Context: http, server > - > - Sets the QUIC active_connection_id_limit transport parameter value. > - This is the maximum number of connection IDs we are willing to store. > - > - > - Syntax: http3_stream_buffer_size size; > - Default: http3_stream_buffer_size 64k; > - Context: http, server > - > - Sets buffer size for reading and writing of the QUIC STREAM payload. > - The buffer size is used to calculate initial flow control limits > - in the following QUIC transport parameters: > - - initial_max_data > - - initial_max_stream_data_bidi_local > - - initial_max_stream_data_bidi_remote > - - initial_max_stream_data_uni > - > - > - Syntax: http3_max_concurrent_streams number; > - Default: http3_max_concurrent_streams 128; > - Context: http, server > - > - Sets the maximum number of concurrent HTTP/3 streams in a connection. > - > - > - Syntax: http3 on | off; > - Default: http3 on; > - Context: http, server > - > - Enables HTTP/3 protocol negotiation. > - > - > - Syntax: http3_hq on | off; > - Default: http3_hq off; > - Context: http, server > - > - Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests. > - > -5. Clients > - > - * Browsers > - > - Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1) > - > - Beware of strange issues: sometimes browser may decide to ignore QUIC > - Cache clearing/restart might help. Always check access.log and > - error.log to make sure the browser is using HTTP/3 and not TCP https. > - > - * Console clients > - > - Known to work: ngtcp2, firefox's neqo and chromium's console clients: > - > - $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html > - > - $ ./neqo-client https://127.0.0.1:8443/ > - > - $ chromium-build/out/my_build/quic_client http://example.com:8443 > - > - > - In case everyhing is right, the access log should show something like: > - > - 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" > - "nghttp3/ngtcp2 client" "quic" > - > - > -6. Troubleshooting > - > - Here are some tips that may help to identify problems: > - > - + Ensure nginx is built with proper SSL library that supports QUIC > - > - + Ensure nginx is using the proper SSL library in runtime > - (`nginx -V` shows what it's using) > - > - + Ensure a client is actually sending requests over QUIC > - (see "Clients" section about browsers and cache) > - > - We recommend to start with simple console client like ngtcp2 > - to ensure the server is configured properly before trying > - with real browsers that may be very picky with certificates, > - for example. > - > - + Build nginx with debug support [9] and check the debug log. > - It should contain all details about connection and why it > - failed. All related messages contain "quic " prefix and can > - be easily filtered out. > - > - + For a deeper investigation, please enable additional debugging > - in src/event/quic/ngx_event_quic_connection.h: > - > - #define NGX_QUIC_DEBUG_PACKETS > - #define NGX_QUIC_DEBUG_FRAMES > - #define NGX_QUIC_DEBUG_ALLOC > - #define NGX_QUIC_DEBUG_CRYPTO > - > -7. Contributing > - > - Please refer to > - http://nginx.org/en/docs/contributing_changes.html > - > -8. Links > - > - [1] https://datatracker.ietf.org/doc/html/rfc9000 > - [2] https://datatracker.ietf.org/doc/html/rfc9114 > - [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel > - [4] https://boringssl.googlesource.com/boringssl/ > - [5] https://www.libressl.org/ > - [6] https://github.com/quictls/openssl > - [7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0 > - [8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen > - [9] https://nginx.org/en/docs/debugging_log.html > - [10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf Looks good. -- Maxim Dounin http://mdounin.ru/ From 0x0davood at gmail.com Thu May 18 22:05:57 2023 From: 0x0davood at gmail.com (Davood Falahati) Date: Fri, 19 May 2023 00:05:57 +0200 Subject: auth request body mask/unmask proposal Message-ID: # HG changeset patch # User Davood Falahati <0x0davood at gmail.com> # Date 1684446142 -7200 # Thu May 18 23:42:22 2023 +0200 # Node ID c073e545e1cdcc736f8869a012a78b2dd836eac9 # Parent 77d5c662f3d9d9b90425128109d3369c30ef5f07 Proposal: add the capacity to ngx_http_auth_request_module to return external auth service response body. Why do we need it? Error handling inside mobile/web clients are being disrupted on receiving 401 from external auth service. For instance, external auth service returns 401 response along with an important error message that client should read. Why do we need to change the patch? ngx_http_auth_request_module doesn't send the external response body by design. This module intercepts the external auth_request response body and doesn't send it to the client. It lets the admin send a customized error-page, but it doesn't open and read external auth's response body. send external auth service body response to client if auth_request_mask_body flag is off diff -r 77d5c662f3d9 -r c073e545e1cd src/http/modules/ngx_http_auth_request_module.c --- a/src/http/modules/ngx_http_auth_request_module.c Tue Apr 18 06:28:46 2023 +0300 +++ b/src/http/modules/ngx_http_auth_request_module.c Thu May 18 23:42:22 2023 +0200 @@ -13,6 +13,7 @@ typedef struct { ngx_str_t uri; ngx_array_t *vars; + ngx_flag_t mask_auth_response_body; } ngx_http_auth_request_conf_t; @@ -63,6 +64,13 @@ 0, NULL }, + { ngx_string("auth_request_mask_body"), + NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_auth_request_conf_t, mask_auth_response_body), + NULL }, + ngx_null_command }; @@ -106,6 +114,8 @@ ngx_http_post_subrequest_t *ps; ngx_http_auth_request_ctx_t *ctx; ngx_http_auth_request_conf_t *arcf; + ngx_buf_t *b; + ngx_chain_t out, *in; arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module); @@ -140,7 +150,36 @@ if (ctx->status == NGX_HTTP_UNAUTHORIZED) { sr = ctx->subrequest; + /* + * send external auth service response body to the client + */ + if (!arcf->mask_auth_response_body) { + r->headers_out.content_type = sr->headers_out.content_type; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + r->headers_out.status = ctx->status; + + b->last_buf = 1; + b->last_in_chain = 1; + b->memory = 1; + + out.buf = b; + out.next = NULL; + + in = sr->out; + in->next = &out; + + ngx_http_send_header(r); + return ngx_http_output_filter(r, in); + } + + return ctx->status; + } h = sr->headers_out.www_authenticate; if (!h && sr->upstream) { @@ -164,8 +203,7 @@ h = h->next; } - return ctx->status; - } + if (ctx->status >= NGX_HTTP_OK && ctx->status < NGX_HTTP_SPECIAL_RESPONSE) @@ -192,9 +230,10 @@ ps->handler = ngx_http_auth_request_done; ps->data = ctx; + if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, - NGX_HTTP_SUBREQUEST_WAITED) - != NGX_OK) + arcf->mask_auth_response_body ? NGX_HTTP_SUBREQUEST_WAITED: + NGX_HTTP_SUBREQUEST_IN_MEMORY) != NGX_OK) { return NGX_ERROR; } @@ -209,8 +248,10 @@ return NGX_ERROR; } - sr->header_only = 1; - + if (arcf->mask_auth_response_body) + { + sr->header_only = 1; + } ctx->subrequest = sr; ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module); @@ -322,6 +363,7 @@ */ conf->vars = NGX_CONF_UNSET_PTR; + conf->mask_auth_response_body = NGX_CONF_UNSET; return conf; } @@ -335,6 +377,7 @@ ngx_conf_merge_str_value(conf->uri, prev->uri, ""); ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); + ngx_conf_merge_value(conf->mask_auth_response_body, prev->mask_auth_response_body,1); return NGX_CONF_OK; } -------------- next part -------------- An HTML attachment was scrubbed... URL: From xeioex at nginx.com Fri May 19 01:41:05 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 19 May 2023 01:41:05 +0000 Subject: [njs] Implemented Array.from(). Message-ID: details: https://hg.nginx.org/njs/rev/4d26300ddc64 branches: changeset: 2123:4d26300ddc64 user: Dmitry Volyntsev date: Thu May 18 18:33:36 2023 -0700 description: Implemented Array.from(). diffstat: src/njs_array.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ src/njs_typed_array.c | 12 +----- src/njs_value.c | 19 ++++++++++ src/njs_value.h | 2 + src/njs_vmcode.c | 2 + src/test/njs_unit_test.c | 34 +++++++++++++++++- 6 files changed, 148 insertions(+), 12 deletions(-) diffs (236 lines): diff -r 25b55a064e42 -r 4d26300ddc64 src/njs_array.c --- a/src/njs_array.c Wed May 17 21:16:19 2023 -0700 +++ b/src/njs_array.c Thu May 18 18:33:36 2023 -0700 @@ -483,6 +483,95 @@ njs_array_constructor(njs_vm_t *vm, njs_ static njs_int_t +njs_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + int64_t length, i; + njs_int_t ret; + njs_array_t *array; + njs_value_t *this, *items, *mapfn; + njs_value_t arguments[3], value, result; + njs_function_t *function; + + mapfn = njs_arg(args, nargs, 2); + + if (njs_slow_path(!njs_is_function_or_undefined(mapfn))) { + njs_type_error(vm, "\"mapfn\" argument is not callable"); + return NJS_ERROR; + } + + function = NULL; + if (njs_is_function(mapfn)) { + function = njs_function(mapfn); + } + + items = njs_arg(args, nargs, 1); + + ret = njs_value_to_object(vm, items); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_object_length(vm, items, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + this = njs_argument(args, 0); + + if (njs_is_constructor(this)) { + njs_set_number(&arguments[0], length); + + ret = njs_value_construct(vm, this, arguments, 1, &value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + } else { + array = njs_array_alloc(vm, 1, length, 0); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + njs_set_array(&value, array); + } + + arguments[0] = *njs_arg(args, nargs, 3); + + for (i = 0; i < length; i++) { + ret = njs_value_property_i64(vm, items, i, &result); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (function != NULL) { + njs_value_assign(&arguments[1], &result); + njs_set_number(&arguments[2], i); + + ret = njs_function_apply(vm, function, arguments, 3, &result); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + ret = njs_value_create_data_prop_i64(vm, &value, i, &result, 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + ret = njs_object_length_set(vm, &value, length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + njs_value_assign(retval, &value); + + return NJS_OK; +} + + +static njs_int_t njs_array_is_array(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { @@ -526,6 +615,8 @@ static const njs_object_prop_t njs_arra NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), + NJS_DECLARE_PROP_NATIVE("from", njs_array_from, 1, 0), + NJS_DECLARE_PROP_NATIVE("isArray", njs_array_is_array, 1, 0), NJS_DECLARE_PROP_NATIVE("of", njs_array_of, 0, 0), diff -r 25b55a064e42 -r 4d26300ddc64 src/njs_typed_array.c --- a/src/njs_typed_array.c Wed May 17 21:16:19 2023 -0700 +++ b/src/njs_typed_array.c Thu May 18 18:33:36 2023 -0700 @@ -222,19 +222,9 @@ njs_typed_array_create(njs_vm_t *vm, njs njs_value_t *args, njs_uint_t nargs, njs_value_t *retval) { njs_int_t ret; - njs_value_t this; - njs_object_t *object; njs_typed_array_t *array; - object = njs_function_new_object(vm, constructor); - if (njs_slow_path(object == NULL)) { - return NJS_ERROR; - } - - njs_set_object(&this, object); - - ret = njs_function_call2(vm, njs_function(constructor), &this, args, - nargs, retval, 1); + ret = njs_value_construct(vm, constructor, args, nargs, retval); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } diff -r 25b55a064e42 -r 4d26300ddc64 src/njs_value.c --- a/src/njs_value.c Wed May 17 21:16:19 2023 -0700 +++ b/src/njs_value.c Thu May 18 18:33:36 2023 -0700 @@ -1681,6 +1681,25 @@ njs_symbol_conversion_failed(njs_vm_t *v njs_int_t +njs_value_construct(njs_vm_t *vm, njs_value_t *constructor, njs_value_t *args, + njs_uint_t nargs, njs_value_t *retval) +{ + njs_value_t this; + njs_object_t *object; + + object = njs_function_new_object(vm, constructor); + if (njs_slow_path(object == NULL)) { + return NJS_ERROR; + } + + njs_set_object(&this, object); + + return njs_function_call2(vm, njs_function(constructor), &this, args, + nargs, retval, 1); +} + + +njs_int_t njs_value_species_constructor(njs_vm_t *vm, njs_value_t *object, njs_value_t *default_constructor, njs_value_t *dst) { diff -r 25b55a064e42 -r 4d26300ddc64 src/njs_value.h --- a/src/njs_value.h Wed May 17 21:16:19 2023 -0700 +++ b/src/njs_value.h Thu May 18 18:33:36 2023 -0700 @@ -1090,6 +1090,8 @@ njs_int_t njs_value_to_object(njs_vm_t * void njs_symbol_conversion_failed(njs_vm_t *vm, njs_bool_t to_string); +njs_int_t njs_value_construct(njs_vm_t *vm, njs_value_t *constructor, + njs_value_t *args, njs_uint_t nargs, njs_value_t *retval); njs_int_t njs_value_species_constructor(njs_vm_t *vm, njs_value_t *object, njs_value_t *default_constructor, njs_value_t *dst); diff -r 25b55a064e42 -r 4d26300ddc64 src/njs_vmcode.c --- a/src/njs_vmcode.c Wed May 17 21:16:19 2023 -0700 +++ b/src/njs_vmcode.c Thu May 18 18:33:36 2023 -0700 @@ -2536,6 +2536,8 @@ njs_function_new_object(njs_vm_t *vm, nj return NULL; } + njs_assert(njs_is_function(constructor)); + function = njs_function(constructor); if (function->bound != NULL) { diff -r 25b55a064e42 -r 4d26300ddc64 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Wed May 17 21:16:19 2023 -0700 +++ b/src/test/njs_unit_test.c Thu May 18 18:33:36 2023 -0700 @@ -4561,6 +4561,38 @@ static njs_unit_test_t njs_test[] = { njs_str("Array.isArray([]) ? 'true' : 'false'"), njs_str("true") }, + { njs_str("[" + " [undefined]," + " [null]," + " ['foo']," + " ['foo', c => c.toUpperCase()]," + " [{length: 3, 1:'a', 2:'b'}]," + " [[7,,9], v => v*2]," + "].map(args => { try { return Array.from.apply(Array,args) }" + " catch (e) {return e.toString()}})"), + njs_str("TypeError: cannot convert null or undefined to object," + "TypeError: cannot convert null or undefined to object," + "f,o,o," + "F,O,O," + ",a,b," + "14,NaN,18" + ) }, + + { njs_str("function f() {return Array.from(arguments);}; f(1,2,3)"), + njs_str("1,2,3") }, + + { njs_str("Array.from({ length: 5 }, (v, i) => i)"), + njs_str("0,1,2,3,4") }, + + { njs_str("const range = (start, stop, step) =>" + "Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);" + "range(1, 10, 2)"), + njs_str("1,3,5,7,9") }, + + { njs_str("var a = Array.from.call(Object, { length: 2, 0:7, 1:9 });" + "[a[0], a[1], Array.isArray(a)]"), + njs_str("7,9,false") }, + { njs_str("Array.of()"), njs_str("") }, @@ -15282,7 +15314,7 @@ static njs_unit_test_t njs_test[] = njs_str("length,name,prototype") }, { njs_str("Object.getOwnPropertyNames(Array)"), - njs_str("name,length,prototype,isArray,of") }, + njs_str("name,length,prototype,from,isArray,of") }, { njs_str("Object.getOwnPropertyNames(Array.isArray)"), njs_str("name,length") }, From arut at nginx.com Fri May 19 04:44:04 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 19 May 2023 08:44:04 +0400 Subject: [PATCH 3 of 4] HTTP/3: removed server push support In-Reply-To: References: <49a8edf7bf31b7868139.1684071534@arut-laptop> Message-ID: <20230519044404.sanvxmuhkzrw7ezp@N00W24XTQX> Hi, On Fri, May 19, 2023 at 12:41:32AM +0300, Maxim Dounin wrote: > Hello! > > On Sun, May 14, 2023 at 05:38:54PM +0400, Roman Arutyunyan wrote: > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1683871330 -14400 > > # Fri May 12 10:02:10 2023 +0400 > > # Branch quic > > # Node ID 49a8edf7bf31b78681399cd7e93a8516788607dd > > # Parent adcc6d8acfd47c1344b121fceeb94fbcc3f8b5c0 > > HTTP/3: removed server push support. > > > > diff --git a/README b/README > > --- a/README > > +++ b/README > > @@ -135,10 +135,7 @@ 3. Configuration > > http3 > > http3_hq > > http3_stream_buffer_size > > - http3_max_concurrent_pushes > > http3_max_concurrent_streams > > - http3_push > > - http3_push_preload > > > > In http, an additional variable is available: $http3. > > The value of $http3 is "h3" for HTTP/3 connections, > > @@ -226,13 +223,6 @@ 4. Directives > > - initial_max_stream_data_uni > > > > > > - Syntax: http3_max_concurrent_pushes number; > > - Default: http3_max_concurrent_pushes 10; > > - Context: http, server > > - > > - Limits the maximum number of concurrent push requests in a connection. > > - > > - > > Syntax: http3_max_concurrent_streams number; > > Default: http3_max_concurrent_streams 128; > > Context: http, server > > @@ -240,31 +230,6 @@ 4. Directives > > Sets the maximum number of concurrent HTTP/3 streams in a connection. > > > > > > - Syntax: http3_push uri | off; > > - Default: http3_push off; > > - Context: http, server, location > > - > > - Pre-emptively sends (pushes) a request to the specified uri along with > > - the response to the original request. Only relative URIs with absolute > > - path will be processed, for example: > > - > > - http3_push /static/css/main.css; > > - > > - The uri value can contain variables. > > - > > - Several http3_push directives can be specified on the same configuration > > - level. The off parameter cancels the effect of the http3_push directives > > - inherited from the previous configuration level. > > - > > - > > - Syntax: http3_push_preload on | off; > > - Default: http3_push_preload off; > > - Context: http, server, location > > - > > - Enables automatic conversion of preload links specified in the “Link” > > - response header fields into push requests. > > - > > - > > Syntax: http3 on | off; > > Default: http3 on; > > Context: http, server > > diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c > > --- a/src/http/v3/ngx_http_v3.c > > +++ b/src/http/v3/ngx_http_v3.c > > @@ -30,11 +30,7 @@ ngx_http_v3_init_session(ngx_connection_ > > goto failed; > > } > > > > - h3c->max_push_id = (uint64_t) -1; > > - h3c->goaway_push_id = (uint64_t) -1; > > - > > ngx_queue_init(&h3c->blocked); > > - ngx_queue_init(&h3c->pushing); > > > > h3c->keepalive.log = c->log; > > h3c->keepalive.data = c; > > diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h > > --- a/src/http/v3/ngx_http_v3.h > > +++ b/src/http/v3/ngx_http_v3.h > > @@ -106,19 +106,11 @@ typedef struct { > > ngx_flag_t enable_hq; > > size_t max_table_capacity; > > ngx_uint_t max_blocked_streams; > > - ngx_uint_t max_concurrent_pushes; > > ngx_uint_t max_concurrent_streams; > > ngx_quic_conf_t quic; > > } ngx_http_v3_srv_conf_t; > > > > > > -typedef struct { > > - ngx_flag_t push_preload; > > - ngx_flag_t push; > > - ngx_array_t *pushes; > > -} ngx_http_v3_loc_conf_t; > > - > > - > > struct ngx_http_v3_parse_s { > > size_t header_limit; > > ngx_http_v3_parse_headers_t headers; > > @@ -136,11 +128,6 @@ struct ngx_http_v3_session_s { > > ngx_queue_t blocked; > > ngx_uint_t nblocked; > > > > - ngx_queue_t pushing; > > - ngx_uint_t npushing; > > - uint64_t next_push_id; > > - uint64_t max_push_id; > > - uint64_t goaway_push_id; > > uint64_t next_request_id; > > > > off_t total_bytes; > > diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c > > --- a/src/http/v3/ngx_http_v3_filter_module.c > > +++ b/src/http/v3/ngx_http_v3_filter_module.c > > @@ -36,17 +36,6 @@ typedef struct { > > > > > > static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); > > -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, > > - ngx_chain_t ***out); > > -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, > > - ngx_str_t *path, ngx_chain_t ***out); > > -static ngx_int_t ngx_http_v3_create_push_request( > > - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); > > -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, > > - const char *name, ngx_str_t *value); > > -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); > > -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, > > - ngx_str_t *path, uint64_t push_id); > > static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, > > ngx_chain_t *in); > > static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, > > @@ -155,14 +144,6 @@ ngx_http_v3_header_filter(ngx_http_reque > > out = NULL; > > ll = &out; > > > > - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 > > - && r->method != NGX_HTTP_HEAD) > > - { > > - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { > > - return NGX_ERROR; > > - } > > - } > > - > > len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); > > > > if (r->headers_out.status == NGX_HTTP_OK) { > > @@ -607,672 +588,6 @@ ngx_http_v3_header_filter(ngx_http_reque > > > > > > static ngx_int_t > > -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) > > -{ > > - u_char *start, *end, *last; > > - ngx_str_t path; > > - ngx_int_t rc; > > - ngx_uint_t i, push; > > - ngx_table_elt_t *h; > > - ngx_http_v3_loc_conf_t *h3lcf; > > - ngx_http_complex_value_t *pushes; > > - > > - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); > > - > > - if (h3lcf->pushes) { > > - pushes = h3lcf->pushes->elts; > > - > > - for (i = 0; i < h3lcf->pushes->nelts; i++) { > > - > > - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { > > - return NGX_ERROR; > > - } > > - > > - if (path.len == 0) { > > - continue; > > - } > > - > > - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { > > - continue; > > - } > > - > > - rc = ngx_http_v3_push_resource(r, &path, out); > > - > > - if (rc == NGX_ERROR) { > > - return NGX_ERROR; > > - } > > - > > - if (rc == NGX_ABORT) { > > - return NGX_OK; > > - } > > - > > - /* NGX_OK, NGX_DECLINED */ > > - } > > - } > > - > > - if (!h3lcf->push_preload) { > > - return NGX_OK; > > - } > > - > > - for (h = r->headers_out.link; h; h = h->next) { > > - > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > - "http3 parse link: \"%V\"", &h->value); > > - > > - start = h->value.data; > > - end = h->value.data + h->value.len; > > - > > - next_link: > > - > > - while (start < end && *start == ' ') { start++; } > > - > > - if (start == end || *start++ != '<') { > > - continue; > > - } > > - > > - while (start < end && *start == ' ') { start++; } > > - > > - for (last = start; last < end && *last != '>'; last++) { > > - /* void */ > > - } > > - > > - if (last == start || last == end) { > > - continue; > > - } > > - > > - path.len = last - start; > > - path.data = start; > > - > > - start = last + 1; > > - > > - while (start < end && *start == ' ') { start++; } > > - > > - if (start == end) { > > - continue; > > - } > > - > > - if (*start == ',') { > > - start++; > > - goto next_link; > > - } > > - > > - if (*start++ != ';') { > > - continue; > > - } > > - > > - last = ngx_strlchr(start, end, ','); > > - > > - if (last == NULL) { > > - last = end; > > - } > > - > > - push = 0; > > - > > - for ( ;; ) { > > - > > - while (start < last && *start == ' ') { start++; } > > - > > - if (last - start >= 6 > > - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) > > - { > > - start += 6; > > - > > - if (start == last || *start == ' ' || *start == ';') { > > - push = 0; > > - break; > > - } > > - > > - goto next_param; > > - } > > - > > - if (last - start >= 11 > > - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) > > - { > > - start += 11; > > - > > - if (start == last || *start == ' ' || *start == ';') { > > - push = 1; > > - } > > - > > - goto next_param; > > - } > > - > > - if (last - start >= 4 > > - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) > > - { > > - start += 4; > > - > > - while (start < last && *start == ' ') { start++; } > > - > > - if (start == last || *start++ != '"') { > > - goto next_param; > > - } > > - > > - for ( ;; ) { > > - > > - while (start < last && *start == ' ') { start++; } > > - > > - if (last - start >= 7 > > - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) > > - { > > - start += 7; > > - > > - if (start < last && (*start == ' ' || *start == '"')) { > > - push = 1; > > - break; > > - } > > - } > > - > > - while (start < last && *start != ' ' && *start != '"') { > > - start++; > > - } > > - > > - if (start == last) { > > - break; > > - } > > - > > - if (*start == '"') { > > - break; > > - } > > - > > - start++; > > - } > > - } > > - > > - next_param: > > - > > - start = ngx_strlchr(start, last, ';'); > > - > > - if (start == NULL) { > > - break; > > - } > > - > > - start++; > > - } > > - > > - if (push) { > > - while (path.len && path.data[path.len - 1] == ' ') { > > - path.len--; > > - } > > - } > > - > > - if (push && path.len > > - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) > > - { > > - rc = ngx_http_v3_push_resource(r, &path, out); > > - > > - if (rc == NGX_ERROR) { > > - return NGX_ERROR; > > - } > > - > > - if (rc == NGX_ABORT) { > > - return NGX_OK; > > - } > > - > > - /* NGX_OK, NGX_DECLINED */ > > - } > > - > > - if (last < end) { > > - start = last + 1; > > - goto next_link; > > - } > > - } > > - > > - return NGX_OK; > > -} > > - > > - > > -static ngx_int_t > > -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, > > - ngx_chain_t ***ll) > > -{ > > - uint64_t push_id; > > - ngx_int_t rc; > > - ngx_chain_t *cl; > > - ngx_connection_t *c; > > - ngx_http_v3_session_t *h3c; > > - ngx_http_v3_srv_conf_t *h3scf; > > - > > - c = r->connection; > > - h3c = ngx_http_v3_get_session(c); > > - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); > > - > > - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, > > - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", > > - path, h3c->npushing, h3scf->max_concurrent_pushes, > > - h3c->next_push_id, h3c->max_push_id); > > - > > - if (!ngx_path_separator(path->data[0])) { > > - ngx_log_error(NGX_LOG_WARN, c->log, 0, > > - "non-absolute path \"%V\" not pushed", path); > > - return NGX_DECLINED; > > - } > > - > > - if (h3c->max_push_id == (uint64_t) -1 > > - || h3c->next_push_id > h3c->max_push_id) > > - { > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > - "http3 abort pushes due to max_push_id"); > > - return NGX_ABORT; > > - } > > - > > - if (h3c->goaway_push_id != (uint64_t) -1) { > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > - "http3 abort pushes due to goaway"); > > - return NGX_ABORT; > > - } > > - > > - if (h3c->npushing >= h3scf->max_concurrent_pushes) { > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > - "http3 abort pushes due to max_concurrent_pushes"); > > - return NGX_ABORT; > > - } > > - > > - if (r->headers_in.server.len == 0) { > > - return NGX_ABORT; > > - } > > - > > - push_id = h3c->next_push_id++; > > - > > - rc = ngx_http_v3_create_push_request(r, path, push_id); > > - if (rc != NGX_OK) { > > - return rc; > > - } > > - > > - cl = ngx_http_v3_create_push_promise(r, path, push_id); > > - if (cl == NULL) { > > - return NGX_ERROR; > > - } > > - > > - for (**ll = cl; **ll; *ll = &(**ll)->next); > > - > > - return NGX_OK; > > -} > > - > > - > > -static ngx_int_t > > -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, > > - uint64_t push_id) > > -{ > > - ngx_connection_t *c, *pc; > > - ngx_http_request_t *r; > > - ngx_http_log_ctx_t *ctx; > > - ngx_http_connection_t *hc, *phc; > > - ngx_http_core_srv_conf_t *cscf; > > - > > - pc = pr->connection; > > - > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, > > - "http3 create push request id:%uL", push_id); > > - > > - c = ngx_http_v3_create_push_stream(pc, push_id); > > - if (c == NULL) { > > - return NGX_ABORT; > > - } > > - > > -#if (NGX_STAT_STUB) > > - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); > > -#endif > > - > > - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); > > - if (hc == NULL) { > > - ngx_http_close_connection(c); > > - return NGX_ERROR; > > - } > > - > > - phc = ngx_http_quic_get_connection(pc); > > - ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); > > - c->data = hc; > > - > > - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); > > - if (ctx == NULL) { > > - ngx_http_close_connection(c); > > - return NGX_ERROR; > > - } > > - > > - ctx->connection = c; > > - ctx->request = NULL; > > - ctx->current_request = NULL; > > - > > - c->log->handler = pc->log->handler; > > - c->log->data = ctx; > > - c->log->action = "processing pushed request headers"; > > - > > - c->log_error = NGX_ERROR_INFO; > > - > > - r = ngx_http_create_request(c); > > - if (r == NULL) { > > - ngx_http_close_connection(c); > > - return NGX_ERROR; > > - } > > - > > - c->data = r; > > - > > - ngx_str_set(&r->http_protocol, "HTTP/3.0"); > > - > > - r->http_version = NGX_HTTP_VERSION_30; > > - r->method_name = ngx_http_core_get_method; > > - r->method = NGX_HTTP_GET; > > - > > - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); > > - > > - r->header_in = ngx_create_temp_buf(r->pool, > > - cscf->client_header_buffer_size); > > - if (r->header_in == NULL) { > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > - return NGX_ERROR; > > - } > > - > > - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, > > - sizeof(ngx_table_elt_t)) > > - != NGX_OK) > > - { > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > - return NGX_ERROR; > > - } > > - > > - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; > > - > > - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); > > - if (r->schema.data == NULL) { > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > - return NGX_ERROR; > > - } > > - > > - r->schema.len = pr->schema.len; > > - > > - r->uri_start = ngx_pstrdup(r->pool, path); > > - if (r->uri_start == NULL) { > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > - return NGX_ERROR; > > - } > > - > > - r->uri_end = r->uri_start + path->len; > > - > > - if (ngx_http_parse_uri(r) != NGX_OK) { > > - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); > > - return NGX_ERROR; > > - } > > - > > - if (ngx_http_process_request_uri(r) != NGX_OK) { > > - return NGX_ERROR; > > - } > > - > > - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) > > - != NGX_OK) > > - { > > - return NGX_ERROR; > > - } > > - > > - if (pr->headers_in.accept_encoding) { > > - if (ngx_http_v3_set_push_header(r, "accept-encoding", > > - &pr->headers_in.accept_encoding->value) > > - != NGX_OK) > > - { > > - return NGX_ERROR; > > - } > > - } > > - > > - if (pr->headers_in.accept_language) { > > - if (ngx_http_v3_set_push_header(r, "accept-language", > > - &pr->headers_in.accept_language->value) > > - != NGX_OK) > > - { > > - return NGX_ERROR; > > - } > > - } > > - > > - if (pr->headers_in.user_agent) { > > - if (ngx_http_v3_set_push_header(r, "user-agent", > > - &pr->headers_in.user_agent->value) > > - != NGX_OK) > > - { > > - return NGX_ERROR; > > - } > > - } > > - > > - c->read->handler = ngx_http_v3_push_request_handler; > > - c->read->handler = ngx_http_v3_push_request_handler; > > - > > - ngx_post_event(c->read, &ngx_posted_events); > > - > > - return NGX_OK; > > -} > > - > > - > > -static ngx_int_t > > -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, > > - ngx_str_t *value) > > -{ > > - u_char *p; > > - ngx_table_elt_t *h; > > - ngx_http_header_t *hh; > > - ngx_http_core_main_conf_t *cmcf; > > - > > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > - "http3 push header \"%s\": \"%V\"", name, value); > > - > > - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); > > - > > - p = ngx_pnalloc(r->pool, value->len + 1); > > - if (p == NULL) { > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > - return NGX_ERROR; > > - } > > - > > - ngx_memcpy(p, value->data, value->len); > > - p[value->len] = '\0'; > > - > > - h = ngx_list_push(&r->headers_in.headers); > > - if (h == NULL) { > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > - return NGX_ERROR; > > - } > > - > > - h->key.data = (u_char *) name; > > - h->key.len = ngx_strlen(name); > > - h->hash = ngx_hash_key(h->key.data, h->key.len); > > - h->lowcase_key = (u_char *) name; > > - h->value.data = p; > > - h->value.len = value->len; > > - > > - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, > > - h->lowcase_key, h->key.len); > > - > > - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { > > - return NGX_ERROR; > > - } > > - > > - return NGX_OK; > > -} > > - > > - > > -static void > > -ngx_http_v3_push_request_handler(ngx_event_t *ev) > > -{ > > - ngx_connection_t *c; > > - ngx_http_request_t *r; > > - > > - c = ev->data; > > - r = c->data; > > - > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); > > - > > - ngx_http_process_request(r); > > -} > > - > > - > > -static ngx_chain_t * > > -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, > > - uint64_t push_id) > > -{ > > - size_t n, len; > > - ngx_buf_t *b; > > - ngx_chain_t *hl, *cl; > > - ngx_http_v3_session_t *h3c; > > - > > - h3c = ngx_http_v3_get_session(r->connection); > > - > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > - "http3 create push promise id:%uL", push_id); > > - > > - len = ngx_http_v3_encode_varlen_int(NULL, push_id); > > - > > - len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); > > - > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > - NGX_HTTP_V3_HEADER_METHOD_GET); > > - > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > - NGX_HTTP_V3_HEADER_AUTHORITY, > > - NULL, r->headers_in.server.len); > > - > > - if (path->len == 1 && path->data[0] == '/') { > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > - NGX_HTTP_V3_HEADER_PATH_ROOT); > > - > > - } else { > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > - NGX_HTTP_V3_HEADER_PATH_ROOT, > > - NULL, path->len); > > - } > > - > > - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); > > - > > - } else if (r->schema.len == 4 > > - && ngx_strncmp(r->schema.data, "http", 4) == 0) > > - { > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP); > > - > > - } else { > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP, > > - NULL, r->schema.len); > > - } > > - > > - if (r->headers_in.accept_encoding) { > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, > > - r->headers_in.accept_encoding->value.len); > > - } > > - > > - if (r->headers_in.accept_language) { > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, > > - r->headers_in.accept_language->value.len); > > - } > > - > > - if (r->headers_in.user_agent) { > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, > > - r->headers_in.user_agent->value.len); > > - } > > - > > - b = ngx_create_temp_buf(r->pool, len); > > - if (b == NULL) { > > - return NULL; > > - } > > - > > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); > > - > > - b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, > > - 0, 0, 0); > > - > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > - NGX_HTTP_V3_HEADER_METHOD_GET); > > - > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > - NGX_HTTP_V3_HEADER_AUTHORITY, > > - r->headers_in.server.data, > > - r->headers_in.server.len); > > - > > - if (path->len == 1 && path->data[0] == '/') { > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > - NGX_HTTP_V3_HEADER_PATH_ROOT); > > - > > - } else { > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > - NGX_HTTP_V3_HEADER_PATH_ROOT, > > - path->data, path->len); > > - } > > - > > - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); > > - > > - } else if (r->schema.len == 4 > > - && ngx_strncmp(r->schema.data, "http", 4) == 0) > > - { > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP); > > - > > - } else { > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP, > > - r->schema.data, r->schema.len); > > - } > > - > > - if (r->headers_in.accept_encoding) { > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, > > - r->headers_in.accept_encoding->value.data, > > - r->headers_in.accept_encoding->value.len); > > - } > > - > > - if (r->headers_in.accept_language) { > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, > > - r->headers_in.accept_language->value.data, > > - r->headers_in.accept_language->value.len); > > - } > > - > > - if (r->headers_in.user_agent) { > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > - NGX_HTTP_V3_HEADER_USER_AGENT, > > - r->headers_in.user_agent->value.data, > > - r->headers_in.user_agent->value.len); > > - } > > - > > - cl = ngx_alloc_chain_link(r->pool); > > - if (cl == NULL) { > > - return NULL; > > - } > > - > > - cl->buf = b; > > - cl->next = NULL; > > - > > - n = b->last - b->pos; > > - > > - h3c->payload_bytes += n; > > - > > - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) > > - + ngx_http_v3_encode_varlen_int(NULL, n); > > - > > - b = ngx_create_temp_buf(r->pool, len); > > - if (b == NULL) { > > - return NULL; > > - } > > - > > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, > > - NGX_HTTP_V3_FRAME_PUSH_PROMISE); > > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); > > - > > - hl = ngx_alloc_chain_link(r->pool); > > - if (hl == NULL) { > > - return NULL; > > - } > > - > > - hl->buf = b; > > - hl->next = cl; > > - > > - return hl; > > -} > > - > > - > > -static ngx_int_t > > ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) > > { > > u_char *chunk; > > diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c > > --- a/src/http/v3/ngx_http_v3_module.c > > +++ b/src/http/v3/ngx_http_v3_module.c > > @@ -18,10 +18,6 @@ static char *ngx_http_v3_merge_srv_conf( > > void *child); > > static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, > > void *conf); > > -static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); > > -static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, > > - void *child); > > -static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); > > > > > > static ngx_command_t ngx_http_v3_commands[] = { > > @@ -40,13 +36,6 @@ static ngx_command_t ngx_http_v3_comman > > offsetof(ngx_http_v3_srv_conf_t, enable_hq), > > NULL }, > > > > - { ngx_string("http3_max_concurrent_pushes"), > > - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > > - ngx_conf_set_num_slot, > > - NGX_HTTP_SRV_CONF_OFFSET, > > - offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), > > - NULL }, > > - > > { ngx_string("http3_max_concurrent_streams"), > > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > > ngx_conf_set_num_slot, > > @@ -54,20 +43,6 @@ static ngx_command_t ngx_http_v3_comman > > offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), > > NULL }, > > > > - { ngx_string("http3_push"), > > - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, > > - ngx_http_v3_push, > > - NGX_HTTP_LOC_CONF_OFFSET, > > - 0, > > - NULL }, > > - > > - { ngx_string("http3_push_preload"), > > - 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_v3_loc_conf_t, push_preload), > > - NULL }, > > - > > { ngx_string("http3_stream_buffer_size"), > > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > > ngx_conf_set_size_slot, > > @@ -117,8 +92,8 @@ static ngx_http_module_t ngx_http_v3_mo > > ngx_http_v3_create_srv_conf, /* create server configuration */ > > ngx_http_v3_merge_srv_conf, /* merge server configuration */ > > > > - ngx_http_v3_create_loc_conf, /* create location configuration */ > > - ngx_http_v3_merge_loc_conf /* merge location configuration */ > > + NULL, /* create location configuration */ > > + NULL /* merge location configuration */ > > }; > > > > > > @@ -224,7 +199,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * > > h3scf->enable = NGX_CONF_UNSET; > > h3scf->enable_hq = NGX_CONF_UNSET; > > h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; > > - h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; > > h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; > > > > h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; > > @@ -255,9 +229,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > > > > ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); > > > > - ngx_conf_merge_uint_value(conf->max_concurrent_pushes, > > - prev->max_concurrent_pushes, 10); > > - > > ngx_conf_merge_uint_value(conf->max_concurrent_streams, > > prev->max_concurrent_streams, 128); > > > > @@ -416,102 +387,3 @@ failed: > > > > return NGX_CONF_ERROR; > > } > > - > > - > > -static void * > > -ngx_http_v3_create_loc_conf(ngx_conf_t *cf) > > -{ > > - ngx_http_v3_loc_conf_t *h3lcf; > > - > > - h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); > > - if (h3lcf == NULL) { > > - return NULL; > > - } > > - > > - /* > > - * set by ngx_pcalloc(): > > - * > > - * h3lcf->pushes = NULL; > > - */ > > - > > - h3lcf->push_preload = NGX_CONF_UNSET; > > - h3lcf->push = NGX_CONF_UNSET; > > - > > - return h3lcf; > > -} > > - > > - > > -static char * > > -ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) > > -{ > > - ngx_http_v3_loc_conf_t *prev = parent; > > - ngx_http_v3_loc_conf_t *conf = child; > > - > > - ngx_conf_merge_value(conf->push, prev->push, 1); > > - > > - if (conf->push && conf->pushes == NULL) { > > - conf->pushes = prev->pushes; > > - } > > - > > - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); > > - > > - return NGX_CONF_OK; > > -} > > - > > - > > -static char * > > -ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) > > -{ > > - ngx_http_v3_loc_conf_t *h3lcf = conf; > > - > > - ngx_str_t *value; > > - ngx_http_complex_value_t *cv; > > - ngx_http_compile_complex_value_t ccv; > > - > > - value = cf->args->elts; > > - > > - if (ngx_strcmp(value[1].data, "off") == 0) { > > - > > - if (h3lcf->pushes) { > > - return "\"off\" parameter cannot be used with URI"; > > - } > > - > > - if (h3lcf->push == 0) { > > - return "is duplicate"; > > - } > > - > > - h3lcf->push = 0; > > - return NGX_CONF_OK; > > - } > > - > > - if (h3lcf->push == 0) { > > - return "URI cannot be used with \"off\" parameter"; > > - } > > - > > - h3lcf->push = 1; > > - > > - if (h3lcf->pushes == NULL) { > > - h3lcf->pushes = ngx_array_create(cf->pool, 1, > > - sizeof(ngx_http_complex_value_t)); > > - if (h3lcf->pushes == NULL) { > > - return NGX_CONF_ERROR; > > - } > > - } > > - > > - cv = ngx_array_push(h3lcf->pushes); > > - if (cv == NULL) { > > - return NGX_CONF_ERROR; > > - } > > - > > - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); > > - > > - ccv.cf = cf; > > - ccv.value = &value[1]; > > - ccv.complex_value = cv; > > - > > - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { > > - return NGX_CONF_ERROR; > > - } > > - > > - return NGX_CONF_OK; > > -} > > diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c > > --- a/src/http/v3/ngx_http_v3_uni.c > > +++ b/src/http/v3/ngx_http_v3_uni.c > > @@ -16,19 +16,10 @@ typedef struct { > > } ngx_http_v3_uni_stream_t; > > > > > > -typedef struct { > > - ngx_queue_t queue; > > - uint64_t id; > > - ngx_connection_t *connection; > > - ngx_uint_t *npushing; > > -} ngx_http_v3_push_t; > > - > > - > > static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); > > static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); > > static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); > > static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); > > -static void ngx_http_v3_push_cleanup(void *data); > > static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, > > ngx_uint_t type); > > > > @@ -316,78 +307,6 @@ ngx_http_v3_uni_dummy_write_handler(ngx_ > > } > > > > > > -ngx_connection_t * > > -ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) > > -{ > > - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; > > - size_t n; > > - ngx_connection_t *sc; > > - ngx_pool_cleanup_t *cln; > > - ngx_http_v3_push_t *push; > > - ngx_http_v3_session_t *h3c; > > - > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > > - "http3 create push stream id:%uL", push_id); > > - > > - sc = ngx_quic_open_stream(c, 0); > > - if (sc == NULL) { > > - goto failed; > > - } > > - > > - p = buf; > > - p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); > > - p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); > > - n = p - buf; > > - > > - h3c = ngx_http_v3_get_session(c); > > - h3c->total_bytes += n; > > - > > - if (sc->send(sc, buf, n) != (ssize_t) n) { > > - goto failed; > > - } > > - > > - cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); > > - if (cln == NULL) { > > - goto failed; > > - } > > - > > - h3c->npushing++; > > - > > - cln->handler = ngx_http_v3_push_cleanup; > > - > > - push = cln->data; > > - push->id = push_id; > > - push->connection = sc; > > - push->npushing = &h3c->npushing; > > - > > - ngx_queue_insert_tail(&h3c->pushing, &push->queue); > > - > > - return sc; > > - > > -failed: > > - > > - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); > > - > > - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, > > - "failed to create push stream"); > > - if (sc) { > > - ngx_http_v3_close_uni_stream(sc); > > - } > > - > > - return NULL; > > -} > > - > > - > > -static void > > -ngx_http_v3_push_cleanup(void *data) > > -{ > > - ngx_http_v3_push_t *push = data; > > - > > - ngx_queue_remove(&push->queue); > > - (*push->npushing)--; > > -} > > - > > - > > static ngx_connection_t * > > ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) > > { > > @@ -696,19 +615,9 @@ failed: > > ngx_int_t > > ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) > > { > > - ngx_http_v3_session_t *h3c; > > - > > - h3c = ngx_http_v3_get_session(c); > > - > > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > > "http3 MAX_PUSH_ID:%uL", max_push_id); > > > > - if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { > > - return NGX_HTTP_V3_ERR_ID_ERROR; > > - } > > - > > - h3c->max_push_id = max_push_id; > > - > > return NGX_OK; > > } > > > > @@ -716,14 +625,8 @@ ngx_http_v3_set_max_push_id(ngx_connecti > > ngx_int_t > > ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) > > { > > - ngx_http_v3_session_t *h3c; > > - > > - h3c = ngx_http_v3_get_session(c); > > - > > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); > > > > - h3c->goaway_push_id = push_id; > > - > > return NGX_OK; > > } > > Shouldn't we simply skip MAX_PUSH_ID and GOAWAY frames somewhere > at ngx_http_v3_parse_control()? Yes, we can skip them. > > @@ -731,40 +634,9 @@ ngx_http_v3_goaway(ngx_connection_t *c, > > ngx_int_t > > ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) > > { > > - ngx_queue_t *q; > > - ngx_http_request_t *r; > > - ngx_http_v3_push_t *push; > > - ngx_http_v3_session_t *h3c; > > - > > - h3c = ngx_http_v3_get_session(c); > > - > > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > > "http3 CANCEL_PUSH:%uL", push_id); > > > > - if (push_id >= h3c->next_push_id) { > > - return NGX_HTTP_V3_ERR_ID_ERROR; > > - } > > - > > - for (q = ngx_queue_head(&h3c->pushing); > > - q != ngx_queue_sentinel(&h3c->pushing); > > - q = ngx_queue_next(q)) > > - { > > - push = (ngx_http_v3_push_t *) q; > > - > > - if (push->id != push_id) { > > - continue; > > - } > > - > > - r = push->connection->data; > > - > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > - "http3 cancel push"); > > - > > - ngx_http_finalize_request(r, NGX_HTTP_CLOSE); > > - > > - break; > > - } > > - > > return NGX_OK; > > } > > > > And CANCEL_PUSH probably worth an explicit error > (https://www.rfc-editor.org/rfc/rfc9114.html#name-cancel_push): > > : If a server receives a CANCEL_PUSH frame for a push ID that has > : not yet been mentioned by a PUSH_PROMISE frame, this MUST be > : treated as a connection error of type H3_ID_ERROR. > > Since no pushes are expected to appear on the connection, > returning NGX_HTTP_V3_ERR_ID_ERROR seems to be correct option. > > Similarly to the above, handling this at > ngx_http_v3_parse_control() might be easier. OK. > > diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h > > --- a/src/http/v3/ngx_http_v3_uni.h > > +++ b/src/http/v3/ngx_http_v3_uni.h > > @@ -17,8 +17,6 @@ > > void ngx_http_v3_init_uni_stream(ngx_connection_t *c); > > ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); > > > > -ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, > > - uint64_t push_id); > > ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, > > uint64_t max_push_id); > > ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); > > Otherwise looks good. Diff attached. -- Roman Arutyunyan -------------- next part -------------- # HG changeset patch # User Roman Arutyunyan # Date 1684470628 -14400 # Fri May 19 08:30:28 2023 +0400 # Branch quic # Node ID 3f8c9f2f31a5d91291139b9049769e47f8de2c0c # Parent 49a8edf7bf31b78681399cd7e93a8516788607dd [mq]: v3-no-push-fix 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 @@ -1159,10 +1159,7 @@ ngx_http_v3_parse_control(ngx_connection sw_first_type, sw_type, sw_length, - sw_cancel_push, sw_settings, - sw_max_push_id, - sw_goaway, sw_skip }; @@ -1212,6 +1209,10 @@ ngx_http_v3_parse_control(ngx_connection return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } + if (st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + st->state = sw_length; break; @@ -1233,22 +1234,10 @@ ngx_http_v3_parse_control(ngx_connection switch (st->type) { - case NGX_HTTP_V3_FRAME_CANCEL_PUSH: - st->state = sw_cancel_push; - break; - case NGX_HTTP_V3_FRAME_SETTINGS: st->state = sw_settings; break; - case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: - st->state = sw_max_push_id; - break; - - case NGX_HTTP_V3_FRAME_GOAWAY: - st->state = sw_goaway; - break; - default: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse skip unknown frame"); @@ -1257,30 +1246,6 @@ ngx_http_v3_parse_control(ngx_connection break; - case sw_cancel_push: - - ngx_http_v3_parse_start_local(b, &loc, st->length); - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - - ngx_http_v3_parse_end_local(b, &loc, &st->length); - - if (st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_cancel_push(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - st->state = sw_type; - break; - case sw_settings: ngx_http_v3_parse_start_local(b, &loc, st->length); @@ -1303,54 +1268,6 @@ ngx_http_v3_parse_control(ngx_connection break; - case sw_max_push_id: - - ngx_http_v3_parse_start_local(b, &loc, st->length); - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - - ngx_http_v3_parse_end_local(b, &loc, &st->length); - - if (st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - st->state = sw_type; - break; - - case sw_goaway: - - ngx_http_v3_parse_start_local(b, &loc, st->length); - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - - ngx_http_v3_parse_end_local(b, &loc, &st->length); - - if (st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_goaway(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - st->state = sw_type; - break; - case sw_skip: rc = ngx_http_v3_parse_skip(b, &st->length); diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -613,35 +613,6 @@ failed: ngx_int_t -ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) -{ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 MAX_PUSH_ID:%uL", max_push_id); - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) -{ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) -{ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 CANCEL_PUSH:%uL", push_id); - - return NGX_OK; -} - - -ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h --- a/src/http/v3/ngx_http_v3_uni.h +++ b/src/http/v3/ngx_http_v3_uni.h @@ -17,10 +17,6 @@ void ngx_http_v3_init_uni_stream(ngx_connection_t *c); ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); -ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, - uint64_t max_push_id); -ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); -ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); From mdounin at mdounin.ru Fri May 19 16:09:40 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 19 May 2023 19:09:40 +0300 Subject: [PATCH 3 of 4] HTTP/3: removed server push support In-Reply-To: <20230519044404.sanvxmuhkzrw7ezp@N00W24XTQX> References: <49a8edf7bf31b7868139.1684071534@arut-laptop> <20230519044404.sanvxmuhkzrw7ezp@N00W24XTQX> Message-ID: Hello! On Fri, May 19, 2023 at 08:44:04AM +0400, Roman Arutyunyan wrote: > Hi, > > On Fri, May 19, 2023 at 12:41:32AM +0300, Maxim Dounin wrote: > > Hello! > > > > On Sun, May 14, 2023 at 05:38:54PM +0400, Roman Arutyunyan wrote: > > > > > # HG changeset patch > > > # User Roman Arutyunyan > > > # Date 1683871330 -14400 > > > # Fri May 12 10:02:10 2023 +0400 > > > # Branch quic > > > # Node ID 49a8edf7bf31b78681399cd7e93a8516788607dd > > > # Parent adcc6d8acfd47c1344b121fceeb94fbcc3f8b5c0 > > > HTTP/3: removed server push support. > > > > > > diff --git a/README b/README > > > --- a/README > > > +++ b/README > > > @@ -135,10 +135,7 @@ 3. Configuration > > > http3 > > > http3_hq > > > http3_stream_buffer_size > > > - http3_max_concurrent_pushes > > > http3_max_concurrent_streams > > > - http3_push > > > - http3_push_preload > > > > > > In http, an additional variable is available: $http3. > > > The value of $http3 is "h3" for HTTP/3 connections, > > > @@ -226,13 +223,6 @@ 4. Directives > > > - initial_max_stream_data_uni > > > > > > > > > - Syntax: http3_max_concurrent_pushes number; > > > - Default: http3_max_concurrent_pushes 10; > > > - Context: http, server > > > - > > > - Limits the maximum number of concurrent push requests in a connection. > > > - > > > - > > > Syntax: http3_max_concurrent_streams number; > > > Default: http3_max_concurrent_streams 128; > > > Context: http, server > > > @@ -240,31 +230,6 @@ 4. Directives > > > Sets the maximum number of concurrent HTTP/3 streams in a connection. > > > > > > > > > - Syntax: http3_push uri | off; > > > - Default: http3_push off; > > > - Context: http, server, location > > > - > > > - Pre-emptively sends (pushes) a request to the specified uri along with > > > - the response to the original request. Only relative URIs with absolute > > > - path will be processed, for example: > > > - > > > - http3_push /static/css/main.css; > > > - > > > - The uri value can contain variables. > > > - > > > - Several http3_push directives can be specified on the same configuration > > > - level. The off parameter cancels the effect of the http3_push directives > > > - inherited from the previous configuration level. > > > - > > > - > > > - Syntax: http3_push_preload on | off; > > > - Default: http3_push_preload off; > > > - Context: http, server, location > > > - > > > - Enables automatic conversion of preload links specified in the “Link” > > > - response header fields into push requests. > > > - > > > - > > > Syntax: http3 on | off; > > > Default: http3 on; > > > Context: http, server > > > diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c > > > --- a/src/http/v3/ngx_http_v3.c > > > +++ b/src/http/v3/ngx_http_v3.c > > > @@ -30,11 +30,7 @@ ngx_http_v3_init_session(ngx_connection_ > > > goto failed; > > > } > > > > > > - h3c->max_push_id = (uint64_t) -1; > > > - h3c->goaway_push_id = (uint64_t) -1; > > > - > > > ngx_queue_init(&h3c->blocked); > > > - ngx_queue_init(&h3c->pushing); > > > > > > h3c->keepalive.log = c->log; > > > h3c->keepalive.data = c; > > > diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h > > > --- a/src/http/v3/ngx_http_v3.h > > > +++ b/src/http/v3/ngx_http_v3.h > > > @@ -106,19 +106,11 @@ typedef struct { > > > ngx_flag_t enable_hq; > > > size_t max_table_capacity; > > > ngx_uint_t max_blocked_streams; > > > - ngx_uint_t max_concurrent_pushes; > > > ngx_uint_t max_concurrent_streams; > > > ngx_quic_conf_t quic; > > > } ngx_http_v3_srv_conf_t; > > > > > > > > > -typedef struct { > > > - ngx_flag_t push_preload; > > > - ngx_flag_t push; > > > - ngx_array_t *pushes; > > > -} ngx_http_v3_loc_conf_t; > > > - > > > - > > > struct ngx_http_v3_parse_s { > > > size_t header_limit; > > > ngx_http_v3_parse_headers_t headers; > > > @@ -136,11 +128,6 @@ struct ngx_http_v3_session_s { > > > ngx_queue_t blocked; > > > ngx_uint_t nblocked; > > > > > > - ngx_queue_t pushing; > > > - ngx_uint_t npushing; > > > - uint64_t next_push_id; > > > - uint64_t max_push_id; > > > - uint64_t goaway_push_id; > > > uint64_t next_request_id; > > > > > > off_t total_bytes; > > > diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c > > > --- a/src/http/v3/ngx_http_v3_filter_module.c > > > +++ b/src/http/v3/ngx_http_v3_filter_module.c > > > @@ -36,17 +36,6 @@ typedef struct { > > > > > > > > > static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); > > > -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, > > > - ngx_chain_t ***out); > > > -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, > > > - ngx_str_t *path, ngx_chain_t ***out); > > > -static ngx_int_t ngx_http_v3_create_push_request( > > > - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); > > > -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, > > > - const char *name, ngx_str_t *value); > > > -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); > > > -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, > > > - ngx_str_t *path, uint64_t push_id); > > > static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, > > > ngx_chain_t *in); > > > static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, > > > @@ -155,14 +144,6 @@ ngx_http_v3_header_filter(ngx_http_reque > > > out = NULL; > > > ll = &out; > > > > > > - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 > > > - && r->method != NGX_HTTP_HEAD) > > > - { > > > - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { > > > - return NGX_ERROR; > > > - } > > > - } > > > - > > > len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); > > > > > > if (r->headers_out.status == NGX_HTTP_OK) { > > > @@ -607,672 +588,6 @@ ngx_http_v3_header_filter(ngx_http_reque > > > > > > > > > static ngx_int_t > > > -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) > > > -{ > > > - u_char *start, *end, *last; > > > - ngx_str_t path; > > > - ngx_int_t rc; > > > - ngx_uint_t i, push; > > > - ngx_table_elt_t *h; > > > - ngx_http_v3_loc_conf_t *h3lcf; > > > - ngx_http_complex_value_t *pushes; > > > - > > > - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); > > > - > > > - if (h3lcf->pushes) { > > > - pushes = h3lcf->pushes->elts; > > > - > > > - for (i = 0; i < h3lcf->pushes->nelts; i++) { > > > - > > > - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { > > > - return NGX_ERROR; > > > - } > > > - > > > - if (path.len == 0) { > > > - continue; > > > - } > > > - > > > - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { > > > - continue; > > > - } > > > - > > > - rc = ngx_http_v3_push_resource(r, &path, out); > > > - > > > - if (rc == NGX_ERROR) { > > > - return NGX_ERROR; > > > - } > > > - > > > - if (rc == NGX_ABORT) { > > > - return NGX_OK; > > > - } > > > - > > > - /* NGX_OK, NGX_DECLINED */ > > > - } > > > - } > > > - > > > - if (!h3lcf->push_preload) { > > > - return NGX_OK; > > > - } > > > - > > > - for (h = r->headers_out.link; h; h = h->next) { > > > - > > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > > - "http3 parse link: \"%V\"", &h->value); > > > - > > > - start = h->value.data; > > > - end = h->value.data + h->value.len; > > > - > > > - next_link: > > > - > > > - while (start < end && *start == ' ') { start++; } > > > - > > > - if (start == end || *start++ != '<') { > > > - continue; > > > - } > > > - > > > - while (start < end && *start == ' ') { start++; } > > > - > > > - for (last = start; last < end && *last != '>'; last++) { > > > - /* void */ > > > - } > > > - > > > - if (last == start || last == end) { > > > - continue; > > > - } > > > - > > > - path.len = last - start; > > > - path.data = start; > > > - > > > - start = last + 1; > > > - > > > - while (start < end && *start == ' ') { start++; } > > > - > > > - if (start == end) { > > > - continue; > > > - } > > > - > > > - if (*start == ',') { > > > - start++; > > > - goto next_link; > > > - } > > > - > > > - if (*start++ != ';') { > > > - continue; > > > - } > > > - > > > - last = ngx_strlchr(start, end, ','); > > > - > > > - if (last == NULL) { > > > - last = end; > > > - } > > > - > > > - push = 0; > > > - > > > - for ( ;; ) { > > > - > > > - while (start < last && *start == ' ') { start++; } > > > - > > > - if (last - start >= 6 > > > - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) > > > - { > > > - start += 6; > > > - > > > - if (start == last || *start == ' ' || *start == ';') { > > > - push = 0; > > > - break; > > > - } > > > - > > > - goto next_param; > > > - } > > > - > > > - if (last - start >= 11 > > > - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) > > > - { > > > - start += 11; > > > - > > > - if (start == last || *start == ' ' || *start == ';') { > > > - push = 1; > > > - } > > > - > > > - goto next_param; > > > - } > > > - > > > - if (last - start >= 4 > > > - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) > > > - { > > > - start += 4; > > > - > > > - while (start < last && *start == ' ') { start++; } > > > - > > > - if (start == last || *start++ != '"') { > > > - goto next_param; > > > - } > > > - > > > - for ( ;; ) { > > > - > > > - while (start < last && *start == ' ') { start++; } > > > - > > > - if (last - start >= 7 > > > - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) > > > - { > > > - start += 7; > > > - > > > - if (start < last && (*start == ' ' || *start == '"')) { > > > - push = 1; > > > - break; > > > - } > > > - } > > > - > > > - while (start < last && *start != ' ' && *start != '"') { > > > - start++; > > > - } > > > - > > > - if (start == last) { > > > - break; > > > - } > > > - > > > - if (*start == '"') { > > > - break; > > > - } > > > - > > > - start++; > > > - } > > > - } > > > - > > > - next_param: > > > - > > > - start = ngx_strlchr(start, last, ';'); > > > - > > > - if (start == NULL) { > > > - break; > > > - } > > > - > > > - start++; > > > - } > > > - > > > - if (push) { > > > - while (path.len && path.data[path.len - 1] == ' ') { > > > - path.len--; > > > - } > > > - } > > > - > > > - if (push && path.len > > > - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) > > > - { > > > - rc = ngx_http_v3_push_resource(r, &path, out); > > > - > > > - if (rc == NGX_ERROR) { > > > - return NGX_ERROR; > > > - } > > > - > > > - if (rc == NGX_ABORT) { > > > - return NGX_OK; > > > - } > > > - > > > - /* NGX_OK, NGX_DECLINED */ > > > - } > > > - > > > - if (last < end) { > > > - start = last + 1; > > > - goto next_link; > > > - } > > > - } > > > - > > > - return NGX_OK; > > > -} > > > - > > > - > > > -static ngx_int_t > > > -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, > > > - ngx_chain_t ***ll) > > > -{ > > > - uint64_t push_id; > > > - ngx_int_t rc; > > > - ngx_chain_t *cl; > > > - ngx_connection_t *c; > > > - ngx_http_v3_session_t *h3c; > > > - ngx_http_v3_srv_conf_t *h3scf; > > > - > > > - c = r->connection; > > > - h3c = ngx_http_v3_get_session(c); > > > - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); > > > - > > > - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", > > > - path, h3c->npushing, h3scf->max_concurrent_pushes, > > > - h3c->next_push_id, h3c->max_push_id); > > > - > > > - if (!ngx_path_separator(path->data[0])) { > > > - ngx_log_error(NGX_LOG_WARN, c->log, 0, > > > - "non-absolute path \"%V\" not pushed", path); > > > - return NGX_DECLINED; > > > - } > > > - > > > - if (h3c->max_push_id == (uint64_t) -1 > > > - || h3c->next_push_id > h3c->max_push_id) > > > - { > > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > - "http3 abort pushes due to max_push_id"); > > > - return NGX_ABORT; > > > - } > > > - > > > - if (h3c->goaway_push_id != (uint64_t) -1) { > > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > - "http3 abort pushes due to goaway"); > > > - return NGX_ABORT; > > > - } > > > - > > > - if (h3c->npushing >= h3scf->max_concurrent_pushes) { > > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > - "http3 abort pushes due to max_concurrent_pushes"); > > > - return NGX_ABORT; > > > - } > > > - > > > - if (r->headers_in.server.len == 0) { > > > - return NGX_ABORT; > > > - } > > > - > > > - push_id = h3c->next_push_id++; > > > - > > > - rc = ngx_http_v3_create_push_request(r, path, push_id); > > > - if (rc != NGX_OK) { > > > - return rc; > > > - } > > > - > > > - cl = ngx_http_v3_create_push_promise(r, path, push_id); > > > - if (cl == NULL) { > > > - return NGX_ERROR; > > > - } > > > - > > > - for (**ll = cl; **ll; *ll = &(**ll)->next); > > > - > > > - return NGX_OK; > > > -} > > > - > > > - > > > -static ngx_int_t > > > -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, > > > - uint64_t push_id) > > > -{ > > > - ngx_connection_t *c, *pc; > > > - ngx_http_request_t *r; > > > - ngx_http_log_ctx_t *ctx; > > > - ngx_http_connection_t *hc, *phc; > > > - ngx_http_core_srv_conf_t *cscf; > > > - > > > - pc = pr->connection; > > > - > > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, > > > - "http3 create push request id:%uL", push_id); > > > - > > > - c = ngx_http_v3_create_push_stream(pc, push_id); > > > - if (c == NULL) { > > > - return NGX_ABORT; > > > - } > > > - > > > -#if (NGX_STAT_STUB) > > > - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); > > > -#endif > > > - > > > - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); > > > - if (hc == NULL) { > > > - ngx_http_close_connection(c); > > > - return NGX_ERROR; > > > - } > > > - > > > - phc = ngx_http_quic_get_connection(pc); > > > - ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); > > > - c->data = hc; > > > - > > > - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); > > > - if (ctx == NULL) { > > > - ngx_http_close_connection(c); > > > - return NGX_ERROR; > > > - } > > > - > > > - ctx->connection = c; > > > - ctx->request = NULL; > > > - ctx->current_request = NULL; > > > - > > > - c->log->handler = pc->log->handler; > > > - c->log->data = ctx; > > > - c->log->action = "processing pushed request headers"; > > > - > > > - c->log_error = NGX_ERROR_INFO; > > > - > > > - r = ngx_http_create_request(c); > > > - if (r == NULL) { > > > - ngx_http_close_connection(c); > > > - return NGX_ERROR; > > > - } > > > - > > > - c->data = r; > > > - > > > - ngx_str_set(&r->http_protocol, "HTTP/3.0"); > > > - > > > - r->http_version = NGX_HTTP_VERSION_30; > > > - r->method_name = ngx_http_core_get_method; > > > - r->method = NGX_HTTP_GET; > > > - > > > - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); > > > - > > > - r->header_in = ngx_create_temp_buf(r->pool, > > > - cscf->client_header_buffer_size); > > > - if (r->header_in == NULL) { > > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > > - return NGX_ERROR; > > > - } > > > - > > > - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, > > > - sizeof(ngx_table_elt_t)) > > > - != NGX_OK) > > > - { > > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > > - return NGX_ERROR; > > > - } > > > - > > > - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; > > > - > > > - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); > > > - if (r->schema.data == NULL) { > > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > > - return NGX_ERROR; > > > - } > > > - > > > - r->schema.len = pr->schema.len; > > > - > > > - r->uri_start = ngx_pstrdup(r->pool, path); > > > - if (r->uri_start == NULL) { > > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > > - return NGX_ERROR; > > > - } > > > - > > > - r->uri_end = r->uri_start + path->len; > > > - > > > - if (ngx_http_parse_uri(r) != NGX_OK) { > > > - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); > > > - return NGX_ERROR; > > > - } > > > - > > > - if (ngx_http_process_request_uri(r) != NGX_OK) { > > > - return NGX_ERROR; > > > - } > > > - > > > - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) > > > - != NGX_OK) > > > - { > > > - return NGX_ERROR; > > > - } > > > - > > > - if (pr->headers_in.accept_encoding) { > > > - if (ngx_http_v3_set_push_header(r, "accept-encoding", > > > - &pr->headers_in.accept_encoding->value) > > > - != NGX_OK) > > > - { > > > - return NGX_ERROR; > > > - } > > > - } > > > - > > > - if (pr->headers_in.accept_language) { > > > - if (ngx_http_v3_set_push_header(r, "accept-language", > > > - &pr->headers_in.accept_language->value) > > > - != NGX_OK) > > > - { > > > - return NGX_ERROR; > > > - } > > > - } > > > - > > > - if (pr->headers_in.user_agent) { > > > - if (ngx_http_v3_set_push_header(r, "user-agent", > > > - &pr->headers_in.user_agent->value) > > > - != NGX_OK) > > > - { > > > - return NGX_ERROR; > > > - } > > > - } > > > - > > > - c->read->handler = ngx_http_v3_push_request_handler; > > > - c->read->handler = ngx_http_v3_push_request_handler; > > > - > > > - ngx_post_event(c->read, &ngx_posted_events); > > > - > > > - return NGX_OK; > > > -} > > > - > > > - > > > -static ngx_int_t > > > -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, > > > - ngx_str_t *value) > > > -{ > > > - u_char *p; > > > - ngx_table_elt_t *h; > > > - ngx_http_header_t *hh; > > > - ngx_http_core_main_conf_t *cmcf; > > > - > > > - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > > - "http3 push header \"%s\": \"%V\"", name, value); > > > - > > > - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); > > > - > > > - p = ngx_pnalloc(r->pool, value->len + 1); > > > - if (p == NULL) { > > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > > - return NGX_ERROR; > > > - } > > > - > > > - ngx_memcpy(p, value->data, value->len); > > > - p[value->len] = '\0'; > > > - > > > - h = ngx_list_push(&r->headers_in.headers); > > > - if (h == NULL) { > > > - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); > > > - return NGX_ERROR; > > > - } > > > - > > > - h->key.data = (u_char *) name; > > > - h->key.len = ngx_strlen(name); > > > - h->hash = ngx_hash_key(h->key.data, h->key.len); > > > - h->lowcase_key = (u_char *) name; > > > - h->value.data = p; > > > - h->value.len = value->len; > > > - > > > - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, > > > - h->lowcase_key, h->key.len); > > > - > > > - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { > > > - return NGX_ERROR; > > > - } > > > - > > > - return NGX_OK; > > > -} > > > - > > > - > > > -static void > > > -ngx_http_v3_push_request_handler(ngx_event_t *ev) > > > -{ > > > - ngx_connection_t *c; > > > - ngx_http_request_t *r; > > > - > > > - c = ev->data; > > > - r = c->data; > > > - > > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); > > > - > > > - ngx_http_process_request(r); > > > -} > > > - > > > - > > > -static ngx_chain_t * > > > -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, > > > - uint64_t push_id) > > > -{ > > > - size_t n, len; > > > - ngx_buf_t *b; > > > - ngx_chain_t *hl, *cl; > > > - ngx_http_v3_session_t *h3c; > > > - > > > - h3c = ngx_http_v3_get_session(r->connection); > > > - > > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > > - "http3 create push promise id:%uL", push_id); > > > - > > > - len = ngx_http_v3_encode_varlen_int(NULL, push_id); > > > - > > > - len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); > > > - > > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_METHOD_GET); > > > - > > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_AUTHORITY, > > > - NULL, r->headers_in.server.len); > > > - > > > - if (path->len == 1 && path->data[0] == '/') { > > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_PATH_ROOT); > > > - > > > - } else { > > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_PATH_ROOT, > > > - NULL, path->len); > > > - } > > > - > > > - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { > > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); > > > - > > > - } else if (r->schema.len == 4 > > > - && ngx_strncmp(r->schema.data, "http", 4) == 0) > > > - { > > > - len += ngx_http_v3_encode_field_ri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP); > > > - > > > - } else { > > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP, > > > - NULL, r->schema.len); > > > - } > > > - > > > - if (r->headers_in.accept_encoding) { > > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, > > > - r->headers_in.accept_encoding->value.len); > > > - } > > > - > > > - if (r->headers_in.accept_language) { > > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, > > > - r->headers_in.accept_language->value.len); > > > - } > > > - > > > - if (r->headers_in.user_agent) { > > > - len += ngx_http_v3_encode_field_lri(NULL, 0, > > > - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, > > > - r->headers_in.user_agent->value.len); > > > - } > > > - > > > - b = ngx_create_temp_buf(r->pool, len); > > > - if (b == NULL) { > > > - return NULL; > > > - } > > > - > > > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); > > > - > > > - b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, > > > - 0, 0, 0); > > > - > > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_METHOD_GET); > > > - > > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_AUTHORITY, > > > - r->headers_in.server.data, > > > - r->headers_in.server.len); > > > - > > > - if (path->len == 1 && path->data[0] == '/') { > > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_PATH_ROOT); > > > - > > > - } else { > > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_PATH_ROOT, > > > - path->data, path->len); > > > - } > > > - > > > - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { > > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); > > > - > > > - } else if (r->schema.len == 4 > > > - && ngx_strncmp(r->schema.data, "http", 4) == 0) > > > - { > > > - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP); > > > - > > > - } else { > > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_SCHEME_HTTP, > > > - r->schema.data, r->schema.len); > > > - } > > > - > > > - if (r->headers_in.accept_encoding) { > > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, > > > - r->headers_in.accept_encoding->value.data, > > > - r->headers_in.accept_encoding->value.len); > > > - } > > > - > > > - if (r->headers_in.accept_language) { > > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, > > > - r->headers_in.accept_language->value.data, > > > - r->headers_in.accept_language->value.len); > > > - } > > > - > > > - if (r->headers_in.user_agent) { > > > - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, > > > - NGX_HTTP_V3_HEADER_USER_AGENT, > > > - r->headers_in.user_agent->value.data, > > > - r->headers_in.user_agent->value.len); > > > - } > > > - > > > - cl = ngx_alloc_chain_link(r->pool); > > > - if (cl == NULL) { > > > - return NULL; > > > - } > > > - > > > - cl->buf = b; > > > - cl->next = NULL; > > > - > > > - n = b->last - b->pos; > > > - > > > - h3c->payload_bytes += n; > > > - > > > - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) > > > - + ngx_http_v3_encode_varlen_int(NULL, n); > > > - > > > - b = ngx_create_temp_buf(r->pool, len); > > > - if (b == NULL) { > > > - return NULL; > > > - } > > > - > > > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, > > > - NGX_HTTP_V3_FRAME_PUSH_PROMISE); > > > - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); > > > - > > > - hl = ngx_alloc_chain_link(r->pool); > > > - if (hl == NULL) { > > > - return NULL; > > > - } > > > - > > > - hl->buf = b; > > > - hl->next = cl; > > > - > > > - return hl; > > > -} > > > - > > > - > > > -static ngx_int_t > > > ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) > > > { > > > u_char *chunk; > > > diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c > > > --- a/src/http/v3/ngx_http_v3_module.c > > > +++ b/src/http/v3/ngx_http_v3_module.c > > > @@ -18,10 +18,6 @@ static char *ngx_http_v3_merge_srv_conf( > > > void *child); > > > static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, > > > void *conf); > > > -static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); > > > -static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, > > > - void *child); > > > -static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); > > > > > > > > > static ngx_command_t ngx_http_v3_commands[] = { > > > @@ -40,13 +36,6 @@ static ngx_command_t ngx_http_v3_comman > > > offsetof(ngx_http_v3_srv_conf_t, enable_hq), > > > NULL }, > > > > > > - { ngx_string("http3_max_concurrent_pushes"), > > > - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > > > - ngx_conf_set_num_slot, > > > - NGX_HTTP_SRV_CONF_OFFSET, > > > - offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), > > > - NULL }, > > > - > > > { ngx_string("http3_max_concurrent_streams"), > > > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > > > ngx_conf_set_num_slot, > > > @@ -54,20 +43,6 @@ static ngx_command_t ngx_http_v3_comman > > > offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), > > > NULL }, > > > > > > - { ngx_string("http3_push"), > > > - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, > > > - ngx_http_v3_push, > > > - NGX_HTTP_LOC_CONF_OFFSET, > > > - 0, > > > - NULL }, > > > - > > > - { ngx_string("http3_push_preload"), > > > - 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_v3_loc_conf_t, push_preload), > > > - NULL }, > > > - > > > { ngx_string("http3_stream_buffer_size"), > > > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, > > > ngx_conf_set_size_slot, > > > @@ -117,8 +92,8 @@ static ngx_http_module_t ngx_http_v3_mo > > > ngx_http_v3_create_srv_conf, /* create server configuration */ > > > ngx_http_v3_merge_srv_conf, /* merge server configuration */ > > > > > > - ngx_http_v3_create_loc_conf, /* create location configuration */ > > > - ngx_http_v3_merge_loc_conf /* merge location configuration */ > > > + NULL, /* create location configuration */ > > > + NULL /* merge location configuration */ > > > }; > > > > > > > > > @@ -224,7 +199,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * > > > h3scf->enable = NGX_CONF_UNSET; > > > h3scf->enable_hq = NGX_CONF_UNSET; > > > h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; > > > - h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; > > > h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; > > > > > > h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; > > > @@ -255,9 +229,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > > > > > > ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); > > > > > > - ngx_conf_merge_uint_value(conf->max_concurrent_pushes, > > > - prev->max_concurrent_pushes, 10); > > > - > > > ngx_conf_merge_uint_value(conf->max_concurrent_streams, > > > prev->max_concurrent_streams, 128); > > > > > > @@ -416,102 +387,3 @@ failed: > > > > > > return NGX_CONF_ERROR; > > > } > > > - > > > - > > > -static void * > > > -ngx_http_v3_create_loc_conf(ngx_conf_t *cf) > > > -{ > > > - ngx_http_v3_loc_conf_t *h3lcf; > > > - > > > - h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); > > > - if (h3lcf == NULL) { > > > - return NULL; > > > - } > > > - > > > - /* > > > - * set by ngx_pcalloc(): > > > - * > > > - * h3lcf->pushes = NULL; > > > - */ > > > - > > > - h3lcf->push_preload = NGX_CONF_UNSET; > > > - h3lcf->push = NGX_CONF_UNSET; > > > - > > > - return h3lcf; > > > -} > > > - > > > - > > > -static char * > > > -ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) > > > -{ > > > - ngx_http_v3_loc_conf_t *prev = parent; > > > - ngx_http_v3_loc_conf_t *conf = child; > > > - > > > - ngx_conf_merge_value(conf->push, prev->push, 1); > > > - > > > - if (conf->push && conf->pushes == NULL) { > > > - conf->pushes = prev->pushes; > > > - } > > > - > > > - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); > > > - > > > - return NGX_CONF_OK; > > > -} > > > - > > > - > > > -static char * > > > -ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) > > > -{ > > > - ngx_http_v3_loc_conf_t *h3lcf = conf; > > > - > > > - ngx_str_t *value; > > > - ngx_http_complex_value_t *cv; > > > - ngx_http_compile_complex_value_t ccv; > > > - > > > - value = cf->args->elts; > > > - > > > - if (ngx_strcmp(value[1].data, "off") == 0) { > > > - > > > - if (h3lcf->pushes) { > > > - return "\"off\" parameter cannot be used with URI"; > > > - } > > > - > > > - if (h3lcf->push == 0) { > > > - return "is duplicate"; > > > - } > > > - > > > - h3lcf->push = 0; > > > - return NGX_CONF_OK; > > > - } > > > - > > > - if (h3lcf->push == 0) { > > > - return "URI cannot be used with \"off\" parameter"; > > > - } > > > - > > > - h3lcf->push = 1; > > > - > > > - if (h3lcf->pushes == NULL) { > > > - h3lcf->pushes = ngx_array_create(cf->pool, 1, > > > - sizeof(ngx_http_complex_value_t)); > > > - if (h3lcf->pushes == NULL) { > > > - return NGX_CONF_ERROR; > > > - } > > > - } > > > - > > > - cv = ngx_array_push(h3lcf->pushes); > > > - if (cv == NULL) { > > > - return NGX_CONF_ERROR; > > > - } > > > - > > > - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); > > > - > > > - ccv.cf = cf; > > > - ccv.value = &value[1]; > > > - ccv.complex_value = cv; > > > - > > > - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { > > > - return NGX_CONF_ERROR; > > > - } > > > - > > > - return NGX_CONF_OK; > > > -} > > > diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c > > > --- a/src/http/v3/ngx_http_v3_uni.c > > > +++ b/src/http/v3/ngx_http_v3_uni.c > > > @@ -16,19 +16,10 @@ typedef struct { > > > } ngx_http_v3_uni_stream_t; > > > > > > > > > -typedef struct { > > > - ngx_queue_t queue; > > > - uint64_t id; > > > - ngx_connection_t *connection; > > > - ngx_uint_t *npushing; > > > -} ngx_http_v3_push_t; > > > - > > > - > > > static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); > > > static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); > > > static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); > > > static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); > > > -static void ngx_http_v3_push_cleanup(void *data); > > > static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, > > > ngx_uint_t type); > > > > > > @@ -316,78 +307,6 @@ ngx_http_v3_uni_dummy_write_handler(ngx_ > > > } > > > > > > > > > -ngx_connection_t * > > > -ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) > > > -{ > > > - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; > > > - size_t n; > > > - ngx_connection_t *sc; > > > - ngx_pool_cleanup_t *cln; > > > - ngx_http_v3_push_t *push; > > > - ngx_http_v3_session_t *h3c; > > > - > > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > - "http3 create push stream id:%uL", push_id); > > > - > > > - sc = ngx_quic_open_stream(c, 0); > > > - if (sc == NULL) { > > > - goto failed; > > > - } > > > - > > > - p = buf; > > > - p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); > > > - p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); > > > - n = p - buf; > > > - > > > - h3c = ngx_http_v3_get_session(c); > > > - h3c->total_bytes += n; > > > - > > > - if (sc->send(sc, buf, n) != (ssize_t) n) { > > > - goto failed; > > > - } > > > - > > > - cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); > > > - if (cln == NULL) { > > > - goto failed; > > > - } > > > - > > > - h3c->npushing++; > > > - > > > - cln->handler = ngx_http_v3_push_cleanup; > > > - > > > - push = cln->data; > > > - push->id = push_id; > > > - push->connection = sc; > > > - push->npushing = &h3c->npushing; > > > - > > > - ngx_queue_insert_tail(&h3c->pushing, &push->queue); > > > - > > > - return sc; > > > - > > > -failed: > > > - > > > - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); > > > - > > > - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, > > > - "failed to create push stream"); > > > - if (sc) { > > > - ngx_http_v3_close_uni_stream(sc); > > > - } > > > - > > > - return NULL; > > > -} > > > - > > > - > > > -static void > > > -ngx_http_v3_push_cleanup(void *data) > > > -{ > > > - ngx_http_v3_push_t *push = data; > > > - > > > - ngx_queue_remove(&push->queue); > > > - (*push->npushing)--; > > > -} > > > - > > > - > > > static ngx_connection_t * > > > ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) > > > { > > > @@ -696,19 +615,9 @@ failed: > > > ngx_int_t > > > ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) > > > { > > > - ngx_http_v3_session_t *h3c; > > > - > > > - h3c = ngx_http_v3_get_session(c); > > > - > > > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > "http3 MAX_PUSH_ID:%uL", max_push_id); > > > > > > - if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { > > > - return NGX_HTTP_V3_ERR_ID_ERROR; > > > - } > > > - > > > - h3c->max_push_id = max_push_id; > > > - > > > return NGX_OK; > > > } > > > > > > @@ -716,14 +625,8 @@ ngx_http_v3_set_max_push_id(ngx_connecti > > > ngx_int_t > > > ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) > > > { > > > - ngx_http_v3_session_t *h3c; > > > - > > > - h3c = ngx_http_v3_get_session(c); > > > - > > > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); > > > > > > - h3c->goaway_push_id = push_id; > > > - > > > return NGX_OK; > > > } > > > > Shouldn't we simply skip MAX_PUSH_ID and GOAWAY frames somewhere > > at ngx_http_v3_parse_control()? > > Yes, we can skip them. > > > > @@ -731,40 +634,9 @@ ngx_http_v3_goaway(ngx_connection_t *c, > > > ngx_int_t > > > ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) > > > { > > > - ngx_queue_t *q; > > > - ngx_http_request_t *r; > > > - ngx_http_v3_push_t *push; > > > - ngx_http_v3_session_t *h3c; > > > - > > > - h3c = ngx_http_v3_get_session(c); > > > - > > > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > > > "http3 CANCEL_PUSH:%uL", push_id); > > > > > > - if (push_id >= h3c->next_push_id) { > > > - return NGX_HTTP_V3_ERR_ID_ERROR; > > > - } > > > - > > > - for (q = ngx_queue_head(&h3c->pushing); > > > - q != ngx_queue_sentinel(&h3c->pushing); > > > - q = ngx_queue_next(q)) > > > - { > > > - push = (ngx_http_v3_push_t *) q; > > > - > > > - if (push->id != push_id) { > > > - continue; > > > - } > > > - > > > - r = push->connection->data; > > > - > > > - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > > > - "http3 cancel push"); > > > - > > > - ngx_http_finalize_request(r, NGX_HTTP_CLOSE); > > > - > > > - break; > > > - } > > > - > > > return NGX_OK; > > > } > > > > > > > And CANCEL_PUSH probably worth an explicit error > > (https://www.rfc-editor.org/rfc/rfc9114.html#name-cancel_push): > > > > : If a server receives a CANCEL_PUSH frame for a push ID that has > > : not yet been mentioned by a PUSH_PROMISE frame, this MUST be > > : treated as a connection error of type H3_ID_ERROR. > > > > Since no pushes are expected to appear on the connection, > > returning NGX_HTTP_V3_ERR_ID_ERROR seems to be correct option. > > > > Similarly to the above, handling this at > > ngx_http_v3_parse_control() might be easier. > > OK. > > > > diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h > > > --- a/src/http/v3/ngx_http_v3_uni.h > > > +++ b/src/http/v3/ngx_http_v3_uni.h > > > @@ -17,8 +17,6 @@ > > > void ngx_http_v3_init_uni_stream(ngx_connection_t *c); > > > ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); > > > > > > -ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, > > > - uint64_t push_id); > > > ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, > > > uint64_t max_push_id); > > > ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); > > > > Otherwise looks good. > > Diff attached. > > -- > Roman Arutyunyan > # HG changeset patch > # User Roman Arutyunyan > # Date 1684470628 -14400 > # Fri May 19 08:30:28 2023 +0400 > # Branch quic > # Node ID 3f8c9f2f31a5d91291139b9049769e47f8de2c0c > # Parent 49a8edf7bf31b78681399cd7e93a8516788607dd > [mq]: v3-no-push-fix > > 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 > @@ -1159,10 +1159,7 @@ ngx_http_v3_parse_control(ngx_connection > sw_first_type, > sw_type, > sw_length, > - sw_cancel_push, > sw_settings, > - sw_max_push_id, > - sw_goaway, > sw_skip > }; > > @@ -1212,6 +1209,10 @@ ngx_http_v3_parse_control(ngx_connection > return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; > } > > + if (st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH) { > + return NGX_HTTP_V3_ERR_ID_ERROR; > + } > + > st->state = sw_length; > break; > > @@ -1233,22 +1234,10 @@ ngx_http_v3_parse_control(ngx_connection > > switch (st->type) { > > - case NGX_HTTP_V3_FRAME_CANCEL_PUSH: > - st->state = sw_cancel_push; > - break; > - > case NGX_HTTP_V3_FRAME_SETTINGS: > st->state = sw_settings; > break; > > - case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: > - st->state = sw_max_push_id; > - break; > - > - case NGX_HTTP_V3_FRAME_GOAWAY: > - st->state = sw_goaway; > - break; > - > default: > ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, > "http3 parse skip unknown frame"); > @@ -1257,30 +1246,6 @@ ngx_http_v3_parse_control(ngx_connection > > break; > > - case sw_cancel_push: > - > - ngx_http_v3_parse_start_local(b, &loc, st->length); > - > - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); > - > - ngx_http_v3_parse_end_local(b, &loc, &st->length); > - > - if (st->length == 0 && rc == NGX_AGAIN) { > - return NGX_HTTP_V3_ERR_FRAME_ERROR; > - } > - > - if (rc != NGX_DONE) { > - return rc; > - } > - > - rc = ngx_http_v3_cancel_push(c, st->vlint.value); > - if (rc != NGX_OK) { > - return rc; > - } > - > - st->state = sw_type; > - break; > - > case sw_settings: > > ngx_http_v3_parse_start_local(b, &loc, st->length); > @@ -1303,54 +1268,6 @@ ngx_http_v3_parse_control(ngx_connection > > break; > > - case sw_max_push_id: > - > - ngx_http_v3_parse_start_local(b, &loc, st->length); > - > - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); > - > - ngx_http_v3_parse_end_local(b, &loc, &st->length); > - > - if (st->length == 0 && rc == NGX_AGAIN) { > - return NGX_HTTP_V3_ERR_FRAME_ERROR; > - } > - > - if (rc != NGX_DONE) { > - return rc; > - } > - > - rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); > - if (rc != NGX_OK) { > - return rc; > - } > - > - st->state = sw_type; > - break; > - > - case sw_goaway: > - > - ngx_http_v3_parse_start_local(b, &loc, st->length); > - > - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); > - > - ngx_http_v3_parse_end_local(b, &loc, &st->length); > - > - if (st->length == 0 && rc == NGX_AGAIN) { > - return NGX_HTTP_V3_ERR_FRAME_ERROR; > - } > - > - if (rc != NGX_DONE) { > - return rc; > - } > - > - rc = ngx_http_v3_goaway(c, st->vlint.value); > - if (rc != NGX_OK) { > - return rc; > - } > - > - st->state = sw_type; > - break; > - > case sw_skip: > > rc = ngx_http_v3_parse_skip(b, &st->length); > diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c > --- a/src/http/v3/ngx_http_v3_uni.c > +++ b/src/http/v3/ngx_http_v3_uni.c > @@ -613,35 +613,6 @@ failed: > > > ngx_int_t > -ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) > -{ > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 MAX_PUSH_ID:%uL", max_push_id); > - > - return NGX_OK; > -} > - > - > -ngx_int_t > -ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) > -{ > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); > - > - return NGX_OK; > -} > - > - > -ngx_int_t > -ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) > -{ > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > - "http3 CANCEL_PUSH:%uL", push_id); > - > - return NGX_OK; > -} > - > - > -ngx_int_t > ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) > { > ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, > diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h > --- a/src/http/v3/ngx_http_v3_uni.h > +++ b/src/http/v3/ngx_http_v3_uni.h > @@ -17,10 +17,6 @@ > void ngx_http_v3_init_uni_stream(ngx_connection_t *c); > ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); > > -ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, > - uint64_t max_push_id); > -ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); > -ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); > ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); > > ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); Looks good. -- Maxim Dounin http://mdounin.ru/ From arut at nginx.com Fri May 19 17:33:39 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 19 May 2023 21:33:39 +0400 Subject: QUIC branch merge Message-ID: <20230519173339.o5c7v4sucyrs5qcj@N00W24XTQX> Hi, Following the push of quic branch, here's the merge diff. -- Roman Arutyunyan -------------- next part -------------- diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -5,12 +5,17 @@ if [ $OPENSSL != NONE ]; then + have=NGX_OPENSSL . auto/have + have=NGX_SSL . auto/have + + if [ $USE_OPENSSL_QUIC = YES ]; then + have=NGX_QUIC . auto/have + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + fi + case "$CC" in cl | bcc32) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CFLAGS="$CFLAGS -DNO_SYS_TYPES_H" CORE_INCS="$CORE_INCS $OPENSSL/openssl/include" @@ -33,9 +38,6 @@ if [ $OPENSSL != NONE ]; then ;; *) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include" CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a" @@ -123,6 +125,35 @@ else CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" OPENSSL=YES + + if [ $USE_OPENSSL_QUIC = YES ]; then + + ngx_feature="OpenSSL QUIC support" + ngx_feature_name="NGX_QUIC" + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" + . auto/feature + + if [ $ngx_found = no ]; then + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + + ngx_feature="OpenSSL QUIC compatibility" + ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0, + NULL, NULL, NULL, NULL, NULL)" + . auto/feature + fi + + if [ $ngx_found = no ]; then +cat << END + +$0: error: certain modules require OpenSSL QUIC support. +You can either do not enable the modules, or install the OpenSSL library with +QUIC support into the system, or build the OpenSSL library with QUIC support +statically from the source with nginx by using --with-openssl= option. + +END + exit 1 + fi + fi fi fi diff --git a/auto/make b/auto/make --- a/auto/make +++ b/auto/make @@ -6,9 +6,10 @@ echo "creating $NGX_MAKEFILE" mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ + $NGX_OBJS/src/event/quic \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ - $NGX_OBJS/src/http/modules/perl \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ $NGX_OBJS/src/misc diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then fi - if [ $HTTP_V2 = YES ]; then + if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS" fi @@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -426,6 +439,33 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + USE_OPENSSL_QUIC=YES + HTTP_SSL=YES + + have=NGX_HTTP_V3 . auto/have + have=NGX_HTTP_HEADERS . auto/have + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_encode.h \ + src/http/v3/ngx_http_v3_parse.h \ + src/http/v3/ngx_http_v3_table.h \ + src/http/v3/ngx_http_v3_uni.h" + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_encode.c \ + src/http/v3/ngx_http_v3_parse.c \ + src/http/v3/ngx_http_v3_table.c \ + src/http/v3/ngx_http_v3_uni.c \ + src/http/v3/ngx_http_v3_request.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= @@ -1272,6 +1312,63 @@ if [ $USE_OPENSSL = YES ]; then fi +if [ $USE_OPENSSL_QUIC = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps="src/event/quic/ngx_event_quic.h \ + src/event/quic/ngx_event_quic_transport.h \ + src/event/quic/ngx_event_quic_protection.h \ + src/event/quic/ngx_event_quic_connection.h \ + src/event/quic/ngx_event_quic_frames.h \ + src/event/quic/ngx_event_quic_connid.h \ + src/event/quic/ngx_event_quic_migration.h \ + src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_ssl.h \ + src/event/quic/ngx_event_quic_tokens.h \ + src/event/quic/ngx_event_quic_ack.h \ + src/event/quic/ngx_event_quic_output.h \ + src/event/quic/ngx_event_quic_socket.h \ + src/event/quic/ngx_event_quic_openssl_compat.h" + ngx_module_srcs="src/event/quic/ngx_event_quic.c \ + src/event/quic/ngx_event_quic_udp.c \ + src/event/quic/ngx_event_quic_transport.c \ + src/event/quic/ngx_event_quic_protection.c \ + src/event/quic/ngx_event_quic_frames.c \ + src/event/quic/ngx_event_quic_connid.c \ + src/event/quic/ngx_event_quic_migration.c \ + src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_ssl.c \ + src/event/quic/ngx_event_quic_tokens.c \ + src/event/quic/ngx_event_quic_ack.c \ + src/event/quic/ngx_event_quic_output.c \ + src/event/quic/ngx_event_quic_socket.c \ + src/event/quic/ngx_event_quic_openssl_compat.c" + + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_bpf_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \ + src/event/quic/ngx_event_quic_bpf_code.c" + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + have=NGX_QUIC_BPF . auto/have + fi +fi + + if [ $USE_PCRE = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_regex_module diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -45,6 +45,8 @@ USE_THREADS=NO NGX_FILE_AIO=NO +QUIC_BPF=NO + HTTP=YES NGX_HTTP_LOG_PATH= @@ -59,6 +61,7 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -149,6 +152,7 @@ PCRE_JIT=NO PCRE2=YES USE_OPENSSL=NO +USE_OPENSSL_QUIC=NO OPENSSL=NONE USE_ZLIB=NO @@ -166,6 +170,8 @@ USE_GEOIP=NO NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO +SO_COOKIE_FOUND=NO + NGX_LIBATOMIC=NO NGX_CPU_CACHE_LINE= @@ -211,6 +217,8 @@ do --with-file-aio) NGX_FILE_AIO=YES ;; + --without-quic_bpf_module) QUIC_BPF=NONE ;; + --with-ipv6) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-ipv6\" option is deprecated" @@ -228,6 +236,7 @@ do --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -443,8 +452,11 @@ cat << END --with-file-aio enable file AIO support + --without-quic_bpf_module disable ngx_quic_bpf_module + --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff --git a/auto/os/linux b/auto/os/linux --- a/auto/os/linux +++ b/auto/os/linux @@ -232,6 +232,50 @@ ngx_feature_test="struct crypt_data cd; ngx_include="sys/vfs.h"; . auto/include +# BPF sockhash + +ngx_feature="BPF sockhash" +ngx_feature_name="NGX_HAVE_BPF" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="union bpf_attr attr = { 0 }; + + attr.map_flags = 0; + attr.map_type = BPF_MAP_TYPE_SOCKHASH; + + syscall(__NR_bpf, 0, &attr, 0);" +. auto/feature + +if [ $ngx_found = yes ]; then + CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" + CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" + + if [ $QUIC_BPF != NONE ]; then + QUIC_BPF=YES + fi +fi + + +ngx_feature="SO_COOKIE" +ngx_feature_name="NGX_HAVE_SO_COOKIE" +ngx_feature_run=no +ngx_feature_incs="#include + $NGX_INCLUDE_INTTYPES_H" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="socklen_t optlen = sizeof(uint64_t); + uint64_t cookie; + getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" +. auto/feature + +if [ $ngx_found = yes ]; then + SO_COOKIE_FOUND=YES +fi + + # UDP segmentation offloading ngx_feature="UDP_SEGMENT" diff --git a/auto/sources b/auto/sources --- a/auto/sources +++ b/auto/sources @@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \ EVENT_MODULES="ngx_events_module ngx_event_core_module" -EVENT_INCS="src/event src/event/modules" +EVENT_INCS="src/event src/event/modules src/event/quic" EVENT_DEPS="src/event/ngx_event.h \ src/event/ngx_event_timer.h \ diff --git a/auto/unix b/auto/unix --- a/auto/unix +++ b/auto/unix @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ . auto/feature +# IP packet fragmentation + +ngx_feature="IP_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IP_PMTUDISC_DO; + setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IPV6_PMTUDISC_DO; + setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IP_DONTFRAG" +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_DONTFRAG" +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" +. auto/feature + + ngx_feature="TCP_DEFER_ACCEPT" ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" ngx_feature_run=no diff --git a/src/core/nginx.c b/src/core/nginx.c --- a/src/core/nginx.c +++ b/src/core/nginx.c @@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].ignore) { + continue; + } p = ngx_sprintf(p, "%ud;", ls[i].fd); } diff --git a/src/core/ngx_bpf.c b/src/core/ngx_bpf.c new file mode 100644 --- /dev/null +++ b/src/core/ngx_bpf.c @@ -0,0 +1,143 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + +#define NGX_BPF_LOGBUF_SIZE (16 * 1024) + + +static ngx_inline int +ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) +{ + return syscall(__NR_bpf, cmd, attr, size); +} + + +void +ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd) +{ + ngx_uint_t i; + ngx_bpf_reloc_t *rl; + + rl = program->relocs; + + for (i = 0; i < program->nrelocs; i++) { + if (ngx_strcmp(rl[i].name, symbol) == 0) { + program->ins[rl[i].offset].src_reg = 1; + program->ins[rl[i].offset].imm = fd; + } + } +} + + +int +ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program) +{ + int fd; + union bpf_attr attr; +#if (NGX_DEBUG) + char buf[NGX_BPF_LOGBUF_SIZE]; +#endif + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.license = (uintptr_t) program->license; + attr.prog_type = program->type; + attr.insns = (uintptr_t) program->ins; + attr.insn_cnt = program->nins; + +#if (NGX_DEBUG) + /* for verifier errors */ + attr.log_buf = (uintptr_t) buf; + attr.log_size = NGX_BPF_LOGBUF_SIZE; + attr.log_level = 1; +#endif + + fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to load BPF program"); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "bpf verifier: %s", buf); + + return -1; + } + + return fd; +} + + +int +ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags) +{ + int fd; + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_type = type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + attr.map_flags = map_flags; + + fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to create BPF map"); + return NGX_ERROR; + } + + return fd; +} + + +int +ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; + attr.flags = flags; + + return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_delete(int fd, const void *key) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + + return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_lookup(int fd, const void *key, void *value) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; + + return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} diff --git a/src/core/ngx_bpf.h b/src/core/ngx_bpf.h new file mode 100644 --- /dev/null +++ b/src/core/ngx_bpf.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_BPF_H_INCLUDED_ +#define _NGX_BPF_H_INCLUDED_ + + +#include +#include + +#include + + +typedef struct { + char *name; + int offset; +} ngx_bpf_reloc_t; + +typedef struct { + char *license; + enum bpf_prog_type type; + struct bpf_insn *ins; + size_t nins; + ngx_bpf_reloc_t *relocs; + size_t nrelocs; +} ngx_bpf_program_t; + + +void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, + int fd); +int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program); + +int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags); +int ngx_bpf_map_update(int fd, const void *key, const void *value, + uint64_t flags); +int ngx_bpf_map_delete(int fd, const void *key); +int ngx_bpf_map_lookup(int fd, const void *key, void *value); + +#endif /* _NGX_BPF_H_INCLUDED_ */ diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1014,6 +1014,78 @@ ngx_configure_listening_sockets(ngx_cycl } #endif + +#if (NGX_HAVE_IP_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6) + +#if (NGX_HAVE_IPV6_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#endif } return; @@ -1037,6 +1109,12 @@ ngx_close_listening_sockets(ngx_cycle_t ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { +#if (NGX_QUIC) + if (ls[i].quic) { + continue; + } +#endif + c = ls[i].connection; if (c) { diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -73,6 +73,7 @@ struct ngx_listening_s { unsigned reuseport:1; unsigned add_reuseport:1; unsigned keepalive:2; + unsigned quic:1; unsigned deferred_accept:1; unsigned delete_deferred:1; @@ -147,6 +148,10 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; +#if (NGX_QUIC || NGX_COMPAT) + ngx_quic_stream_t *quic; +#endif + #if (NGX_SSL || NGX_COMPAT) ngx_ssl_connection_t *ssl; #endif diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx typedef struct ngx_thread_task_s ngx_thread_task_t; typedef struct ngx_ssl_s ngx_ssl_t; typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; +typedef struct ngx_quic_stream_s ngx_quic_stream_t; typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; typedef struct ngx_udp_connection_s ngx_udp_connection_t; @@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt #include #if (NGX_OPENSSL) #include +#if (NGX_QUIC) +#include +#endif #endif #include #include @@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt #include #include #include +#if (NGX_HAVE_BPF) +#include +#endif #define LF (u_char) '\n' diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_ ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { +#if (NGX_QUIC) + + ngx_connection_t *c; + + c = rev->data; + + if (c->quic) { + return NGX_OK; + } + +#endif + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, { ngx_connection_t *c; + c = wev->data; + +#if (NGX_QUIC) + if (c->quic) { + return NGX_OK; + } +#endif + if (lowat) { - c = wev->data; - if (ngx_send_lowat(c, lowat) == NGX_ERROR) { return NGX_ERROR; } @@ -873,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycl #else - rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept - : ngx_event_recvmsg; + if (c->type == SOCK_STREAM) { + rev->handler = ngx_event_accept; + +#if (NGX_QUIC) + } else if (ls[i].quic) { + rev->handler = ngx_quic_recvmsg; +#endif + } else { + rev->handler = ngx_event_recvmsg; + } #if (NGX_HAVE_REUSEPORT) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ng #ifdef SSL_READ_EARLY_DATA_SUCCESS static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c); #endif -#if (NGX_DEBUG) -static void ngx_ssl_handshake_log(ngx_connection_t *c); -#endif static void ngx_ssl_handshake_handler(ngx_event_t *ev); #ifdef SSL_READ_EARLY_DATA_SUCCESS static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, @@ -2052,7 +2049,7 @@ ngx_ssl_try_early_data(ngx_connection_t #if (NGX_DEBUG) -static void +void ngx_ssl_handshake_log(ngx_connection_t *c) { char buf[129], *s, *d; @@ -3202,6 +3199,13 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_err_t err; ngx_uint_t tries; +#if (NGX_QUIC) + if (c->quic) { + /* QUIC streams inherit SSL object */ + return NGX_OK; + } +#endif + rc = NGX_OK; ngx_ssl_ocsp_cleanup(c); diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -24,6 +24,14 @@ #include #endif #include +#if (NGX_QUIC) +#ifdef OPENSSL_IS_BORINGSSL +#include +#include +#else +#include +#endif +#endif #include #ifndef OPENSSL_NO_OCSP #include @@ -302,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ng ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); +#if (NGX_DEBUG) +void ngx_ssl_handshake_log(ngx_connection_t *c); +#endif ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size); ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size); ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit); diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -12,13 +12,6 @@ #if !(NGX_WIN32) -struct ngx_udp_connection_s { - ngx_rbtree_node_t node; - ngx_connection_t *connection; - ngx_buf_t *buffer; -}; - - static void ngx_close_accepted_udp_connection(ngx_connection_t *c); static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -424,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_n udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, + udp->key.len, udpt->key.len); if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -478,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection ngx_crc32_final(hash); udp->node.key = hash; + udp->key.data = (u_char *) c->sockaddr; + udp->key.len = c->socklen; cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h --- a/src/event/ngx_event_udp.h +++ b/src/event/ngx_event_udp.h @@ -23,6 +23,14 @@ #endif +struct ngx_udp_connection_s { + ngx_rbtree_node_t node; + ngx_connection_t *connection; + ngx_buf_t *buffer; + ngx_str_t key; +}; + + #if (NGX_HAVE_ADDRINFO_CMSG) typedef union { diff --git a/src/event/quic/bpf/bpfgen.sh b/src/event/quic/bpf/bpfgen.sh new file mode 100644 --- /dev/null +++ b/src/event/quic/bpf/bpfgen.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +export LANG=C + +set -e + +if [ $# -lt 1 ]; then + echo "Usage: PROGNAME=foo LICENSE=bar $0 " + exit 1 +fi + + +self=$0 +filename=$1 +funcname=$PROGNAME + +generate_head() +{ + cat << END +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include +#include + +#include "ngx_bpf.h" + + +END +} + +generate_tail() +{ + cat << END + +ngx_bpf_program_t $PROGNAME = { + .relocs = bpf_reloc_prog_$funcname, + .nrelocs = sizeof(bpf_reloc_prog_$funcname) + / sizeof(bpf_reloc_prog_$funcname[0]), + .ins = bpf_insn_prog_$funcname, + .nins = sizeof(bpf_insn_prog_$funcname) + / sizeof(bpf_insn_prog_$funcname[0]), + .license = "$LICENSE", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; + +END +} + +process_relocations() +{ + echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {" + + objdump -r $filename | awk '{ + + if (enabled && $NF > 0) { + off = strtonum(sprintf("0x%s", $1)); + name = $3; + + printf(" { \"%s\", %d },\n", name, off/8); + } + + if ($1 == "OFFSET") { + enabled=1; + } +}' + echo "};" + echo +} + +process_section() +{ + echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {" + echo " /* opcode dst src offset imm */" + + section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname") + + # dd doesn't know hex + length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3)) + offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6)) + + for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8) + do + opcode=0x${ins:0:2} + srcdst=0x${ins:2:2} + + # bytes are dumped in LE order + offset=0x${ins:6:2}${ins:4:2} # short + immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int + + dst="$(($srcdst & 0xF))" + src="$(($srcdst & 0xF0))" + src="$(($src >> 4))" + + opcode=$(printf "0x%x" $opcode) + dst=$(printf "BPF_REG_%d" $dst) + src=$(printf "BPF_REG_%d" $src) + offset=$(printf "%d" $offset) + immedi=$(printf "0x%x" $immedi) + + printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi + done + +cat << END +}; + +END +} + +generate_head +process_relocations +process_section +generate_tail + diff --git a/src/event/quic/bpf/makefile b/src/event/quic/bpf/makefile new file mode 100644 --- /dev/null +++ b/src/event/quic/bpf/makefile @@ -0,0 +1,30 @@ +CFLAGS=-O2 -Wall + +LICENSE=BSD + +PROGNAME=ngx_quic_reuseport_helper +RESULT=ngx_event_quic_bpf_code +DEST=../$(RESULT).c + +all: $(RESULT) + +$(RESULT): $(PROGNAME).o + LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@ + +DEFS=-DPROGNAME=\"$(PROGNAME)\" \ + -DLICENSE_$(LICENSE) \ + -DLICENSE=\"$(LICENSE)\" \ + +$(PROGNAME).o: $(PROGNAME).c + clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@ + +install: $(RESULT) + cp $(RESULT) $(DEST) + +clean: + @rm -f $(RESULT) *.o + +debug: $(PROGNAME).o + llvm-objdump -S -no-show-raw-insn $< + +.DELETE_ON_ERROR: diff --git a/src/event/quic/bpf/ngx_quic_reuseport_helper.c b/src/event/quic/bpf/ngx_quic_reuseport_helper.c new file mode 100644 --- /dev/null +++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +/* + * the bpf_helpers.h is not included into linux-headers, only available + * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf. + */ +#include + + +#if !defined(SEC) +#define SEC(NAME) __attribute__((section(NAME), used)) +#endif + + +#if defined(LICENSE_GPL) + +/* + * To see debug: + * + * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable + * cat /sys/kernel/debug/tracing/trace_pipe + * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable + */ + +#define debugmsg(fmt, ...) \ +do { \ + char __buf[] = fmt; \ + bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \ +} while (0) + +#else + +#define debugmsg(fmt, ...) + +#endif + +char _license[] SEC("license") = LICENSE; + +/*****************************************************************************/ + +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_SERVER_CID_LEN 20 + + +#define advance_data(nbytes) \ + offset += nbytes; \ + if (start + offset > end) { \ + debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \ + goto failed; \ + } \ + data = start + offset - 1; + + +#define ngx_quic_parse_uint64(p) \ + (((__u64)(p)[0] << 56) | \ + ((__u64)(p)[1] << 48) | \ + ((__u64)(p)[2] << 40) | \ + ((__u64)(p)[3] << 32) | \ + ((__u64)(p)[4] << 24) | \ + ((__u64)(p)[5] << 16) | \ + ((__u64)(p)[6] << 8) | \ + ((__u64)(p)[7])) + +/* + * actual map object is created by the "bpf" system call, + * all pointers to this variable are replaced by the bpf loader + */ +struct bpf_map_def SEC("maps") ngx_quic_sockmap; + + +SEC(PROGNAME) +int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) +{ + int rc; + __u64 key; + size_t len, offset; + unsigned char *start, *end, *data, *dcid; + + start = ctx->data; + end = (unsigned char *) ctx->data_end; + offset = 0; + + advance_data(sizeof(struct udphdr)); /* data at UDP header */ + advance_data(1); /* data at QUIC flags */ + + if (data[0] & NGX_QUIC_PKT_LONG) { + + advance_data(4); /* data at QUIC version */ + advance_data(1); /* data at DCID len */ + + len = data[0]; /* read DCID length */ + + if (len < 8) { + /* it's useless to search for key in such short DCID */ + return SK_PASS; + } + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + } + + dcid = &data[1]; + advance_data(len); /* we expect the packet to have full DCID */ + + /* make verifier happy */ + if (dcid + sizeof(__u64) > end) { + goto failed; + } + + key = ngx_quic_parse_uint64(dcid); + + rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0); + + switch (rc) { + case 0: + debugmsg("nginx quic socket selected by key 0x%llx", key); + return SK_PASS; + + /* kernel returns positive error numbers, errno.h defines positive */ + case -ENOENT: + debugmsg("nginx quic default route for key 0x%llx", key); + /* let the default reuseport logic decide which socket to choose */ + return SK_PASS; + + default: + debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx", + rc, key); + goto failed; + } + +failed: + /* + * SK_DROP will generate ICMP, but we may want to process "invalid" packet + * in userspace quic to investigate further and finally react properly + * (maybe ignore, maybe send something in response or close connection) + */ + return SK_PASS; +} diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic.c @@ -0,0 +1,1445 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static void ngx_quic_input_handler(ngx_event_t *rev); +static void ngx_quic_close_handler(ngx_event_t *ev); + +static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf); +static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +static void ngx_quic_push_handler(ngx_event_t *ev); + + +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_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 +}; + + +#if (NGX_DEBUG) + +void +ngx_quic_connstate_dbg(ngx_connection_t *c) +{ + u_char *p, *last; + ngx_quic_connection_t *qc; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = p + sizeof(buf); + + qc = ngx_quic_get_connection(c); + + p = ngx_slprintf(p, last, "state:"); + + if (qc) { + + if (qc->error != (ngx_uint_t) -1) { + p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); + p = ngx_slprintf(p, last, " error:%ui", qc->error); + + if (qc->error_reason) { + p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); + } + } + + p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : ""); + p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); + p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); + p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); + + } else { + p = ngx_slprintf(p, last, " early"); + } + + if (c->read->timer_set) { + p = ngx_slprintf(p, last, + qc && qc->send_timer_set ? " send:%M" : " read:%M", + c->read->timer.key - ngx_current_msec); + } + + if (qc) { + + if (qc->push.timer_set) { + p = ngx_slprintf(p, last, " push:%M", + qc->push.timer.key - ngx_current_msec); + } + + if (qc->pto.timer_set) { + p = ngx_slprintf(p, last, " pto:%M", + qc->pto.timer.key - ngx_current_msec); + } + + if (qc->close.timer_set) { + p = ngx_slprintf(p, last, " close:%M", + qc->close.timer.key - ngx_current_msec); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %*s", p - buf, buf); +} + +#endif + + +ngx_int_t +ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) +{ + ngx_str_t scid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + scid.data = qc->path->cid->id; + scid.len = qc->path->cid->len; + + if (scid.len != ctp->initial_scid.len + || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id mismatch"); + return NGX_ERROR; + } + + if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic maximum packet size is invalid"); + return NGX_ERROR; + + } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) { + ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client maximum packet size truncated"); + } + + if (ctp->active_connection_id_limit < 2) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid active_connection_id_limit"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic active_connection_id_limit is invalid"); + return NGX_ERROR; + } + + if (ctp->ack_delay_exponent > 20) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid ack_delay_exponent"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ack_delay_exponent is invalid"); + return NGX_ERROR; + } + + if (ctp->max_ack_delay >= 16384) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid max_ack_delay"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic max_ack_delay is invalid"); + return NGX_ERROR; + } + + if (ctp->max_idle_timeout > 0 + && ctp->max_idle_timeout < qc->tp.max_idle_timeout) + { + qc->tp.max_idle_timeout = ctp->max_idle_timeout; + } + + qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi; + qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni; + + ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t)); + + return NGX_OK; +} + + +void +ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) +{ + ngx_int_t rc; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); + + rc = ngx_quic_handle_datagram(c, c->buffer, conf); + if (rc != NGX_OK) { + ngx_quic_close_connection(c, rc); + return; + } + + /* quic connection is now created */ + qc = ngx_quic_get_connection(c); + + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + ngx_quic_connstate_dbg(c); + + c->read->handler = ngx_quic_input_handler; + + return; +} + + +static ngx_quic_connection_t * +ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_uint_t i; + ngx_quic_tp_t *ctp; + ngx_quic_connection_t *qc; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NULL; + } + + qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); + if (qc->keys == NULL) { + return NULL; + } + + qc->version = pkt->version; + + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, + ngx_quic_rbtree_insert_stream); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_queue_init(&qc->send_ctx[i].frames); + ngx_queue_init(&qc->send_ctx[i].sending); + ngx_queue_init(&qc->send_ctx[i].sent); + qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; + } + + qc->send_ctx[0].level = ssl_encryption_initial; + qc->send_ctx[1].level = ssl_encryption_handshake; + qc->send_ctx[2].level = ssl_encryption_application; + + ngx_queue_init(&qc->free_frames); + + qc->avg_rtt = NGX_QUIC_INITIAL_RTT; + qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; + qc->min_rtt = NGX_TIMER_INFINITE; + qc->first_rtt = NGX_TIMER_INFINITE; + + /* + * qc->latest_rtt = 0 + */ + + qc->pto.log = c->log; + qc->pto.data = c; + qc->pto.handler = ngx_quic_pto_handler; + + qc->push.log = c->log; + qc->push.data = c; + qc->push.handler = ngx_quic_push_handler; + + qc->close.log = c->log; + qc->close.data = c; + qc->close.handler = ngx_quic_close_handler; + + qc->path_validation.log = c->log; + qc->path_validation.data = c; + qc->path_validation.handler = ngx_quic_path_validation_handler; + + qc->conf = conf; + + if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { + return NULL; + } + + ctp = &qc->ctp; + + /* defaults to be used before actual client parameters are received */ + ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); + ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + ctp->active_connection_id_limit = 2; + + ngx_queue_init(&qc->streams.uninitialized); + ngx_queue_init(&qc->streams.free); + + qc->streams.recv_max_data = qc->tp.initial_max_data; + qc->streams.recv_window = qc->streams.recv_max_data; + + qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni; + qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi; + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + + if (pkt->validated && pkt->retried) { + qc->tp.retry_scid.len = pkt->dcid.len; + qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->tp.retry_scid.data == NULL) { + return NULL; + } + } + + if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) + != NGX_OK) + { + return NULL; + } + + qc->validated = pkt->validated; + + if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { + return NULL; + } + + c->idle = 1; + ngx_reusable_connection(c, 1); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection created"); + + return qc; +} + + +static ngx_int_t +ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *tail, ch; + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* A stateless reset uses an entire UDP datagram */ + if (!pkt->first) { + return NGX_DECLINED; + } + + tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (cid->seqnum == 0 || !cid->used) { + /* + * No stateless reset token in initial connection id. + * Don't accept a token from an unused connection id. + */ + continue; + } + + /* constant time comparison */ + + for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { + ch |= tail[i] ^ cid->sr_token[i]; + } + + if (ch == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static void +ngx_quic_input_handler(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + + c = rev->data; + qc = ngx_quic_get_connection(c); + + c->log->action = "handling quic input"; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "quic client timed out"); + ngx_quic_close_connection(c, NGX_DONE); + return; + } + + if (c->close) { + c->close = 0; + + if (!ngx_exiting) { + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (!qc->closing && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + + return; + } + + b = c->udp->buffer; + if (b == NULL) { + return; + } + + rc = ngx_quic_handle_datagram(c, b, NULL); + + if (rc == NGX_ERROR) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (rc == NGX_DONE) { + return; + } + + /* rc == NGX_OK */ + + qc->send_timer_set = 0; + ngx_add_timer(rev, qc->tp.max_idle_timeout); + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) +{ + ngx_uint_t i; + ngx_pool_t *pool; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rejected rc:%i, cleanup connection", rc); + goto quic_done; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close %s rc:%i", + qc->closing ? "resumed": "initiated", rc); + + if (!qc->closing) { + + /* drop packets from retransmit queues, no ack is expected */ + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); + } + + if (rc == NGX_DONE) { + + /* + * RFC 9000, 10.1. Idle Timeout + * + * If a max_idle_timeout is specified by either endpoint in its + * transport parameters (Section 18.2), the connection is silently + * closed and its state is discarded when it remains idle + */ + + /* this case also handles some errors from ngx_quic_run() */ + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close silent drain:%d timedout:%d", + qc->draining, c->read->timedout); + } else { + + /* + * RFC 9000, 10.2. Immediate Close + * + * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) + * to terminate the connection immediately. + */ + + qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) + : ssl_encryption_initial; + + if (qc->error == (ngx_uint_t) -1) { + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + qc->error_app = 0; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close immediate term:%d drain:%d " + "%serror:%ui \"%s\"", + rc == NGX_ERROR ? 1 : 0, qc->draining, + qc->error_app ? "app " : "", qc->error, + qc->error_reason ? qc->error_reason : ""); + + if (rc == NGX_OK) { + ctx = ngx_quic_get_send_ctx(qc, qc->error_level); + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); + } + + (void) ngx_quic_send_cc(c); + + if (qc->error_level == ssl_encryption_handshake) { + /* for clients that might not have handshake keys */ + qc->error_level = ssl_encryption_initial; + (void) ngx_quic_send_cc(c); + } + } + + qc->closing = 1; + } + + if (rc == NGX_ERROR && qc->close.timer_set) { + /* do not wait for timer in case of fatal error */ + ngx_del_timer(&qc->close); + } + + if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { + return; + } + + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } + + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); + } + + if (qc->close.timer_set) { + return; + } + + if (qc->close.posted) { + ngx_delete_posted_event(&qc->close); + } + + ngx_quic_close_sockets(c); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed"); + + /* may be tested from SSL callback during SSL shutdown */ + c->udp = NULL; + +quic_done: + + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->closing) { + return; + } + + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + qc->error_ftype = 0; + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +void +ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qc->shutdown = 1; + qc->shutdown_code = err; + qc->shutdown_reason = reason; + + ngx_quic_shutdown_quic(c); +} + + +static void +ngx_quic_close_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); + + c = ev->data; + + ngx_quic_close_connection(c, NGX_OK); +} + + +static ngx_int_t +ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf) +{ + size_t size; + u_char *p, *start; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_path_t *path; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + + good = 0; + path = NULL; + + size = b->last - b->pos; + + p = start = b->pos; + + while (p < b->last) { + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + pkt.log = c->log; + pkt.first = (p == start) ? 1 : 0; + pkt.path = path; + pkt.flags = p[0]; + pkt.raw->pos++; + + rc = ngx_quic_handle_packet(c, conf, &pkt); + +#if (NGX_DEBUG) + if (pkt.parsed) { + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done rc:%i level:%s" + " decr:%d pn:%L perr:%ui", + rc, ngx_quic_level_name(pkt.level), + pkt.decrypted, pkt.pn, pkt.error); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done rc:%i parse failed", rc); + } +#endif + + if (rc == NGX_ERROR || rc == NGX_DONE) { + return rc; + } + + if (rc == NGX_OK) { + good = 1; + } + + path = pkt.path; /* preserve packet path from 1st packet */ + + /* NGX_OK || NGX_DECLINED */ + + /* + * we get NGX_DECLINED when there are no keys [yet] available + * to decrypt packet. + * Instead of queueing it, we ignore it and rely on the sender's + * retransmission: + * + * RFC 9000, 12.2. Coalescing Packets + * + * For example, if decryption fails (because the keys are + * not available or for any other reason), the receiver MAY either + * discard or buffer the packet for later processing and MUST + * attempt to process the remaining packets. + * + * We also skip packets that don't match connection state + * or cannot be parsed properly. + */ + + /* b->pos is at header end, adjust by actual packet length */ + b->pos = pkt.data + pkt.len; + + p = b->pos; + } + + if (!good) { + return NGX_DONE; + } + + qc = ngx_quic_get_connection(c); + + if (qc) { + qc->received += size; + + if ((uint64_t) (c->sent + qc->received) / 8 > + (qc->streams.sent + qc->streams.recv_last) + 1048576) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "QUIC flood detected"; + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + c->log->action = "parsing quic packet"; + + rc = ngx_quic_parse_packet(pkt); + + if (rc == NGX_ERROR) { + return NGX_DECLINED; + } + + pkt->parsed = 1; + + c->log->action = "handling quic packet"; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx dcid len:%uz %xV", + pkt->dcid.len, &pkt->dcid); + +#if (NGX_DEBUG) + if (pkt->level != ssl_encryption_application) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx scid len:%uz %xV", + pkt->scid.len, &pkt->scid); + } + + if (pkt->level == ssl_encryption_initial) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic address validation token len:%uz %xV", + pkt->token.len, &pkt->token); + } +#endif + + qc = ngx_quic_get_connection(c); + + if (qc) { + + if (rc == NGX_ABORT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->level != ssl_encryption_application) { + + if (pkt->version != qc->version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic version mismatch: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->first) { + qsock = ngx_quic_get_socket(c); + + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, + qc->path->sockaddr, qc->path->socklen, 1) + != NGX_OK) + { + /* packet comes from unknown path, possibly migration */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too early migration attempt"); + return NGX_DONE; + } + } + + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } + + } + + rc = ngx_quic_handle_payload(c, pkt); + + if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) { + if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stateless reset packet detected"); + + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); + + return NGX_OK; + } + } + + return rc; + } + + /* packet does not belong to a connection */ + + if (rc == NGX_ABORT) { + return ngx_quic_negotiate_version(c, pkt); + } + + if (pkt->level == ssl_encryption_application) { + return ngx_quic_send_stateless_reset(c, conf, pkt); + } + + if (pkt->level != ssl_encryption_initial) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic expected initial, got handshake"); + return NGX_ERROR; + } + + c->log->action = "handling initial packet"; + + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* RFC 9000, 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial" + " packet: len:%i", pkt->dcid.len); + return NGX_ERROR; + } + + /* process retry and initialize connection IDs */ + + if (pkt->token.len) { + + rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); + + if (rc == NGX_ERROR) { + /* internal error */ + return NGX_ERROR; + + } else if (rc == NGX_ABORT) { + /* token cannot be decrypted */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "cannot decrypt token"); + } else if (rc == NGX_DECLINED) { + /* token is invalid */ + + if (pkt->retried) { + /* invalid address validation token */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "invalid address validation token"); + } else if (conf->retry) { + /* invalid NEW_TOKEN */ + return ngx_quic_send_retry(c, conf, pkt); + } + } + + /* NGX_OK */ + + } else if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + + } else { + pkt->odcid = pkt->dcid; + } + + if (ngx_terminate || ngx_exiting) { + if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + } + + return NGX_ERROR; + } + + c->log->action = "creating quic connection"; + + qc = ngx_quic_new_connection(c, conf, pkt); + if (qc == NULL) { + return NGX_ERROR; + } + + return ngx_quic_handle_payload(c, pkt); +} + + +static ngx_int_t +ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + + qc->error = (ngx_uint_t) -1; + qc->error_reason = 0; + + c->log->action = "decrypting packet"; + + if (!ngx_quic_keys_available(qc->keys, pkt->level)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } + +#if !defined (OPENSSL_IS_BORINGSSL) + /* OpenSSL provides read keys for an application level before it's ready */ + + if (pkt->level == ssl_encryption_application + && SSL_quic_read_level(c->ssl->connection) + < ssl_encryption_application) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys ready, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } +#endif + + pkt->keys = qc->keys; + pkt->key_phase = qc->key_phase; + pkt->plaintext = buf; + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); + if (rc != NGX_OK) { + qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; + return rc; + } + + pkt->decrypted = 1; + + c->log->action = "handling decrypted packet"; + + if (pkt->path == NULL) { + rc = ngx_quic_set_path(c, pkt); + if (rc != NGX_OK) { + return rc; + } + } + + if (c->ssl == NULL) { + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (pkt->level == ssl_encryption_handshake) { + /* + * RFC 9001, 4.9.1. Discarding Initial Keys + * + * The successful use of Handshake packets indicates + * that no more Initial packets need to be exchanged + */ + ngx_quic_discard_ctx(c, ssl_encryption_initial); + + if (!qc->path->validated) { + qc->path->validated = 1; + qc->path->limited = 0; + ngx_quic_path_dbg(c, "in handshake", qc->path); + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + + if (qc->closing) { + /* + * RFC 9000, 10.2. Immediate Close + * + * ... delayed or reordered packets are properly discarded. + * + * In the closing state, an endpoint retains only enough information + * to generate a packet containing a CONNECTION_CLOSE frame and to + * identify packets as belonging to the connection. + */ + + qc->error_level = pkt->level; + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "connection is closing, packet discarded"; + qc->error_ftype = 0; + qc->error_app = 0; + + return ngx_quic_send_cc(c); + } + + pkt->received = ngx_current_msec; + + c->log->action = "handling payload"; + + if (pkt->level != ssl_encryption_application) { + return ngx_quic_handle_frames(c, pkt); + } + + if (!pkt->key_update) { + return ngx_quic_handle_frames(c, pkt); + } + + /* switch keys and generate next on Key Phase change */ + + qc->key_phase ^= 1; + ngx_quic_keys_switch(c, qc->keys); + + rc = ngx_quic_handle_frames(c, pkt); + if (rc != NGX_OK) { + return rc; + } + + return ngx_quic_keys_update(c, qc->keys); +} + + +void +ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, level)) { + return; + } + + 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); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_free_frame(c, f); + } + + if (level == ssl_encryption_initial) { + /* close temporary listener with odcid */ + qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN); + if (qsock) { + ngx_quic_close_socket(c, qsock); + } + } + + ctx->send_ack = 0; + + ngx_quic_set_lost_timer(c); +} + + +static ngx_int_t +ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (pkt->scid.len == cid->len + && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) + { + return NGX_OK; + } + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end, *p; + ssize_t len; + ngx_buf_t buf; + ngx_uint_t do_close, nonprobing; + ngx_chain_t chain; + ngx_quic_frame_t frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + p = pkt->payload.data; + end = p + pkt->payload.len; + + do_close = 0; + nonprobing = 0; + + while (p < end) { + + c->log->action = "parsing frames"; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&buf, sizeof(ngx_buf_t)); + buf.temporary = 1; + + chain.buf = &buf; + chain.next = NULL; + frame.data = &chain; + + len = ngx_quic_parse_frame(pkt, p, end, &frame); + + if (len < 0) { + qc->error = pkt->error; + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, &frame, 0); + + c->log->action = "handling frames"; + + p += len; + + switch (frame.type) { + /* probing frames */ + case NGX_QUIC_FT_PADDING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_NEW_CONNECTION_ID: + break; + + /* non-probing frames */ + default: + nonprobing = 1; + break; + } + + switch (frame.type) { + + case NGX_QUIC_FT_ACK: + if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + continue; + + case NGX_QUIC_FT_PADDING: + /* no action required */ + continue; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + do_close = 1; + continue; + } + + /* got there with ack-eliciting packet */ + pkt->need_ack = 1; + + switch (frame.type) { + + case NGX_QUIC_FT_CRYPTO: + + if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { +