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) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_STREAM: + + if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_DATA: + + if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + if (ngx_quic_handle_streams_blocked_frame(c, pkt, + &frame.u.streams_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + if (ngx_quic_handle_data_blocked_frame(c, pkt, + &frame.u.data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, + &frame.u.stream_data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + if (ngx_quic_handle_max_stream_data_frame(c, pkt, + &frame.u.max_stream_data) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + if (ngx_quic_handle_reset_stream_frame(c, pkt, + &frame.u.reset_stream) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + if (ngx_quic_handle_stop_sending_frame(c, pkt, + &frame.u.stop_sending) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + if (ngx_quic_handle_path_challenge_frame(c, pkt, + &frame.u.path_challenge) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + if (ngx_quic_handle_retire_connection_id_frame(c, + &frame.u.retire_cid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic missing frame handler"); + return NGX_ERROR; + } + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic trailing garbage in payload:%ui bytes", end - p); + + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + return NGX_ERROR; + } + + if (do_close) { + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); + } + + if (pkt->path != qc->path && nonprobing) { + + /* + * RFC 9000, 9.2. Initiating Connection Migration + * + * An endpoint can migrate a connection to a new local + * address by sending packets containing non-probing frames + * from that address. + */ + if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_push_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler"); + + c = ev->data; + + if (ngx_quic_output(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_shutdown_quic(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + if (c->reusable) { + qc = ngx_quic_get_connection(c); + ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); + } +} diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic.h @@ -0,0 +1,128 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include +#include + + +#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 + +#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 +#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32 +#define NGX_QUIC_SR_KEY_LEN 32 +#define NGX_QUIC_AV_KEY_LEN 32 + +#define NGX_QUIC_SR_TOKEN_LEN 16 + +#define NGX_QUIC_MIN_INITIAL_SIZE 1200 + +#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 +#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 + + +typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); +typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); + + +typedef enum { + NGX_QUIC_STREAM_SEND_READY = 0, + NGX_QUIC_STREAM_SEND_SEND, + NGX_QUIC_STREAM_SEND_DATA_SENT, + NGX_QUIC_STREAM_SEND_DATA_RECVD, + NGX_QUIC_STREAM_SEND_RESET_SENT, + NGX_QUIC_STREAM_SEND_RESET_RECVD +} ngx_quic_stream_send_state_e; + + +typedef enum { + NGX_QUIC_STREAM_RECV_RECV = 0, + NGX_QUIC_STREAM_RECV_SIZE_KNOWN, + NGX_QUIC_STREAM_RECV_DATA_RECVD, + NGX_QUIC_STREAM_RECV_DATA_READ, + NGX_QUIC_STREAM_RECV_RESET_RECVD, + NGX_QUIC_STREAM_RECV_RESET_READ +} ngx_quic_stream_recv_state_e; + + +typedef struct { + uint64_t size; + uint64_t offset; + uint64_t last_offset; + ngx_chain_t *chain; + ngx_chain_t *last_chain; +} ngx_quic_buffer_t; + + +typedef struct { + ngx_ssl_t *ssl; + + ngx_flag_t retry; + ngx_flag_t gso_enabled; + ngx_flag_t disable_active_migration; + ngx_msec_t timeout; + ngx_str_t host_key; + size_t stream_buffer_size; + ngx_uint_t max_concurrent_streams_bidi; + ngx_uint_t max_concurrent_streams_uni; + ngx_uint_t active_connection_id_limit; + ngx_int_t stream_close_code; + ngx_int_t stream_reject_code_uni; + ngx_int_t stream_reject_code_bidi; + + ngx_quic_init_pt init; + ngx_quic_shutdown_pt shutdown; + + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; + u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; +} ngx_quic_conf_t; + + +struct ngx_quic_stream_s { + ngx_rbtree_node_t node; + ngx_queue_t queue; + ngx_connection_t *parent; + ngx_connection_t *connection; + uint64_t id; + uint64_t sent; + uint64_t acked; + uint64_t send_max_data; + uint64_t send_offset; + uint64_t send_final_size; + uint64_t recv_max_data; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t recv_final_size; + ngx_quic_buffer_t send; + ngx_quic_buffer_t recv; + ngx_quic_stream_send_state_e send_state; + ngx_quic_stream_recv_state_e recv_state; + unsigned cancelable:1; + unsigned fin_acked:1; +}; + + +void ngx_quic_recvmsg(ngx_event_t *ev); +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, + const char *reason); +void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason); +ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); +ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how); +void ngx_quic_cancelable_stream(ngx_connection_t *c); +ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, + ngx_str_t *dcid); +ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, + ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ack.c @@ -0,0 +1,1192 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_ACK_GAP 2 + +/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* RFC 9002, 6.1.2. Time Threshold: kGranularity */ +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + +/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */ +#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 + + +/* send time of ACK'ed packets */ +typedef struct { + ngx_msec_t max_pn; + ngx_msec_t oldest; + ngx_msec_t newest; +} ngx_quic_ack_stat_t; + + +static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc); +static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time); +static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, + ngx_quic_ack_stat_t *st); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, + ngx_quic_ack_stat_t *st); +static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c); +static void ngx_quic_persistent_congestion(ngx_connection_t *c); +static void ngx_quic_congestion_lost(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static void ngx_quic_lost_handler(ngx_event_t *ev); + + +/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ +static ngx_inline ngx_msec_t +ngx_quic_lost_threshold(ngx_quic_connection_t *qc) +{ + ngx_msec_t thr; + + thr = ngx_max(qc->latest_rtt, qc->avg_rtt); + thr += thr >> 3; + + return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); +} + + +ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *f) +{ + ssize_t n; + u_char *pos, *end; + uint64_t min, max, gap, range; + ngx_uint_t i; + ngx_quic_ack_stat_t send_time; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_frame_t *ack; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_handle_ack_frame level:%d", pkt->level); + + ack = &f->u.ack; + + /* + * RFC 9000, 19.3.1. ACK Ranges + * + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + */ + + if (ack->first_range > ack->largest) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid first range in ack frame"); + return NGX_ERROR; + } + + min = ack->largest - ack->first_range; + max = ack->largest; + + send_time.oldest = NGX_TIMER_INFINITE; + send_time.newest = NGX_TIMER_INFINITE; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + + /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */ + if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { + ctx->largest_ack = max; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic updated largest received ack:%uL", max); + + /* + * RFC 9002, 5.1. Generating RTT Samples + * + * An endpoint generates an RTT sample on receiving an + * ACK frame that meets the following two conditions: + * + * - the largest acknowledged packet number is newly acknowledged + * - at least one of the newly acknowledged packets was ack-eliciting. + */ + + if (send_time.max_pn != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn); + } + } + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + for (i = 0; i < ack->range_count; i++) { + + n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + pos += n; + + if (gap + 2 > min) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + max = min - gap - 2; + + if (range > max) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + min = max - range; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return ngx_quic_detect_lost(c, &send_time); +} + + +static void +ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time) +{ + ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + latest_rtt = ngx_current_msec - send_time; + qc->latest_rtt = latest_rtt; + + if (qc->min_rtt == NGX_TIMER_INFINITE) { + qc->min_rtt = latest_rtt; + qc->avg_rtt = latest_rtt; + qc->rttvar = latest_rtt / 2; + qc->first_rtt = ngx_current_msec; + + } else { + qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + + ack_delay = (ack->delay << qc->ctp.ack_delay_exponent) / 1000; + + if (c->ssl->handshaked) { + ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); + } + + adjusted_rtt = latest_rtt; + + if (qc->min_rtt + ack_delay < latest_rtt) { + adjusted_rtt -= ack_delay; + } + + qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3); + rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); + qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2); + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rtt sample latest:%M min:%M avg:%M var:%M", + latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); +} + + +static ngx_int_t +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st) +{ + ngx_uint_t found; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + st->max_pn = NGX_TIMER_INFINITE; + found = 0; + + q = ngx_queue_head(&ctx->sent); + + while (q != ngx_queue_sentinel(&ctx->sent)) { + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_next(q); + + if (f->pnum > max) { + break; + } + + if (f->pnum >= min) { + ngx_quic_congestion_ack(c, f); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_RESET_STREAM: + ngx_quic_handle_stream_ack(c, f); + break; + } + + if (f->pnum == max) { + st->max_pn = f->last; + } + + /* save earliest and latest send times of frames ack'ed */ + if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) { + st->oldest = f->last; + } + + if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) { + st->newest = f->last; + } + + ngx_queue_remove(&f->queue); + ngx_quic_free_frame(c, f); + found = 1; + } + } + + if (!found) { + + if (max < ctx->pnum) { + /* duplicate ACK or ACK for non-ack-eliciting frame */ + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ACK for the packet not sent"); + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_ftype = NGX_QUIC_FT_ACK; + qc->error_reason = "unknown packet number"; + + return NGX_ERROR; + } + + if (!qc->push.timer_set) { + ngx_post_event(&qc->push, &ngx_posted_events); + } + + qc->pto_count = 0; + + return NGX_OK; +} + + +void +ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_uint_t blocked; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + + cg->in_flight -= f->plen; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion ack recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + goto done; + } + + if (cg->window < cg->ssthresh) { + cg->window += f->plen; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion slow start win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + } else { + cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion avoidance win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + } + + /* prevent recovery_start from wrapping */ + + timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; + + if ((ngx_msec_int_t) timer < 0) { + cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; + } + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } +} + + +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" + " fr:%uL nranges:%ui", pn, ctx->largest_range, + ctx->first_range, ctx->nranges); + + base = ctx->largest_range; + + if (base == NGX_QUIC_UNSET_PN) { + return; + } + + if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = NGX_QUIC_UNSET_PN; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + +static ngx_int_t +ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) +{ + ngx_uint_t i, nlost; + ngx_msec_t now, wait, thr, oldest, newest; + ngx_queue_t *q; + ngx_quic_frame_t *start; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + thr = ngx_quic_lost_threshold(qc); + + /* send time of lost packets across all send contexts */ + oldest = NGX_TIMER_INFINITE; + newest = NGX_TIMER_INFINITE; + + nlost = 0; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { + continue; + } + + while (!ngx_queue_empty(&ctx->sent)) { + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum > ctx->largest_ack) { + break; + } + + wait = start->last + thr - now; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); + + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { + break; + } + + if (start->last > qc->first_rtt) { + + if (oldest == NGX_TIMER_INFINITE || start->last < oldest) { + oldest = start->last; + } + + if (newest == NGX_TIMER_INFINITE || start->last > newest) { + newest = start->last; + } + + nlost++; + } + + ngx_quic_resend_frames(c, ctx); + } + } + + + /* RFC 9002, 7.6.2. Establishing Persistent Congestion */ + + /* + * Once acknowledged, packets are no longer tracked. Thus no send time + * information is available for such packets. This limits persistent + * congestion algorithm to packets mentioned within ACK ranges of the + * latest ACK frame. + */ + + if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) { + + if (newest - oldest > ngx_quic_pcg_duration(c)) { + ngx_quic_persistent_congestion(c); + } + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_msec_t +ngx_quic_pcg_duration(ngx_connection_t *c) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + duration = qc->avg_rtt; + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration += qc->ctp.max_ack_delay; + duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR; + + return duration; +} + + +static void +ngx_quic_persistent_congestion(ngx_connection_t *c) +{ + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->recovery_start = ngx_current_msec; + cg->window = qc->tp.max_udp_payload_size * 2; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic persistent congestion win:%uz", cg->window); +} + + +void +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic resend packet pnum:%uL", start->pnum); + + ngx_quic_congestion_lost(c, start); + + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum != start->pnum) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + if (ctx->level == ssl_encryption_application) { + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + f->u.max_streams.limit = f->u.max_streams.bidi + ? qc->streams.client_max_streams_bidi + : qc->streams.client_max_streams_uni; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + qs = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (qs == NULL) { + ngx_quic_free_frame(c, f); + break; + } + + f->u.max_stream_data.limit = qs->recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_STREAM: + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + + if (qs) { + if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + ngx_quic_free_frame(c, f); + break; + } + } + + /* fall through */ + + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +static void +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_uint_t blocked; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + + cg->in_flight -= f->plen; + f->plen = 0; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + goto done; + } + + cg->recovery_start = ngx_current_msec; + cg->window /= 2; + + if (cg->window < qc->tp.max_udp_payload_size * 2) { + cg->window = qc->tp.max_udp_payload_size * 2; + } + + cg->ssthresh = cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } +} + + +void +ngx_quic_set_lost_timer(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t lost, pto, w; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + lost = -1; + pto = -1; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); + + if (f->pnum <= ctx->largest_ack) { + if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { + w = 0; + } + + if (lost == -1 || w < lost) { + lost = w; + } + } + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) + - now); + + if (w < 0) { + w = 0; + } + + if (pto == -1 || w < pto) { + pto = w; + } + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (lost != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer lost:%M", lost); + + qc->pto.handler = ngx_quic_lost_handler; + ngx_add_timer(&qc->pto, lost); + return; + } + + if (pto != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer pto:%M", pto); + + qc->pto.handler = ngx_quic_pto_handler; + ngx_add_timer(&qc->pto, pto); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); +} + + +ngx_msec_t +ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + 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); + + if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { + duration += qc->ctp.max_ack_delay; + } + + return duration; +} + + +static +void ngx_quic_lost_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); + + c = ev->data; + + if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_pto_handler(ngx_event_t *ev) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q, *next; + ngx_connection_t *c; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); + + c = ev->data; + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum <= ctx->largest_ack + && ctx->largest_ack != NGX_QUIC_UNSET_PN) + { + continue; + } + + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) + - now) > 0) + { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto %s pto_count:%ui", + ngx_quic_level_name(ctx->level), qc->pto_count); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->type == NGX_QUIC_FT_PING) { + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + q = next; + } + + for (q = ngx_queue_head(&ctx->sent); + q != ngx_queue_sentinel(&ctx->sent); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->type == NGX_QUIC_FT_PING) { + ngx_quic_congestion_lost(c, f); + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + q = next; + } + + /* enforce 2 udp datagrams */ + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->flush = 1; + + ngx_quic_queue_frame(qc, f); + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, f); + } + + qc->pto_count++; + + ngx_quic_connstate_dbg(c); +} + + +ngx_int_t +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + ngx_quic_connection_t *qc; + + c->log->action = "preparing ack"; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" + " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, + ctx->first_range, ctx->nranges); + + prev_pending = ctx->pending_ack; + + if (pkt->need_ack) { + + ngx_post_event(&qc->push, &ngx_posted_events); + + if (ctx->send_ack == 0) { + ctx->ack_delay_start = ngx_current_msec; + } + + ctx->send_ack++; + + if (ctx->pending_ack == NGX_QUIC_UNSET_PN + || ctx->pending_ack < pkt->pn) + { + ctx->pending_ack = pkt->pn; + } + } + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == NGX_QUIC_UNSET_PN) { + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + + if (pn - base == 1) { + ctx->first_range++; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + return NGX_OK; + + } else { + /* new gap in front of current largest */ + + /* no place for new range, send current range as is */ + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + /* packet is out of order, force send */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + /* packet is out of order */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + gap = ge - pn - 1; + range = 0; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + r->gap = pn - gs - 1; + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + + if (pkt->need_ack) { + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + return NGX_OK; + } + + gap = smallest - 2 - pn; + range = 0; + +insert: + + if (ctx->nranges < NGX_QUIC_MAX_RANGES) { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t delay; + ngx_quic_connection_t *qc; + + if (!ctx->send_ack) { + return NGX_OK; + } + + if (ctx->level == ssl_encryption_application) { + + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); + + if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } + + return NGX_OK; + } + } + + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + ctx->send_ack = 0; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ack.h b/src/event/quic/ngx_event_quic_ack.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ack.h @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_ +#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *f); + +void ngx_quic_congestion_ack(ngx_connection_t *c, + ngx_quic_frame_t *frame); +void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +void ngx_quic_set_lost_timer(ngx_connection_t *c); +void ngx_quic_pto_handler(ngx_event_t *ev); +ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); + +ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); + +#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_bpf.c b/src/event/quic/ngx_event_quic_bpf.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_bpf.c @@ -0,0 +1,657 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS" +#define NGX_QUIC_BPF_VARSEP ';' +#define NGX_QUIC_BPF_ADDRSEP '#' + + +#define ngx_quic_bpf_get_conf(cycle) \ + (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module) + +#define ngx_quic_bpf_get_old_conf(cycle) \ + cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \ + : NULL + +#define ngx_core_get_conf(cycle) \ + (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module) + + +typedef struct { + ngx_queue_t queue; + int map_fd; + + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_uint_t unused; /* unsigned unused:1; */ +} ngx_quic_sock_group_t; + + +typedef struct { + ngx_flag_t enabled; + ngx_uint_t map_size; + ngx_queue_t groups; /* of ngx_quic_sock_group_t */ +} ngx_quic_bpf_conf_t; + + +static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle); + +static void ngx_quic_bpf_cleanup(void *data); +static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd, + const char *name); + +static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, + struct sockaddr *sa, socklen_t socklen); +static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log); + +static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle); + +extern ngx_bpf_program_t ngx_quic_reuseport_helper; + + +static ngx_command_t ngx_quic_bpf_commands[] = { + + { ngx_string("quic_bpf"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_quic_bpf_conf_t, enabled), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_bpf_module_ctx = { + ngx_string("quic_bpf"), + ngx_quic_bpf_create_conf, + NULL +}; + + +ngx_module_t ngx_quic_bpf_module = { + NGX_MODULE_V1, + &ngx_quic_bpf_module_ctx, /* module context */ + ngx_quic_bpf_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + ngx_quic_bpf_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_quic_bpf_create_conf(ngx_cycle_t *cycle) +{ + ngx_quic_bpf_conf_t *bcf; + + bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t)); + if (bcf == NULL) { + return NULL; + } + + bcf->enabled = NGX_CONF_UNSET; + bcf->map_size = NGX_CONF_UNSET_UINT; + + ngx_queue_init(&bcf->groups); + + return bcf; +} + + +static ngx_int_t +ngx_quic_bpf_module_init(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + ngx_listening_t *ls; + ngx_core_conf_t *ccf; + ngx_pool_cleanup_t *cln; + ngx_quic_bpf_conf_t *bcf; + + if (ngx_test_config) { + /* + * during config test, SO_REUSEPORT socket option is + * not set, thus making further processing meaningless + */ + return NGX_OK; + } + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + ngx_conf_init_value(bcf->enabled, 0); + + bcf->map_size = ccf->worker_processes * 4; + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = bcf; + cln->handler = ngx_quic_bpf_cleanup; + + if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) { + if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) { + goto failed; + } + } + + ls = cycle->listening.elts; + + for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].quic && ls[i].reuseport) { + if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) { + goto failed; + } + } + } + + if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + if (ngx_is_init_cycle(cycle->old_cycle)) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize, check limits"); + + /* refuse to start */ + return NGX_ERROR; + } + + /* + * returning error now will lead to master process exiting immediately + * leaving worker processes orphaned, what is really unexpected. + * Instead, just issue a not about failed initialization and try + * to cleanup a bit. Still program can be already loaded to kernel + * for some reuseport groups, and there is no way to revert, so + * behaviour may be inconsistent. + */ + + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize properly, ignored." + "please check limits and note that nginx state now " + "can be inconsistent and restart may be required"); + + return NGX_OK; +} + + +static void +ngx_quic_bpf_cleanup(void *data) +{ + ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data; + + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map"); + } +} + + +static ngx_inline void +ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name) +{ + if (close(fd) != -1) { + return; + } + + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "quic bpf close %s fd:%d failed", name, fd); +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls) +{ + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, + grp->sockaddr, grp->socklen, 1) + == NGX_OK) + { + return grp; + } + } + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa, + socklen_t socklen) +{ + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NULL; + } + + grp->socklen = socklen; + grp->sockaddr = ngx_palloc(cycle->pool, socklen); + if (grp->sockaddr == NULL) { + return NULL; + } + ngx_memcpy(grp->sockaddr, sa, socklen); + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + return grp; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + int progfd, failed, flags, rc; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + if (!bcf->enabled) { + return NULL; + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH, + sizeof(uint64_t), sizeof(uint64_t), + bcf->map_size, 0); + if (grp->map_fd == -1) { + goto failed; + } + + flags = fcntl(grp->map_fd, F_GETFD); + if (flags == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf getfd failed"); + goto failed; + } + + /* need to inherit map during binary upgrade after exec */ + flags &= ~FD_CLOEXEC; + + rc = fcntl(grp->map_fd, F_SETFD, flags); + if (rc == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf setfd failed"); + goto failed; + } + + ngx_bpf_program_link(&ngx_quic_reuseport_helper, + "ngx_quic_sockmap", grp->map_fd); + + progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper); + if (progfd < 0) { + goto failed; + } + + failed = 0; + + if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, + &progfd, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed"); + failed = 1; + } + + ngx_quic_bpf_close(cycle->log, progfd, "program"); + + if (failed) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap created fd:%d", grp->map_fd); + return grp; + +failed: + + if (grp->map_fd != -1) { + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + } + + ngx_queue_remove(&grp->queue); + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + ngx_quic_bpf_conf_t *bcf, *old_bcf; + ngx_quic_sock_group_t *grp, *ogrp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_find_group(bcf, ls); + if (grp) { + return grp; + } + + old_bcf = ngx_quic_bpf_get_old_conf(cycle); + + if (old_bcf == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + ogrp = ngx_quic_bpf_find_group(old_bcf, ls); + if (ogrp == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = dup(ogrp->map_fd); + if (grp->map_fd == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to duplicate bpf map descriptor"); + + ngx_queue_remove(&grp->queue); + + return NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd duplicated old:%d new:%d", + ogrp->map_fd, grp->map_fd); + + return grp; +} + + +static ngx_int_t +ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + uint64_t cookie; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_get_group(cycle, ls); + + if (grp == NULL) { + if (!bcf->enabled) { + return NGX_OK; + } + + return NGX_ERROR; + } + + grp->unused = 0; + + cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log); + if (cookie == (uint64_t) NGX_ERROR) { + return NGX_ERROR; + } + + /* map[cookie] = socket; for use in kernel helper */ + if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to update socket map key=%xL", cookie); + return NGX_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui", + grp->map_fd, ls->fd, cookie, ls->worker); + + /* do not inherit this socket */ + ls->ignore = 1; + + return NGX_OK; +} + + +static uint64_t +ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log) +{ + uint64_t cookie; + socklen_t optlen; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "quic bpf getsockopt(SO_COOKIE) failed"); + + return (ngx_uint_t) NGX_ERROR; + } + + return cookie; +} + + +static ngx_int_t +ngx_quic_bpf_export_maps(ngx_cycle_t *cycle) +{ + u_char *p, *buf; + size_t len; + ngx_str_t *var; + ngx_queue_t *q; + ngx_core_conf_t *ccf; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + len = sizeof(NGX_QUIC_BPF_VARNAME) + 1; + + q = ngx_queue_head(&bcf->groups); + + while (q != ngx_queue_sentinel(&bcf->groups)) { + + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + q = ngx_queue_next(q); + + if (grp->unused) { + /* + * map was inherited, but it is not used in this configuration; + * do not pass such map further and drop the group to prevent + * interference with changes during reload + */ + + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + ngx_queue_remove(&grp->queue); + + continue; + } + + len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1; + } + + len++; + + buf = ngx_palloc(cycle->pool, len); + if (buf == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=", + sizeof(NGX_QUIC_BPF_VARNAME)); + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + p = ngx_sprintf(p, "%ud", grp->map_fd); + + *p++ = NGX_QUIC_BPF_ADDRSEP; + + p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p, + NGX_SOCKADDR_STRLEN, 1); + + *p++ = NGX_QUIC_BPF_VARSEP; + } + + *p = '\0'; + + var = ngx_array_push(&ccf->env); + if (var == NULL) { + return NGX_ERROR; + } + + var->data = buf; + var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_bpf_import_maps(ngx_cycle_t *cycle) +{ + int s; + u_char *inherited, *p, *v; + ngx_uint_t in_fd; + ngx_addr_t tmp; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME); + + if (inherited == NULL) { + return NGX_OK; + } + + bcf = ngx_quic_bpf_get_conf(cycle); + +#if (NGX_SUPPRESS_WARN) + s = -1; +#endif + + in_fd = 1; + + for (p = inherited, v = p; *p; p++) { + + switch (*p) { + + case NGX_QUIC_BPF_ADDRSEP: + + if (!in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 0; + + s = ngx_atoi(v, p - v); + if (s == NGX_ERROR) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited map fd"); + return NGX_ERROR; + } + + v = p + 1; + break; + + case NGX_QUIC_BPF_VARSEP: + + if (in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 1; + + grp = ngx_pcalloc(cycle->pool, + sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NGX_ERROR; + } + + grp->map_fd = s; + + if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v) + != NGX_OK) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited" + " address '%*s'", p - v , v); + + ngx_quic_bpf_close(cycle->log, s, "inherited map"); + + return NGX_ERROR; + } + + grp->sockaddr = tmp.sockaddr; + grp->socklen = tmp.socklen; + + grp->unused = 1; + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap inherited with " + "fd:%d address:%*s", + grp->map_fd, p - v, v); + v = p + 1; + break; + + default: + break; + } + } + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_bpf_code.c b/src/event/quic/ngx_event_quic_bpf_code.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_bpf_code.c @@ -0,0 +1,88 @@ +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include +#include + +#include "ngx_bpf.h" + + +static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = { + { "ngx_quic_sockmap", 55 }, +}; + +static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { + /* opcode dst src offset imm */ + { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 }, + { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 }, + { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 }, + { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 }, + { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 }, + { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 }, + { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 }, + { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 }, + { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 }, + { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, +}; + + +ngx_bpf_program_t ngx_quic_reuseport_helper = { + .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper, + .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]), + .ins = bpf_insn_prog_ngx_quic_reuseport_helper, + .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]), + .license = "BSD", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connection.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ + + +#include +#include +#include + + +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ + +typedef struct ngx_quic_connection_s ngx_quic_connection_t; +typedef struct ngx_quic_server_id_s ngx_quic_server_id_t; +typedef struct ngx_quic_client_id_s ngx_quic_client_id_t; +typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; +typedef struct ngx_quic_socket_s ngx_quic_socket_t; +typedef struct ngx_quic_path_s ngx_quic_path_t; +typedef struct ngx_quic_keys_s ngx_quic_keys_t; + +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ +#define NGX_QUIC_INITIAL_RTT 333 /* ms */ + +#define NGX_QUIC_UNSET_PN (uint64_t) -1 + +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) + +/* 0-RTT and 1-RTT data exist in the same packet number space, + * so we have 3 packet number spaces: + * + * 0 - Initial + * 1 - Handshake + * 2 - 0-RTT and 1-RTT + */ +#define ngx_quic_get_send_ctx(qc, level) \ + ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ + : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ + : &((qc)->send_ctx[2])) + +#define ngx_quic_get_connection(c) \ + (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL) + +#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp)) + + +struct ngx_quic_client_id_s { + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + ngx_uint_t used; /* unsigned used:1; */ +}; + + +struct ngx_quic_server_id_s { + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; +}; + + +struct ngx_quic_path_s { + ngx_queue_t queue; + struct sockaddr *sockaddr; + ngx_sockaddr_t sa; + socklen_t socklen; + ngx_quic_client_id_t *cid; + ngx_msec_t expires; + ngx_uint_t tries; + ngx_uint_t tag; + off_t sent; + off_t received; + u_char challenge1[8]; + u_char challenge2[8]; + uint64_t seqnum; + ngx_str_t addr_text; + u_char text[NGX_SOCKADDR_STRLEN]; + unsigned validated:1; + unsigned validating:1; + unsigned limited:1; +}; + + +struct ngx_quic_socket_s { + ngx_udp_connection_t udp; + ngx_quic_connection_t *quic; + ngx_queue_t queue; + ngx_quic_server_id_t sid; + ngx_sockaddr_t sockaddr; + socklen_t socklen; + ngx_uint_t used; /* unsigned used:1; */ +}; + + +typedef struct { + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + + ngx_queue_t uninitialized; + ngx_queue_t free; + + uint64_t sent; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t recv_max_data; + uint64_t send_offset; + uint64_t send_max_data; + + uint64_t server_max_streams_uni; + uint64_t server_max_streams_bidi; + uint64_t server_streams_uni; + uint64_t server_streams_bidi; + + uint64_t client_max_streams_uni; + uint64_t client_max_streams_bidi; + uint64_t client_streams_uni; + uint64_t client_streams_bidi; + + ngx_uint_t initialized; + /* unsigned initialized:1; */ +} ngx_quic_streams_t; + + +typedef struct { + size_t in_flight; + size_t window; + size_t ssthresh; + ngx_msec_t recovery_start; +} ngx_quic_congestion_t; + + +/* + * RFC 9000, 12.3. Packet Numbers + * + * Conceptually, a packet number space is the context in which a packet + * can be processed and acknowledged. Initial packets can only be sent + * with Initial packet protection keys and acknowledged in packets that + * are also Initial packets. + */ +struct ngx_quic_send_ctx_s { + enum ssl_encryption_level_t level; + + ngx_quic_buffer_t crypto; + uint64_t crypto_sent; + + uint64_t pnum; /* to be sent */ + uint64_t largest_ack; /* received from peer */ + uint64_t largest_pn; /* received from peer */ + + ngx_queue_t frames; /* generated frames */ + ngx_queue_t sending; /* frames assigned to pkt */ + ngx_queue_t sent; /* frames waiting ACK */ + + uint64_t pending_ack; /* non sent ack-eliciting */ + uint64_t largest_range; + uint64_t first_range; + ngx_msec_t largest_received; + ngx_msec_t ack_delay_start; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + ngx_uint_t send_ack; +}; + + +struct ngx_quic_connection_s { + uint32_t version; + + ngx_quic_path_t *path; + + ngx_queue_t sockets; + ngx_queue_t paths; + ngx_queue_t client_ids; + ngx_queue_t free_sockets; + ngx_queue_t free_paths; + ngx_queue_t free_client_ids; + + ngx_uint_t nsockets; + ngx_uint_t nclient_ids; + uint64_t max_retired_seqnum; + uint64_t client_seqnum; + uint64_t server_seqnum; + uint64_t path_seqnum; + + ngx_quic_tp_t tp; + ngx_quic_tp_t ctp; + + ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; + + ngx_quic_keys_t *keys; + + ngx_quic_conf_t *conf; + + ngx_event_t push; + ngx_event_t pto; + ngx_event_t close; + ngx_event_t path_validation; + ngx_msec_t last_cc; + + ngx_msec_t first_rtt; + ngx_msec_t latest_rtt; + ngx_msec_t avg_rtt; + ngx_msec_t min_rtt; + ngx_msec_t rttvar; + + ngx_uint_t pto_count; + + ngx_queue_t free_frames; + ngx_buf_t *free_bufs; + ngx_buf_t *free_shadow_bufs; + + ngx_uint_t nframes; +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_uint_t nbufs; + ngx_uint_t nshadowbufs; +#endif + +#if (NGX_QUIC_OPENSSL_COMPAT) + ngx_quic_compat_t *compat; +#endif + + ngx_quic_streams_t streams; + ngx_quic_congestion_t congestion; + + off_t received; + + ngx_uint_t error; + enum ssl_encryption_level_t error_level; + ngx_uint_t error_ftype; + const char *error_reason; + + ngx_uint_t shutdown_code; + const char *shutdown_reason; + + unsigned error_app:1; + unsigned send_timer_set:1; + unsigned closing:1; + unsigned shutdown:1; + unsigned draining:1; + unsigned key_phase:1; + unsigned validated:1; + unsigned client_tp_done:1; +}; + + +ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, + ngx_quic_tp_t *ctp); +void ngx_quic_discard_ctx(ngx_connection_t *c, + enum ssl_encryption_level_t level); +void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); +void ngx_quic_shutdown_quic(ngx_connection_t *c); + +#if (NGX_DEBUG) +void ngx_quic_connstate_dbg(ngx_connection_t *c); +#else +#define ngx_quic_connstate_dbg(c) +#endif + +#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connid.c @@ -0,0 +1,502 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + +#define NGX_QUIC_MAX_SERVER_IDS 8 + + +#if (NGX_QUIC_BPF) +static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); +#endif +static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); +static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, + ngx_quic_server_id_t *sid); + + +ngx_int_t +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) +{ + if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + +#if (NGX_QUIC_BPF) + if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "quic bpf failed to generate socket key"); + /* ignore error, things still may work */ + } +#endif + + return NGX_OK; +} + + +#if (NGX_QUIC_BPF) + +static ngx_int_t +ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) +{ + int fd; + uint64_t cookie; + socklen_t optlen; + + fd = c->listening->fd; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic getsockopt(SO_COOKIE) failed"); + + return NGX_ERROR; + } + + ngx_quic_dcid_encode_key(id, cookie); + + return NGX_OK; +} + +#endif + + +ngx_int_t +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_new_conn_id_frame_t *f) +{ + ngx_str_t id; + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_client_id_t *cid, *item; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->seqnum < qc->max_retired_seqnum) { + /* + * RFC 9000, 19.15. NEW_CONNECTION_ID Frame + * + * An endpoint that receives a NEW_CONNECTION_ID frame with + * a sequence number smaller than the Retire Prior To field + * of a previously received NEW_CONNECTION_ID frame MUST send + * a corresponding RETIRE_CONNECTION_ID frame that retires + * the newly received connection ID, unless it has already + * done so for that sequence number. + */ + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = f->seqnum; + + ngx_quic_queue_frame(qc, frame); + + goto retire; + } + + cid = NULL; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (item->seqnum == f->seqnum) { + cid = item; + break; + } + } + + if (cid) { + /* + * Transmission errors, timeouts, and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times. + */ + + if (cid->len != f->len + || ngx_strncmp(cid->id, f->cid, f->len) != 0 + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) + { + /* + * ..if a sequence number is used for different connection IDs, + * the endpoint MAY treat that receipt as a connection error + * of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "seqnum refers to different connection id/token"; + return NGX_ERROR; + } + + } else { + + id.data = f->cid; + id.len = f->len; + + if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) { + return NGX_ERROR; + } + } + +retire: + + if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { + /* + * Once a sender indicates a Retire Prior To value, smaller values sent + * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver + * MUST ignore any Retire Prior To fields that do not increase the + * largest received Retire Prior To value. + */ + goto done; + } + + qc->max_retired_seqnum = f->retire; + + q = ngx_queue_head(&qc->client_ids); + + while (q != ngx_queue_sentinel(&qc->client_ids)) { + + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + q = ngx_queue_next(q); + + if (cid->seqnum >= f->retire) { + continue; + } + + if (ngx_quic_retire_client_id(c, cid) != NGX_OK) { + return NGX_ERROR; + } + } + +done: + + if (qc->nclient_ids > qc->tp.active_connection_id_limit) { + /* + * RFC 9000, 5.1.1. Issuing Connection IDs + * + * After processing a NEW_CONNECTION_ID frame and + * adding and retiring active connection IDs, if the number of active + * connection IDs exceeds the value advertised in its + * active_connection_id_limit transport parameter, an endpoint MUST + * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. + */ + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "too many connection ids received"; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_client_id_t *new_cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!cid->used) { + return ngx_quic_free_client_id(c, cid); + } + + /* we are going to retire client id which is in use */ + + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); + + if (path->cid != cid) { + continue; + } + + if (path == qc->path) { + /* this is the active path: update it with new CID */ + new_cid = ngx_quic_next_client_id(c); + if (new_cid == NULL) { + return NGX_ERROR; + } + + qc->path->cid = new_cid; + new_cid->used = 1; + + return ngx_quic_free_client_id(c, cid); + } + + return ngx_quic_free_path(c, path); + } + + return NGX_OK; +} + + +static ngx_quic_client_id_t * +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, + uint64_t seqnum, u_char *token) +{ + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NULL; + } + + cid->seqnum = seqnum; + + cid->len = id->len; + ngx_memcpy(cid->id, id->data, id->len); + + if (token) { + ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN); + } + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + if (seqnum > qc->client_seqnum) { + qc->client_seqnum = seqnum; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic cid seq:%uL received id:%uz:%xV:%*xs", + cid->seqnum, id->len, id, + (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token); + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_next_client_id(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + 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->used) { + return cid; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_retire_cid_frame_t *f) +{ + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->sequence_number >= qc->server_seqnum) { + /* + * RFC 9000, 19.16. + * + * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence + * number greater than any previously sent to the peer MUST be + * treated as a connection error of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire was never issued"; + + return NGX_ERROR; + } + + qsock = ngx_quic_get_socket(c); + + if (qsock->sid.seqnum == f->sequence_number) { + + /* + * RFC 9000, 19.16. + * + * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST + * NOT refer to the Destination Connection ID field of the packet in + * which the frame is contained. The peer MAY treat this as a + * connection error of type PROTOCOL_VIOLATION. + */ + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire refers DCID"; + + return NGX_ERROR; + } + + qsock = ngx_quic_find_socket(c, f->sequence_number); + if (qsock == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%uL is retired", qsock->sid.seqnum); + + ngx_quic_close_socket(c, qsock); + + /* restore socket count up to a limit after deletion */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_create_sockets(ngx_connection_t *c) +{ + ngx_uint_t n; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create sockets has:%ui max:%ui", qc->nsockets, n); + + while (qc->nsockets < n) { + + qsock = ngx_quic_create_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid) +{ + ngx_str_t dcid; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + dcid.len = sid->len; + dcid.data = sid->id; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN); + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = cid->seqnum; + + ngx_quic_queue_frame(qc, frame); + + /* we are no longer going to use this client id */ + + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); + + qc->nclient_ids--; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connid.h @@ -0,0 +1,29 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_retire_cid_frame_t *f); +ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_new_conn_id_frame_t *f); + +ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); +ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); + +ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, + ngx_str_t *id, uint64_t seqnum, u_char *token); +ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); +ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); + +#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_frames.c @@ -0,0 +1,894 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_BUFFER_SIZE 4096 + +#define ngx_quic_buf_refs(b) (b)->shadow->num +#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++ +#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)-- +#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v + + +static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c); +static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, + off_t offset); + + +static ngx_buf_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + u_char *p; + ngx_buf_t *b; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + b = qc->free_bufs; + + if (b) { + qc->free_bufs = b->shadow; + p = b->start; + + } else { + b = qc->free_shadow_bufs; + + if (b) { + qc->free_shadow_bufs = b->shadow; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic use shadow buffer n:%ui %ui", + ++qc->nbufs, --qc->nshadowbufs); +#endif + + } else { + b = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new buffer n:%ui", ++qc->nbufs); +#endif + } + + p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE); + if (p == NULL) { + return NULL; + } + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b); +#endif + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + b->temporary = 1; + b->shadow = b; + + b->start = p; + b->pos = p; + b->last = p; + b->end = p + NGX_QUIC_BUFFER_SIZE; + + ngx_quic_buf_set_refs(b, 1); + + return b; +} + + +static void +ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *shadow; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_quic_buf_dec_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer %p r:%ui", + b, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + shadow = b->shadow; + + if (ngx_quic_buf_refs(b) == 0) { + shadow->shadow = qc->free_bufs; + qc->free_bufs = shadow; + } + + if (b != shadow) { + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + } + +} + + +static ngx_buf_t * +ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *nb; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + nb = qc->free_shadow_bufs; + + if (nb) { + qc->free_shadow_bufs = nb->shadow; + + } else { + nb = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (nb == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new shadow buffer n:%ui", ++qc->nshadowbufs); +#endif + } + + *nb = *b; + + ngx_quic_buf_inc_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clone buffer %p %p r:%ui", + b, nb, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + return nb; +} + + +static ngx_int_t +ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset) +{ + ngx_buf_t *b, *tb; + ngx_chain_t *tail; + + b = cl->buf; + + tail = ngx_alloc_chain_link(c->pool); + if (tail == NULL) { + return NGX_ERROR; + } + + tb = ngx_quic_clone_buf(c, b); + if (tb == NULL) { + return NGX_ERROR; + } + + tail->buf = tb; + + tb->pos += offset; + + b->last = tb->pos; + b->last_buf = 0; + + tail->next = cl->next; + cl->next = tail; + + return NGX_OK; +} + + +ngx_quic_frame_t * +ngx_quic_alloc_frame(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_frames)) { + + q = ngx_queue_head(&qc->free_frames); + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(&frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse frame n:%ui", qc->nframes); +#endif + + } else if (qc->nframes < 10000) { + frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NULL; + } + + ++qc->nframes; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc frame n:%ui", qc->nframes); +#endif + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + return NULL; + } + + ngx_memzero(frame, sizeof(ngx_quic_frame_t)); + + return frame; +} + + +void +ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (frame->data) { + ngx_quic_free_chain(c, frame->data); + } + + ngx_queue_insert_head(&qc->free_frames, &frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free frame n:%ui", qc->nframes); +#endif +} + + +void +ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) +{ + ngx_chain_t *cl; + + while (in) { + cl = in; + in = in->next; + + ngx_quic_free_buf(c, cl->buf); + ngx_free_chain(c->pool, cl); + } +} + + +void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + + do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_quic_free_frame(c, f); + } while (1); +} + + +void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) +{ + ngx_quic_send_ctx_t *ctx; + + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + ngx_queue_insert_tail(&ctx->frames, &frame->queue); + + frame->len = ngx_quic_create_frame(NULL, frame); + /* always succeeds */ + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +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; + + switch (f->type) { + case NGX_QUIC_FT_CRYPTO: + case NGX_QUIC_FT_STREAM: + break; + + default: + return NGX_DECLINED; + } + + if ((size_t) f->len <= len) { + return NGX_OK; + } + + shrink = f->len - len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic split frame now:%uz need:%uz shrink:%uz", + f->len, len, shrink); + + of = &f->u.ord; + + if (of->length <= shrink) { + return NGX_DECLINED; + } + + of->length -= shrink; + f->len = ngx_quic_create_frame(NULL, f); + + if ((size_t) f->len > len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); + return NGX_ERROR; + } + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + qb.chain = f->data; + + 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; + } + + *nf = *f; + onf = &nf->u.ord; + onf->offset += of->length; + onf->length = shrink; + nf->len = ngx_quic_create_frame(NULL, nf); + nf->data = qb.chain; + + if (f->type == NGX_QUIC_FT_STREAM) { + f->u.stream.fin = 0; + } + + ngx_queue_insert_after(&f->queue, &nf->queue); + + return NGX_OK; +} + + +ngx_chain_t * +ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len) +{ + ngx_buf_t buf; + ngx_chain_t cl, *out; + ngx_quic_buffer_t qb; + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = data; + buf.last = buf.pos + len; + buf.temporary = 1; + + cl.buf = &buf; + cl.next = NULL; + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + + if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + out = ngx_quic_read_buffer(c, &qb, len); + if (out == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + ngx_quic_free_buffer(c, &qb); + + return out; +} + + +ngx_chain_t * +ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) +{ + uint64_t n; + ngx_buf_t *b; + ngx_chain_t *out, **ll; + + out = qb->chain; + + for (ll = &out; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; + + if (b->sync) { + /* hole */ + break; + } + + if (limit == 0) { + break; + } + + n = b->last - b->pos; + + if (n > limit) { + if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + n = limit; + } + + limit -= n; + qb->offset += n; + } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } + + qb->chain = *ll; + *ll = NULL; + + return out; +} + + +void +ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl; + + while (qb->chain) { + if (qb->offset >= offset) { + break; + } + + cl = qb->chain; + b = cl->buf; + n = b->last - b->pos; + + if (qb->offset + n > offset) { + n = offset - qb->offset; + b->pos += n; + qb->offset += n; + break; + } + + qb->offset += n; + qb->chain = cl->next; + + cl->next = NULL; + ngx_quic_free_chain(c, cl); + } + + if (qb->chain == NULL) { + qb->offset = offset; + } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } +} + + +ngx_chain_t * +ngx_quic_alloc_chain(ngx_connection_t *c) +{ + ngx_chain_t *cl; + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_quic_alloc_buf(c); + if (cl->buf == NULL) { + return NULL; + } + + return cl; +} + + +ngx_chain_t * +ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset) +{ + u_char *p; + uint64_t n, base; + ngx_buf_t *b; + ngx_chain_t *cl, **chain; + + if (qb->last_chain && offset >= qb->last_offset) { + base = qb->last_offset; + chain = &qb->last_chain; + + } else { + base = qb->offset; + chain = &qb->chain; + } + + while (in && limit) { + + if (offset < base) { + n = ngx_min((uint64_t) (in->buf->last - in->buf->pos), + ngx_min(base - offset, limit)); + + in->buf->pos += n; + offset += n; + limit -= n; + + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + continue; + } + + cl = *chain; + + if (cl == NULL) { + cl = ngx_quic_alloc_chain(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->last = cl->buf->end; + cl->buf->sync = 1; /* hole */ + cl->next = NULL; + *chain = cl; + } + + b = cl->buf; + n = b->last - b->pos; + + if (base + n <= offset) { + base += n; + chain = &cl->next; + continue; + } + + if (b->sync && offset > base) { + if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + continue; + } + + p = b->pos + (offset - base); + + while (in) { + + if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { + in = in->next; + continue; + } + + if (p == b->last || limit == 0) { + break; + } + + n = ngx_min(b->last - p, in->buf->last - in->buf->pos); + n = ngx_min(n, limit); + + if (b->sync) { + ngx_memcpy(p, in->buf->pos, n); + qb->size += n; + } + + p += n; + in->buf->pos += n; + offset += n; + limit -= n; + } + + if (b->sync && p == b->last) { + b->sync = 0; + continue; + } + + if (b->sync && p != b->pos) { + if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + b->sync = 0; + } + } + + qb->last_offset = base; + qb->last_chain = *chain; + + return in; +} + + +void +ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb) +{ + ngx_quic_free_chain(c, qb->chain); + + qb->chain = NULL; +} + + +#if (NGX_DEBUG) + +void +ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) +{ + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range, largest, smallest; + ngx_uint_t i; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = buf + sizeof(buf); + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_PADDING: + p = ngx_slprintf(p, last, "PADDING"); + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", + f->u.ack.range_count, f->u.ack.delay); + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + largest = f->u.ack.largest; + smallest = f->u.ack.largest - f->u.ack.first_range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, "%uL", largest); + + } else { + p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); + } + + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; + } + + pos += n; + + largest = smallest - gap - 2; + smallest = largest - range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, " %uL", largest); + + } else { + p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); + } + } + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; + + case NGX_QUIC_FT_PING: + p = ngx_slprintf(p, last, "PING"); + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + p = ngx_slprintf(p, last, + "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", + f->u.retire_cid.sequence_number); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", + f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", + f->u.close.error_code); + + if (f->u.close.reason.len) { + p = ngx_slprintf(p, last, " %V", &f->u.close.reason); + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); + } + + break; + + case NGX_QUIC_FT_STREAM: + p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); + + if (f->u.stream.off) { + p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); + } + + if (f->u.stream.len) { + p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); + } + + if (f->u.stream.fin) { + p = ngx_slprintf(p, last, " fin:1"); + } + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_MAX_DATA: + p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", + f->u.max_data.max_data); + break; + + case NGX_QUIC_FT_RESET_STREAM: + p = ngx_slprintf(p, last, "RESET_STREAM" + " id:0x%xL error_code:0x%xL final_size:0x%xL", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", + f->u.streams_blocked.limit, f->u.streams_blocked.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", + f->u.max_streams.limit, f->u.max_streams.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", + f->u.max_stream_data.id, f->u.max_stream_data.limit); + break; + + + case NGX_QUIC_FT_DATA_BLOCKED: + p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", + f->u.data_blocked.limit); + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + p = ngx_slprintf(p, last, "NEW_TOKEN"); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " token:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_HANDSHAKE_DONE: + p = ngx_slprintf(p, last, "HANDSHAKE DONE"); + break; + + default: + p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", + tx ? "tx" : "rx", ngx_quic_level_name(f->level), + p - buf, buf); +} + +#endif diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_frames.h @@ -0,0 +1,45 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ +#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ + + +#include +#include + + +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + + +ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); +void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); +void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); +void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); +ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, + size_t len); + +ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); +void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); + +ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, + size_t len); +ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t limit); +ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset); +void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset); +void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb); + +#if (NGX_DEBUG) +void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); +#else +#define ngx_quic_log_frame(log, f, tx) +#endif + +#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_migration.c @@ -0,0 +1,711 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +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 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); + + +ngx_int_t +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + ngx_quic_frame_t frame, *fp; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + + frame.level = ssl_encryption_application; + frame.type = NGX_QUIC_FT_PATH_RESPONSE; + frame.u.path_response = *f; + + /* + * RFC 9000, 8.2.2. Path Validation Responses + * + * A PATH_RESPONSE frame MUST be sent on the network path where the + * PATH_CHALLENGE frame was received. + */ + + /* + * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes. + */ + if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) { + return NGX_ERROR; + } + + if (pkt->path == qc->path) { + /* + * RFC 9000, 9.3.3. Off-Path Packet Forwarding + * + * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD + * send a non-probing packet in response. + */ + + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } + + fp->level = ssl_encryption_application; + fp->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, fp); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_path_challenge_frame_t *f) +{ + ngx_uint_t rst; + ngx_queue_t *q; + ngx_quic_path_t *path, *prev; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* + * RFC 9000, 8.2.3. Successful Path Validation + * + * A PATH_RESPONSE frame received on any network path validates the path + * on which the PATH_CHALLENGE was sent. + */ + + 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; + } + + if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 + || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) + { + goto valid; + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stale PATH_RESPONSE ignored"); + + return NGX_OK; + +valid: + + /* + * RFC 9000, 9.4. Loss Detection and Congestion Control + * + * On confirming a peer's ownership of its new address, + * an endpoint MUST immediately reset the congestion controller + * and round-trip time estimator for the new path to initial values + * unless the only change in the peer's address is its port number. + */ + + rst = 1; + + prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (prev != NULL) { + + if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, + path->sockaddr, path->socklen, 0) + == NGX_OK) + { + /* address did not change */ + rst = 0; + } + } + + if (rst) { + ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); + + 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; + } + + /* + * RFC 9000, 9.3. Responding to Connection Migration + * + * After verifying a new client address, the server SHOULD + * send new address validation tokens (Section 8) to the client. + */ + + if (ngx_quic_send_new_token(c, path) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path seq:%uL addr:%V successfully validated", + path->seqnum, &path->addr_text); + + ngx_quic_path_dbg(c, "is validated", path); + + path->validated = 1; + path->validating = 0; + path->limited = 0; + + ngx_quic_set_path_timer(c); + + return NGX_OK; +} + + +ngx_quic_path_t * +ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_paths)) { + + q = ngx_queue_head(&qc->free_paths); + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + ngx_queue_remove(&path->queue); + + ngx_memzero(path, sizeof(ngx_quic_path_t)); + + } else { + + path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); + if (path == NULL) { + return NULL; + } + } + + ngx_queue_insert_tail(&qc->paths, &path->queue); + + path->cid = cid; + cid->used = 1; + + path->limited = 1; + + path->seqnum = qc->path_seqnum++; + + path->sockaddr = &path->sa.sockaddr; + path->socklen = socklen; + ngx_memcpy(path->sockaddr, sockaddr, socklen); + + path->addr_text.data = path->text; + path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL created addr:%V", + path->seqnum, &path->addr_text); + return path; +} + + +static ngx_quic_path_t * +ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + 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->tag == tag) { + return path; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + off_t len; + ngx_queue_t *q; + ngx_quic_path_t *path, *probe; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qsock = ngx_quic_get_socket(c); + + len = pkt->raw->last - pkt->raw->start; + + if (c->udp->buffer == NULL) { + /* first ever packet in connection, path already exists */ + path = qc->path; + goto update; + } + + probe = NULL; + + 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 (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, + path->sockaddr, path->socklen, 1) + == NGX_OK) + { + goto update; + } + + if (path->tag == NGX_QUIC_PATH_PROBE) { + probe = path; + } + } + + /* packet from new path, drop current probe, if any */ + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * only accept highest-numbered packets to prevent connection id + * exhaustion by excessive probing packets from unknown paths + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_DONE; + } + + if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { + return NGX_ERROR; + } + + /* new path requires new client id */ + cid = ngx_quic_next_client_id(c); + if (cid == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no available client ids for new path"); + /* stop processing of this datagram */ + return NGX_DONE; + } + + path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid); + if (path == NULL) { + return NGX_ERROR; + } + + path->tag = NGX_QUIC_PATH_PROBE; + + /* + * client arrived using new path and previously seen DCID, + * this indicates NAT rebinding (or bad client) + */ + if (qsock->used) { + pkt->rebound = 1; + } + +update: + + qsock->used = 1; + pkt->path = path; + + /* TODO: this may be too late in some cases; + * for example, if error happens during decrypt(), we cannot + * send CC, if error happens in 1st packet, due to amplification + * limit, because path->received = 0 + * + * should we account garbage as received or only decrypting packets? + */ + path->received += len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet len:%O via sock seq:%L path seq:%uL", + len, (int64_t) qsock->sid.seqnum, path->seqnum); + ngx_quic_path_dbg(c, "status", path); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&path->queue); + ngx_queue_insert_head(&qc->free_paths, &path->queue); + + /* + * invalidate CID that is no longer usable for any other path; + * this also requests new CIDs from client + */ + if (path->cid) { + if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL addr:%V retired", + path->seqnum, &path->addr_text); + + return NGX_OK; +} + + +static void +ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); + c->socklen = path->socklen; + + if (c->addr_text.data) { + 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, + "quic send path set to seq:%uL addr:%V", + path->seqnum, &path->addr_text); +} + + +ngx_int_t +ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_path_t *next, *bkp; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* got non-probing packet via non-active path */ + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * RFC 9000, 9.3. Responding to Connection Migration + * + * An endpoint only changes the address to which it sends packets in + * response to the highest-numbered non-probing packet. + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_OK; + } + + next = pkt->path; + + /* + * RFC 9000, 9.3.3: + * + * In response to an apparent migration, endpoints MUST validate the + * previously active path using a PATH_CHALLENGE frame. + */ + if (pkt->rebound) { + + /* NAT rebinding: client uses new path with old SID */ + if (ngx_quic_validate_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + if (qc->path->validated) { + + if (next->tag != NGX_QUIC_PATH_BACKUP) { + /* can delete backup path, if any */ + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) { + return NGX_ERROR; + } + } + + qc->path->tag = NGX_QUIC_PATH_BACKUP; + ngx_quic_path_dbg(c, "is now backup", qc->path); + + } else { + if (ngx_quic_free_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + /* switch active path to migrated */ + qc->path = next; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, next); + + if (!next->validated && !next->validating) { + if (ngx_quic_validate_path(c, next) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic migrated to path seq:%uL addr:%V", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is now active", qc->path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_msec_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "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; + } + + if (RAND_bytes(path->challenge2, 8) != 1) { + return NGX_ERROR; + } + + if (ngx_quic_send_path_challenge(c, path) != NGX_OK) { + return NGX_ERROR; + } + + 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; + + ngx_quic_set_path_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_frame_t frame; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL send path_challenge tries:%ui", + path->seqnum, path->tries); + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + + frame.level = ssl_encryption_application; + frame.type = NGX_QUIC_FT_PATH_CHALLENGE; + + ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); + + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes, + * unless the anti-amplification limit for the path does not permit + * sending a datagram of this size. + */ + + /* same applies to PATH_RESPONSE frames */ + if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { + return NGX_ERROR; + } + + ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); + + if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +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; + left = ngx_max(left, 1); + + if (next == -1 || left < next) { + next = left; + } + } + + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); + + } else if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } +} + + +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_connection_t *c; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + c = ev->data; + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + next = -1; + now = ngx_current_msec; + + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + + 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; + } + + 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) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + } + + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); + } +} diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_migration.h @@ -0,0 +1,42 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ + + +#include +#include + +#define NGX_QUIC_PATH_RETRIES 3 + +#define NGX_QUIC_PATH_PROBE 0 +#define NGX_QUIC_PATH_ACTIVE 1 +#define NGX_QUIC_PATH_BACKUP 2 + +#define ngx_quic_path_dbg(c, msg, path) \ + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \ + "quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \ + path->seqnum, msg, path->sent, path->received, \ + path->limited ? "L" : "", path->validated ? "V": "N", \ + path->validating ? "R": ""); + +ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_path_challenge_frame_t *f); + +ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid); +ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path); + +ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +void ngx_quic_path_validation_handler(ngx_event_t *ev); + +#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -0,0 +1,646 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +#define NGX_QUIC_COMPAT_RECORD_SIZE 1024 + +#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39 + +#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0" +#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0" + + +typedef struct { + ngx_quic_secret_t secret; + ngx_uint_t cipher; +} ngx_quic_compat_keys_t; + + +typedef struct { + ngx_log_t *log; + + u_char type; + ngx_str_t payload; + uint64_t number; + ngx_quic_compat_keys_t *keys; + + enum ssl_encryption_level_t level; +} ngx_quic_compat_record_t; + + +struct ngx_quic_compat_s { + const SSL_QUIC_METHOD *method; + + enum ssl_encryption_level_t write_level; + enum ssl_encryption_level_t read_level; + + uint64_t read_record; + ngx_quic_compat_keys_t keys; + + ngx_str_t tp; + ngx_str_t ctp; +}; + + +static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line); +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +static int ngx_quic_compat_add_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char **out, + size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg); +static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char *in, + size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg); +static void ngx_quic_compat_message_callback(int write_p, int version, + int content_type, const void *buf, size_t len, SSL *ssl, void *arg); +static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, + u_char *out, ngx_uint_t plain); +static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx) +{ + SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback); + + if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) { + return NGX_OK; + } + + if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT, + SSL_EXT_CLIENT_HELLO + |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + ngx_quic_compat_add_transport_params_callback, + NULL, + NULL, + ngx_quic_compat_parse_transport_params_callback, + NULL) + == 0) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_add_custom_ext() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) +{ + u_char ch, *p, *start, value; + size_t n; + ngx_uint_t write; + const SSL_CIPHER *cipher; + ngx_quic_compat_t *com; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + u_char secret[EVP_MAX_MD_SIZE]; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return; + } + + p = (u_char *) line; + + for (start = p; *p && *p != ' '; p++); + + n = p - start; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat secret %*s", n, start); + + if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 0; + + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 1; + + } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n) + == 0) + { + level = ssl_encryption_application; + write = 0; + + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n) + == 0) + { + level = ssl_encryption_application; + write = 1; + + } else { + return; + } + + if (*p++ == '\0') { + return; + } + + for ( /* void */ ; *p && *p != ' '; p++); + + if (*p++ == '\0') { + return; + } + + for (n = 0, start = p; *p; p++) { + ch = *p; + + if (ch >= '0' && ch <= '9') { + value = ch - '0'; + goto next; + } + + ch = (u_char) (ch | 0x20); + + if (ch >= 'a' && ch <= 'f') { + value = ch - 'a' + 10; + goto next; + } + + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "invalid OpenSSL QUIC secret format"); + + return; + + next: + + if ((p - start) % 2) { + secret[n++] += value; + + } else { + if (n >= EVP_MAX_MD_SIZE) { + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "too big OpenSSL QUIC secret"); + return; + } + + secret[n] = (value << 4); + } + } + + qc = ngx_quic_get_connection(c); + com = qc->compat; + cipher = SSL_get_current_cipher(ssl); + + if (write) { + com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); + com->write_level = level; + + } else { + com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); + com->read_level = level; + com->read_record = 0; + + (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level, + cipher, secret, n); + } +} + + +static ngx_int_t +ngx_quic_compat_set_encryption_secret(ngx_log_t *log, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_hkdf_t seq[2]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = &keys->secret; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + if (sizeof(peer_secret->secret.data) < secret_len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "unexpected secret len: %uz", secret_len); + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + + peer_secret->key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static int +ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat add transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out = com->tp.data; + *outlen = com->tp.len; + + return 1; +} + + +static int +ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char *in, size_t inlen, X509 *x, + size_t chainidx, int *al, void *parse_arg) +{ + u_char *p; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat parse transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + p = ngx_pnalloc(c->pool, inlen); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, in, inlen); + + com->ctp.data = p; + com->ctp.len = inlen; + + return 1; +} + + +int +SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) +{ + BIO *rbio, *wbio; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method"); + + qc = ngx_quic_get_connection(c); + + qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t)); + if (qc->compat == NULL) { + return 0; + } + + com = qc->compat; + com->method = quic_method; + + rbio = BIO_new(BIO_s_mem()); + if (rbio == NULL) { + return 0; + } + + wbio = BIO_new(BIO_s_null()); + if (wbio == NULL) { + return 0; + } + + SSL_set_bio(ssl, rbio, wbio); + + SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback); + + /* early data is not supported */ + SSL_set_max_early_data(ssl, 0); + + return 1; +} + + +static void +ngx_quic_compat_message_callback(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + ngx_uint_t alert; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + + if (!write_p) { + return; + } + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + /* closing */ + return; + } + + com = qc->compat; + level = com->write_level; + + switch (content_type) { + + case SSL3_RT_HANDSHAKE: + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat tx %s len:%uz ", + ngx_quic_level_name(level), len); + + (void) com->method->add_handshake_data(ssl, level, buf, len); + + break; + + case SSL3_RT_ALERT: + if (len >= 2) { + alert = ((u_char *) buf)[1]; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat %s alert:%ui len:%uz ", + ngx_quic_level_name(level), alert, len); + + (void) com->method->send_alert(ssl, level, alert); + } + + break; + } +} + + +int +SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) +{ + BIO *rbio; + size_t n; + u_char *p; + ngx_str_t res; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + ngx_quic_compat_record_t rec; + u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1]; + u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1 + + SSL3_RT_HEADER_LENGTH + + EVP_GCM_TLS_TAG_LEN]; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz", + ngx_quic_level_name(level), len); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + rbio = SSL_get_rbio(ssl); + + while (len) { + ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t)); + + rec.type = SSL3_RT_HANDSHAKE; + rec.log = c->log; + rec.number = com->read_record++; + rec.keys = &com->keys; + + if (level == ssl_encryption_initial) { + n = ngx_min(len, 65535); + + rec.payload.len = n; + rec.payload.data = (u_char *) data; + + ngx_quic_compat_create_header(&rec, out, 1); + + BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH); + BIO_write(rbio, data, n); + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %*xs%*xs", + n + SSL3_RT_HEADER_LENGTH, + (size_t) SSL3_RT_HEADER_LENGTH, out, n, data); +#endif + + } else { + n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE); + + p = ngx_cpymem(in, data, n); + *p++ = SSL3_RT_HANDSHAKE; + + rec.payload.len = p - in; + rec.payload.data = in; + + res.data = out; + + if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) { + return 0; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %xV", res.len, &res); +#endif + + BIO_write(rbio, res.data, res.len); + } + + data += n; + len -= n; + } + + return 1; +} + + +static size_t +ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out, + ngx_uint_t plain) +{ + u_char type; + size_t len; + + len = rec->payload.len; + + if (plain) { + type = rec->type; + + } else { + type = SSL3_RT_APPLICATION_DATA; + len += EVP_GCM_TLS_TAG_LEN; + } + + out[0] = type; + out[1] = 0x03; + out[2] = 0x03; + out[3] = (len >> 8); + out[4] = len; + + return 5; +} + + +static ngx_int_t +ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res) +{ + ngx_str_t ad, out; + ngx_quic_secret_t *secret; + ngx_quic_ciphers_t ciphers; + u_char nonce[NGX_QUIC_IV_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); + + out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0, + "quic compat ad len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR) + { + return NGX_ERROR; + } + + secret = &rec->keys->secret; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); + + if (ngx_quic_tls_seal(ciphers.c, secret, &out, + nonce, &rec->payload, &ad, rec->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +enum ssl_encryption_level_t +SSL_quic_read_level(const SSL *ssl) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + return qc->compat->read_level; +} + + +enum ssl_encryption_level_t +SSL_quic_write_level(const SSL *ssl) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + return qc->compat->write_level; +} + + +int +SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + com->tp.len = params_len; + com->tp.data = (u_char *) params; + + return 1; +} + + +void +SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params, + size_t *out_params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out_params = com->ctp.data; + *out_params_len = com->ctp.len; +} + +#endif /* NGX_QUIC_OPENSSL_COMPAT */ diff --git a/src/event/quic/ngx_event_quic_openssl_compat.h b/src/event/quic/ngx_event_quic_openssl_compat.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_openssl_compat.h @@ -0,0 +1,60 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ + +#ifdef TLSEXT_TYPE_quic_transport_parameters +#undef NGX_QUIC_OPENSSL_COMPAT +#else + + +#include +#include + + +typedef struct ngx_quic_compat_s ngx_quic_compat_t; + + +enum ssl_encryption_level_t { + ssl_encryption_initial = 0, + ssl_encryption_early_data, + ssl_encryption_handshake, + ssl_encryption_application +}; + + +typedef struct ssl_quic_method_st { + int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len); + int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len); + int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); + int (*flush_flight)(SSL *ssl); + int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, + uint8_t alert); +} SSL_QUIC_METHOD; + + +ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx); + +int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method); +int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); +enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl); +enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl); +int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len); +void SSL_get_peer_quic_transport_params(const SSL *ssl, + const uint8_t **out_params, size_t *out_params_len); + + +#endif /* TLSEXT_TYPE_quic_transport_parameters */ + +#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_output.c @@ -0,0 +1,1293 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#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 */ + +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ +#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ +#define NGX_QUIC_RETRY_BUFFER_SIZE 256 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ + +/* + * RFC 9000, 10.3. Stateless Reset + * + * Endpoints MUST discard packets that are too small to be valid QUIC + * packets. With the set of AEAD functions defined in [QUIC-TLS], + * short header packets that are smaller than 21 bytes are never valid. + */ +#define NGX_QUIC_MIN_PKT_LEN 21 + +#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */ +#define NGX_QUIC_MAX_SR_PACKET 1200 + +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + +#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */ + + +static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c); +static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum); +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) +static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c); +static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c); +static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, + size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment); +#endif +static ssize_t ngx_quic_output_packet(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); +static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_header_t *pkt, ngx_quic_path_t *path); +static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen); +static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, + ngx_quic_send_ctx_t *ctx); +static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, + size_t size); + + +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) +{ + size_t in_flight; + ngx_int_t rc; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + c->log->action = "sending frames"; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + in_flight = cg->in_flight; + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + if (ngx_quic_allow_segmentation(c)) { + rc = ngx_quic_create_segments(c); + } else +#endif + { + rc = ngx_quic_create_datagrams(c); + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (in_flight == cg->in_flight || qc->closing) { + /* no ack-eliciting data was sent or we are done */ + return NGX_OK; + } + + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_datagrams(ngx_connection_t *c) +{ + size_t len, min; + ssize_t n; + u_char *p; + uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST]; + ngx_uint_t i, pad; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + path = qc->path; + + while (cg->in_flight < cg->window) { + + 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); + + pad = ngx_quic_get_padding_level(c); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + preserved_pnum[i] = ctx->pnum; + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + + if (min > len) { + /* padding can't be applied - avoid sending the packet */ + + while (i-- > 0) { + ctx = &qc->send_ctx[i]; + ngx_quic_revert_send(c, ctx, preserved_pnum[i]); + } + + return NGX_OK; + } + + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + p += n; + len -= n; + } + + len = p - dst; + if (len == 0) { + break; + } + + n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]); + } + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_commit_send(c, &qc->send_ctx[i]); + } + + path->sent += len; + } + + return NGX_OK; +} + + +static void +ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cg = &qc->congestion; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_head(&ctx->sending); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + + if (f->pkt_need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, q); + + cg->in_flight += f->plen; + + } else { + ngx_quic_free_frame(c, f); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); +} + + +static void +ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum) +{ + ngx_queue_t *q; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_last(&ctx->sending); + ngx_queue_remove(q); + ngx_queue_insert_head(&ctx->frames, q); + } + + ctx->pnum = pnum; +} + + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + +static ngx_uint_t +ngx_quic_allow_segmentation(ngx_connection_t *c) +{ + size_t bytes, len; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!qc->conf->gso_enabled) { + return 0; + } + + if (qc->path->limited) { + /* don't even try to be faster on non-validated paths */ + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + 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); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + bytes += f->len; + + if (bytes > len * 3) { + /* require at least ~3 full packets to batch */ + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_create_segments(ngx_connection_t *c) +{ + size_t len, segsize; + ssize_t n; + u_char *p, *end; + uint64_t preserved_pnum; + ngx_uint_t nseg; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + path = qc->path; + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + segsize = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_SEGMENT_BUF); + p = dst; + end = dst + sizeof(dst); + + nseg = 0; + + preserved_pnum = ctx->pnum; + + for ( ;; ) { + + len = ngx_min(segsize, (size_t) (end - p)); + + if (len && cg->in_flight < cg->window) { + + n = ngx_quic_output_packet(c, ctx, p, len, len); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n) { + p += n; + nseg++; + } + + } else { + n = 0; + } + + if (p == dst) { + break; + } + + if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) { + n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr, + path->socklen, segsize); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + ngx_quic_revert_send(c, ctx, preserved_pnum); + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + ngx_quic_commit_send(c, ctx); + + path->sent += n; + + p = dst; + nseg = 0; + preserved_pnum = ctx->pnum; + } + } + + return NGX_OK; +} + + +static ssize_t +ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen, size_t segment) +{ + size_t clen; + ssize_t n; + uint16_t *valp; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + +#if (NGX_HAVE_ADDRINFO_CMSG) + char msg_control[CMSG_SPACE(sizeof(uint16_t)) + + CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#else + char msg_control[CMSG_SPACE(sizeof(uint16_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + ngx_memzero(msg_control, sizeof(msg_control)); + + iov.iov_len = len; + iov.iov_base = buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + + cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_level = SOL_UDP; + cmsg->cmsg_type = UDP_SEGMENT; + cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + + clen = CMSG_SPACE(sizeof(uint16_t)); + + valp = (void *) CMSG_DATA(cmsg); + *valp = segment; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + cmsg = CMSG_NXTHDR(&msg, cmsg); + clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + msg.msg_controllen = clen; + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; + } + + c->sent += n; + + return n; +} + +#endif + + + +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* + * RFC 9000, 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes. + */ + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->need_ack) { + for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i + 1]; + + if (ngx_queue_empty(&ctx->frames)) { + break; + } + } + + return i; + } + } + + return NGX_QUIC_SEND_CTX_LAST; +} + + +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) +{ + size_t len, pad, min_payload, max_payload; + u_char *p; + ssize_t flen; + ngx_str_t res; + ngx_int_t rc; + ngx_uint_t nframes, expand; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + qc = ngx_quic_get_connection(c); + + ngx_quic_init_packet(c, ctx, &pkt, qc->path); + + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); + + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + if (min_payload > max_payload) { + return 0; + } + + now = ngx_current_msec; + nframes = 0; + p = src; + len = 0; + expand = 0; + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE + || f->type == NGX_QUIC_FT_PATH_CHALLENGE)) + { + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a + * PATH_CHALLENGE frame to at least the smallest allowed + * maximum datagram size of 1200 bytes... + * + * (same applies to PATH_RESPONSE frames) + */ + + if (max < 1200) { + /* expanded packet will not fit */ + break; + } + + if (min < 1200) { + min = 1200; + + min_payload = ngx_quic_payload_size(&pkt, min); + } + + expand = 1; + } + + if (len >= max_payload) { + break; + } + + if (len + f->len > max_payload) { + rc = ngx_quic_split_frame(c, f, max_payload - len); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + break; + } + } + + if (f->need_ack) { + pkt.need_ack = 1; + } + + ngx_quic_log_frame(c->log, f, 1); + + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; + } + + len += flen; + p += flen; + + f->pnum = ctx->pnum; + f->first = now; + f->last = now; + f->plen = 0; + + nframes++; + + if (f->flush) { + break; + } + } + + if (nframes == 0) { + return 0; + } + + if (len < min_payload) { + ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = data; + + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx %s bytes:%ui" + " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", + ngx_quic_level_name(ctx->level), pkt.payload.len, + pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + ctx->pnum++; + + if (pkt.need_ack) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + f->plen = res.len; + } + + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + f->pkt_need_ack = pkt.need_ack; + + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sending, q); + } + + return res.len; +} + + +static void +ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_header_t *pkt, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(pkt, sizeof(ngx_quic_header_t)); + + pkt->flags = NGX_QUIC_PKT_FIXED_BIT; + + if (ctx->level == ssl_encryption_initial) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + + } else if (ctx->level == ssl_encryption_handshake) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; + + } else { + if (qc->key_phase) { + pkt->flags |= NGX_QUIC_PKT_KPHASE; + } + } + + pkt->dcid.data = path->cid->id; + pkt->dcid.len = path->cid->len; + + pkt->scid = qc->tp.initial_scid; + + pkt->version = qc->version; + pkt->log = c->log; + pkt->level = ctx->level; + + pkt->keys = qc->keys; + + ngx_quic_set_packet_number(pkt, ctx); +} + + +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen) +{ + ssize_t n; + struct iovec iov; + struct msghdr msg; +#if (NGX_HAVE_ADDRINFO_CMSG) + struct cmsghdr *cmsg; + char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov.iov_len = len; + iov.iov_base = buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + ngx_memzero(msg_control, sizeof(msg_control)); + + cmsg = CMSG_FIRSTHDR(&msg); + + msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; + } + + c->sent += n; + + return n; +} + + +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) +{ + uint64_t delta; + + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; + + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ctx->pnum & 0xff; + + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ctx->pnum & 0xffff; + + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ctx->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ctx->pnum & 0xffffffff; + } +} + + +ngx_int_t +ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) +{ + size_t len; + ngx_quic_header_t pkt; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending version negotiation packet"); + + pkt.log = c->log; + pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + + len = ngx_quic_create_version_negotiation(&pkt, buf); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic vnego packet to send len:%uz %*xs", len, len, buf); +#endif + + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); + + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + u_char *token; + size_t len, max; + uint16_t rndbytes; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle stateless reset output"); + + if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { + len = pkt->len - 1; + + } else { + max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); + + if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { + return NGX_ERROR; + } + + len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) + + NGX_QUIC_MIN_SR_PACKET; + } + + if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { + return NGX_ERROR; + } + + buf[0] &= ~NGX_QUIC_PKT_LONG; + buf[0] |= NGX_QUIC_PKT_FIXED_BIT; + + token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; + + if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) + != NGX_OK) + { + return NGX_ERROR; + } + + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_quic_send_cc(ngx_connection_t *c) +{ + ngx_quic_frame_t frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->draining) { + return NGX_OK; + } + + if (qc->closing + && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) + { + /* dot not send CC too often */ + return NGX_OK; + } + + 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; + + if (qc->error_reason) { + frame.u.close.reason.len = ngx_strlen(qc->error_reason); + frame.u.close.reason.data = (u_char *) qc->error_reason; + } + + qc->last_cc = ngx_current_msec; + + return ngx_quic_frame_sendto(c, &frame, 0, qc->path); +} + + +ngx_int_t +ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, + ngx_uint_t err, const char *reason) +{ + ssize_t len; + ngx_str_t res; + ngx_quic_keys_t keys; + ngx_quic_frame_t frame; + ngx_quic_header_t pkt; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + frame.level = inpkt->level; + frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame.u.close.error_code = err; + + frame.u.close.reason.data = (u_char *) reason; + frame.u.close.reason.len = ngx_strlen(reason); + + len = ngx_quic_create_frame(NULL, &frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, &frame, 1); + + len = ngx_quic_create_frame(src, &frame); + if (len == -1) { + return NGX_ERROR; + } + + ngx_memzero(&keys, sizeof(ngx_quic_keys_t)); + + pkt.keys = &keys; + + if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) + != NGX_OK) + { + return NGX_ERROR; + } + + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG + | NGX_QUIC_PKT_INITIAL; + + pkt.num_len = 1; + /* + * pkt.num = 0; + * pkt.trunc = 0; + */ + + pkt.version = inpkt->version; + pkt.log = c->log; + pkt.level = inpkt->level; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) { + return NGX_ERROR; + } + + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *inpkt) +{ + time_t expires; + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + u_char dcid[NGX_QUIC_SERVER_CID_LEN]; + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + + expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; + + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key, + &token, &inpkt->dcid, expires, 1) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.version = inpkt->version; + pkt.log = c->log; + + pkt.odcid = inpkt->dcid; + pkt.dcid = inpkt->scid; + + /* TODO: generate routable dcid */ + if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + + pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; + pkt.scid.data = dcid; + + pkt.token = token; + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet to send len:%uz %xV", res.len, &res); +#endif + + len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); + if (len < 0) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic retry packet sent to %xV", &pkt.dcid); + + /* + * RFC 9000, 17.2.5.1. Sending a Retry Packet + * + * A server MUST NOT send more than one Retry + * packet in response to a single UDP datagram. + * NGX_DONE will stop quic_input() from processing further + */ + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) +{ + time_t expires; + ngx_str_t token; + ngx_chain_t *out; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + + qc = ngx_quic_get_connection(c); + + expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; + + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen, + qc->conf->av_token_key, &token, NULL, expires, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + out = ngx_quic_copy_buffer(c, token.data, token.len); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->data = out; + frame->u.token.length = token.len; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t len, left; + uint64_t ack_delay; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, **ll; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= qc->tp.ack_delay_exponent; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + ll = &frame->data; + b = NULL; + + for (i = 0; i < ctx->nranges; i++) { + len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + + left = b ? b->end - b->last : 0; + + if (left < len) { + cl = ngx_quic_alloc_chain(c); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + left = b->end - b->last; + + if (left < len) { + return NGX_ERROR; + } + } + + b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, + ctx->ranges[i].range); + + frame->u.ack.ranges_length += len; + } + + *ll = NULL; + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ack_delay; + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path) +{ + size_t min_payload, pad; + ssize_t len, sent; + ngx_str_t res; + ngx_quic_header_t pkt; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + ngx_quic_init_packet(c, ctx, &pkt, path); + + min = ngx_quic_path_limit(c, path, min); + + min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; + + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + len = ngx_quic_create_frame(NULL, frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, frame, 1); + + len = ngx_quic_create_frame(src, frame); + if (len == -1) { + return NGX_ERROR; + } + + if (len < (ssize_t) min_payload) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + ctx->pnum++; + + sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen); + if (sent < 0) { + return NGX_ERROR; + } + + path->sent += sent; + + return NGX_OK; +} + + +static size_t +ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) +{ + off_t max; + + if (path->limited) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + + if ((off_t) size > max) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path limit %uz - %O", size, max); + return max; + } + } + + return size; +} diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_output.h @@ -0,0 +1,40 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ + + +#include +#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, + ngx_quic_header_t *inpkt); + +ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); +ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, + ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); + +ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); + +ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); + +ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path); + +#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_protection.c @@ -0,0 +1,1087 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ +#define NGX_QUIC_HP_LEN 5 + +#define NGX_QUIC_AES_128_KEY_LEN 16 + +#ifndef TLS1_3_CK_AES_128_GCM_SHA256 +#define TLS1_3_CK_AES_128_GCM_SHA256 0x03001301 +#define TLS1_3_CK_AES_256_GCM_SHA384 0x03001302 +#define TLS1_3_CK_CHACHA20_POLY1305_SHA256 \ + 0x03001303 +#endif + + +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); + +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn); + +static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad, ngx_log_t *log); +static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in); + +static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); +static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, + enum ssl_encryption_level_t level) +{ + ngx_int_t len; + + if (level == ssl_encryption_initial) { + id = TLS1_3_CK_AES_128_GCM_SHA256; + } + + switch (id) { + + case TLS1_3_CK_AES_128_GCM_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_128_gcm(); +#else + ciphers->c = EVP_aes_128_gcm(); +#endif + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; + + case TLS1_3_CK_AES_256_GCM_SHA384: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_256_gcm(); +#else + ciphers->c = EVP_aes_256_gcm(); +#endif + ciphers->hp = EVP_aes_256_ctr(); + ciphers->d = EVP_sha384(); + len = 32; + break; + + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_chacha20_poly1305(); +#else + ciphers->c = EVP_chacha20_poly1305(); +#endif +#ifdef OPENSSL_IS_BORINGSSL + ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); +#else + ciphers->hp = EVP_chacha20(); +#endif + ciphers->d = EVP_sha256(); + len = 32; + break; + + default: + return NGX_ERROR; + } + + return len; +} + + +ngx_int_t +ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, + ngx_log_t *log) +{ + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_str_t iss; + ngx_uint_t i; + const EVP_MD *digest; + ngx_quic_hkdf_t seq[8]; + ngx_quic_secret_t *client, *server; + + static const uint8_t salt[20] = + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" + "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; + + client = &keys->secrets[ssl_encryption_initial].client; + server = &keys->secrets[ssl_encryption_initial].server; + + /* + * RFC 9001, section 5. Packet Protection + * + * Initial packets use AEAD_AES_128_GCM. The hash function + * for HKDF when deriving initial secrets and keys is SHA-256. + */ + + digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt, sizeof(salt)) + != NGX_OK) + { + return NGX_ERROR; + } + + iss.len = is_len; + iss.data = is; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic ngx_quic_set_initial_secret"); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, + "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, + "quic initial secret len:%uz %*xs", is_len, is_len, is); +#endif + + client->secret.len = SHA256_DIGEST_LENGTH; + server->secret.len = SHA256_DIGEST_LENGTH; + + client->key.len = NGX_QUIC_AES_128_KEY_LEN; + server->key.len = NGX_QUIC_AES_128_KEY_LEN; + + client->hp.len = NGX_QUIC_AES_128_KEY_LEN; + server->hp.len = NGX_QUIC_AES_128_KEY_LEN; + + client->iv.len = NGX_QUIC_IV_LEN; + server->iv.len = NGX_QUIC_IV_LEN; + + /* labels per RFC 9001, 5.1. Packet Protection Keys */ + ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client->key, &client->secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret); + ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss); + ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server->key, &server->secret); + ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret); + ngx_quic_hkdf_set(&seq[7], "tls13 quic hp", &server->hp, &server->secret); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log) +{ + size_t info_len; + uint8_t *p; + uint8_t info[20]; + + info_len = 2 + 1 + h->label_len + 1; + + info[0] = 0; + info[1] = h->out_len; + info[2] = h->label_len; + + p = ngx_cpymem(&info[3], h->label, h->label_len); + *p = '\0'; + + if (ngx_hkdf_expand(h->out, h->out_len, digest, + h->prk, h->prk_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%*s) failed", h->label_len, h->label); + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, + "quic expand \"%*s\" len:%uz %*xs", + h->label_len, h->label, h->out_len, h->out_len, h->out); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, + const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) + == 0) + { + return NGX_ERROR; + } + + return NGX_OK; + +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + + return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif +} + + +static ngx_int_t +ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, + const u_char *secret, size_t secret_len, const u_char *salt, + size_t salt_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, + salt_len) + == 0) + { + return NGX_ERROR; + } + + return NGX_OK; + +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + + return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif +} + + +static ngx_int_t +ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, + ngx_log_t *log) +{ + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + u_char *tag; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, + in->len - EVP_GCM_TLS_TAG_LEN) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + EVP_CIPHER_CTX_free(ctx); +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, + out->data + in->len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); + return NGX_ERROR; + } + + EVP_CIPHER_CTX_free(ctx); + + out->len += EVP_GCM_TLS_TAG_LEN; +#endif + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + u_char zero[NGX_QUIC_HP_LEN] = {0}; + +#ifdef OPENSSL_IS_BORINGSSL + uint32_t cnt; + + ngx_memcpy(&cnt, in, sizeof(uint32_t)); + + if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); + return NGX_OK; + } +#endif + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + goto failed; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + goto failed; + } + + if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); + goto failed; + } + + EVP_CIPHER_CTX_free(ctx); + + return NGX_OK; + +failed: + + EVP_CIPHER_CTX_free(ctx); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_hkdf_t seq[3]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = is_write ? &keys->secrets[level].server + : &keys->secrets[level].client; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + if (sizeof(peer_secret->secret.data) < secret_len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "unexpected secret len: %uz", secret_len); + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + + peer_secret->key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + peer_secret->hp.len = key_len; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 quic key", + &peer_secret->key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str); + ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &peer_secret->hp, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_uint_t +ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + return keys->secrets[level].client.key.len != 0; +} + + +void +ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + keys->secrets[level].client.key.len = 0; +} + + +void +ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_quic_secrets_t *current, *next, tmp; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + tmp = *current; + *current = *next; + *next = tmp; +} + + +ngx_int_t +ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_uint_t i; + ngx_quic_hkdf_t seq[6]; + ngx_quic_ciphers_t ciphers; + ngx_quic_secrets_t *current, *next; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); + + if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) + == NGX_ERROR) + { + return NGX_ERROR; + } + + next->client.secret.len = current->client.secret.len; + next->client.key.len = current->client.key.len; + next->client.iv.len = NGX_QUIC_IV_LEN; + next->client.hp = current->client.hp; + + next->server.secret.len = current->server.secret.len; + next->server.key.len = current->server.key.len; + next->server.iv.len = NGX_QUIC_IV_LEN; + next->server.hp = current->server.hp; + + ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", + &next->client.secret, ¤t->client.secret); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", + &next->client.key, &next->client.secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", + &next->client.iv, &next->client.secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic ku", + &next->server.secret, ¤t->server.secret); + ngx_quic_hkdf_set(&seq[4], "tls13 quic key", + &next->server.key, &next->server.secret); + ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", + &next->server.iv, &next->server.secret); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *pnp, *sample; + ngx_str_t ad, out; + ngx_uint_t i; + ngx_quic_secret_t *secret; + ngx_quic_ciphers_t ciphers; + u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_create_header(pkt, ad.data, &pnp); + + out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { + return NGX_ERROR; + } + + secret = &pkt->keys->secrets[pkt->level].server; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); + + if (ngx_quic_tls_seal(ciphers.c, secret, &out, + nonce, &pkt->payload, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + sample = &out.data[4 - pkt->num_len]; + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) + != NGX_OK) + { + return NGX_ERROR; + } + + /* RFC 9001, 5.4.1. Header Protection Application */ + ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + for (i = 0; i < pkt->num_len; i++) { + pnp[i] ^= mask[i + 1]; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *start; + ngx_str_t ad, itag; + ngx_quic_secret_t secret; + ngx_quic_ciphers_t ciphers; + + /* 5.8. Retry Packet Integrity */ + static u_char key[16] = + "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; + static u_char nonce[NGX_QUIC_IV_LEN] = + "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; + static ngx_str_t in = ngx_string(""); + + ad.data = res->data; + ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + itag.len = EVP_GCM_TLS_TAG_LEN; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic retry itag len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; + } + + secret.key.len = sizeof(key); + ngx_memcpy(secret.key.data, key, sizeof(key)); + secret.iv.len = NGX_QUIC_IV_LEN; + + if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = itag.data + itag.len - start; + res->data = start; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, + ngx_str_t *salt, u_char *out, size_t len) +{ + size_t is_len, info_len; + uint8_t *p; + const EVP_MD *digest; + + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t info[20]; + + digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt->data, salt->len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_extract(%s) failed", label); + return NGX_ERROR; + } + + info[0] = 0; + info[1] = len; + info[2] = ngx_strlen(label); + + info_len = 2 + 1 + info[2] + 1; + + if (info_len >= 20) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "ngx_quic_create_key label \"%s\" too long", label); + return NGX_ERROR; + } + + p = ngx_cpymem(&info[3], label, info[2]); + *p = '\0'; + + if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%s) failed", label); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn) +{ + u_char *p; + uint64_t truncated_pn, expected_pn, candidate_pn; + uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; + + pn_nbits = ngx_min(len * 8, 62); + + p = *pos; + truncated_pn = *p++ ^ *mask++; + + while (--len) { + truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); + } + + *pos = p; + + expected_pn = *largest_pn + 1; + pn_win = 1ULL << pn_nbits; + pn_hwin = pn_win / 2; + pn_mask = pn_win - 1; + + candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) + && candidate_pn < (1ULL << 62) - pn_win) + { + candidate_pn += pn_win; + + } else if (candidate_pn > expected_pn + pn_hwin + && candidate_pn >= pn_win) + { + candidate_pn -= pn_win; + } + + *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); + + return candidate_pn; +} + + +void +ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) +{ + nonce[len - 8] ^= (pn >> 56) & 0x3f; + nonce[len - 7] ^= (pn >> 48) & 0xff; + nonce[len - 6] ^= (pn >> 40) & 0xff; + nonce[len - 5] ^= (pn >> 32) & 0xff; + nonce[len - 4] ^= (pn >> 24) & 0xff; + nonce[len - 3] ^= (pn >> 16) & 0xff; + nonce[len - 2] ^= (pn >> 8) & 0xff; + nonce[len - 1] ^= pn & 0xff; +} + + +ngx_int_t +ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + if (ngx_quic_pkt_retry(pkt->flags)) { + return ngx_quic_create_retry_packet(pkt, res); + } + + return ngx_quic_create_packet(pkt, res); +} + + +ngx_int_t +ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) +{ + u_char *p, *sample; + size_t len; + uint64_t pn, lpn; + ngx_int_t pnl, rc; + ngx_str_t in, ad; + ngx_uint_t key_phase; + ngx_quic_secret_t *secret; + ngx_quic_ciphers_t ciphers; + uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; + + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { + return NGX_ERROR; + } + + secret = &pkt->keys->secrets[pkt->level].client; + + p = pkt->raw->pos; + len = pkt->data + pkt->len - p; + + /* + * RFC 9001, 5.4.2. Header Protection Sample + * 5.4.3. AES-Based Header Protection + * 5.4.4. ChaCha20-Based Header Protection + * + * the Packet Number field is assumed to be 4 bytes long + * AES and ChaCha20 algorithms sample 16 bytes + */ + + if (len < EVP_GCM_TLS_TAG_LEN + 4) { + return NGX_DECLINED; + } + + sample = p + 4; + + /* header protection */ + + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) + != NGX_OK) + { + return NGX_DECLINED; + } + + pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + if (ngx_quic_short_pkt(pkt->flags)) { + key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; + + if (key_phase != pkt->key_phase) { + secret = &pkt->keys->next_key.client; + pkt->key_update = 1; + } + } + + lpn = *largest_pn; + + pnl = (pkt->flags & 0x03) + 1; + pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); + + pkt->pn = pn; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx clearflags:%xd", pkt->flags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx number:%uL len:%xi", pn, pnl); + + /* packet protection */ + + in.data = p; + in.len = len - pnl; + + ad.len = p - pkt->data; + ad.data = pkt->plaintext; + + ngx_memcpy(ad.data, pkt->data, ad.len); + ad.data[0] = pkt->flags; + + do { + ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; + } while (--pnl); + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; + pkt->payload.data = pkt->plaintext + ad.len; + + rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, + nonce, &in, &ad, pkt->log); + if (rc != NGX_OK) { + return NGX_DECLINED; + } + + if (pkt->payload.len == 0) { + /* + * RFC 9000, 12.4. Frames and Frame Types + * + * An endpoint MUST treat receipt of a packet containing no + * frames as a connection error of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { + /* + * RFC 9000, Reserved Bits + * + * An endpoint MUST treat receipt of a packet that has + * a non-zero value for these bits, after removing both + * packet and header protection, as a connection error + * of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic reserved bit set in packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet payload len:%uz %xV", + pkt->payload.len, &pkt->payload); +#endif + + *largest_pn = lpn; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_protection.h @@ -0,0 +1,114 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ + + +#include +#include + +#include + + +#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) + +/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ +#define NGX_QUIC_IV_LEN 12 + +/* largest hash used in TLS is SHA-384 */ +#define NGX_QUIC_MAX_MD_SIZE 48 + + +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#else +#define ngx_quic_cipher_t EVP_CIPHER +#endif + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_MAX_MD_SIZE]; +} ngx_quic_md_t; + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_IV_LEN]; +} ngx_quic_iv_t; + + +typedef struct { + ngx_quic_md_t secret; + ngx_quic_md_t key; + ngx_quic_iv_t iv; + ngx_quic_md_t hp; +} ngx_quic_secret_t; + + +typedef struct { + ngx_quic_secret_t client; + ngx_quic_secret_t server; +} ngx_quic_secrets_t; + + +struct ngx_quic_keys_s { + ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; + ngx_uint_t cipher; +}; + + +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; + + +typedef struct { + size_t out_len; + u_char *out; + + size_t prk_len; + const uint8_t *prk; + + size_t label_len; + const u_char *label; +} ngx_quic_hkdf_t; + +#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ + (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ + (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ + (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); + + +ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, + ngx_str_t *secret, ngx_log_t *log); +ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, + ngx_uint_t is_write, ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); +ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); +void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); +ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, + enum ssl_encryption_level_t level); +ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad, ngx_log_t *log); +ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, + ngx_log_t *log); + + +#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_socket.c @@ -0,0 +1,238 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +ngx_int_t +ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt) +{ + ngx_quic_socket_t *qsock, *tmp; + ngx_quic_client_id_t *cid; + + /* + * qc->path = NULL + * + * qc->nclient_ids = 0 + * qc->nsockets = 0 + * qc->max_retired_seqnum = 0 + * qc->client_seqnum = 0 + */ + + ngx_queue_init(&qc->sockets); + ngx_queue_init(&qc->free_sockets); + + ngx_queue_init(&qc->paths); + ngx_queue_init(&qc->free_paths); + + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->free_client_ids); + + qc->tp.original_dcid.len = pkt->odcid.len; + qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid); + if (qc->tp.original_dcid.data == NULL) { + return NGX_ERROR; + } + + /* socket to use for further processing (id auto-generated) */ + qsock = ngx_quic_create_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + /* socket is listening at new server id */ + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + qsock->used = 1; + + qc->tp.initial_scid.len = qsock->sid.len; + qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len); + if (qc->tp.initial_scid.data == NULL) { + goto failed; + } + ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len); + + /* for all packets except first, this is set at udp layer */ + c->udp = &qsock->udp; + + /* ngx_quic_get_connection(c) macro is now usable */ + + /* we have a client identified by scid */ + cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); + if (cid == NULL) { + goto failed; + } + + /* path of the first packet is our initial active path */ + qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid); + if (qc->path == NULL) { + goto failed; + } + + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + if (pkt->validated) { + qc->path->validated = 1; + qc->path->limited = 0; + } + + ngx_quic_path_dbg(c, "set active", qc->path); + + tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (tmp == NULL) { + goto failed; + } + + tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ + + ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len); + tmp->sid.len = pkt->odcid.len; + + if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + c->udp = NULL; + + return NGX_ERROR; +} + + +ngx_quic_socket_t * +ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_socket_t *sock; + + if (!ngx_queue_empty(&qc->free_sockets)) { + + q = ngx_queue_head(&qc->free_sockets); + sock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_queue_remove(&sock->queue); + + ngx_memzero(sock, sizeof(ngx_quic_socket_t)); + + } else { + + sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (sock == NULL) { + return NULL; + } + } + + sock->sid.len = NGX_QUIC_SERVER_CID_LEN; + if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) { + return NULL; + } + + sock->sid.seqnum = qc->server_seqnum++; + + return sock; +} + + +void +ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&qsock->queue); + ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + qc->nsockets--; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L closed nsock:%ui", + (int64_t) qsock->sid.seqnum, qc->nsockets); +} + + +ngx_int_t +ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock) +{ + ngx_str_t id; + ngx_quic_server_id_t *sid; + + sid = &qsock->sid; + + id.data = sid->id; + id.len = sid->len; + + 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); + + ngx_queue_insert_tail(&qc->sockets, &qsock->queue); + + qc->nsockets++; + qsock->quic = qc; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L listening at sid:%xV nsock:%ui", + (int64_t) sid->seqnum, &id, qc->nsockets); + + return NGX_OK; +} + + +void +ngx_quic_close_sockets(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (!ngx_queue_empty(&qc->sockets)) { + q = ngx_queue_head(&qc->sockets); + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_quic_close_socket(c, qsock); + } +} + + +ngx_quic_socket_t * +ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (qsock->sid.seqnum == seqnum) { + return qsock; + } + } + + return NULL; +} diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_socket.h @@ -0,0 +1,28 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); +void ngx_quic_close_sockets(ngx_connection_t *c); + +ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c, + ngx_quic_connection_t *qc); +ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock); +void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); + +ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); + + +#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -0,0 +1,600 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#if defined OPENSSL_IS_BORINGSSL \ + || defined LIBRESSL_VERSION_NUMBER \ + || NGX_QUIC_OPENSSL_COMPAT +#define NGX_QUIC_BORINGSSL_API 1 +#endif + + +/* + * RFC 9000, 7.5. Cryptographic Message Buffering + * + * Implementations MUST support buffering at least 4096 bytes of data + */ +#define NGX_QUIC_MAX_BUFFERED 65535 + + +#if (NGX_QUIC_BORINGSSL_API) +static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +#else +static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); +#endif + +static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); + + +#if (NGX_QUIC_BORINGSSL_API) + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_read_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, + cipher, rsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_write_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + +#else + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + const SSL_CIPHER *cipher; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_encryption_secrets() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + cipher = SSL_get_current_cipher(ssl_conn); + + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, + cipher, rsecret, secret_len) + != NGX_OK) + { + return 0; + } + + if (level == ssl_encryption_early_data) { + return 1; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + +#endif + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p, *end; + size_t client_params_len; + ngx_chain_t *out; + const uint8_t *client_params; + ngx_quic_tp_t ctp; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + unsigned int alpn_len; + const unsigned char *alpn_data; +#endif + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_add_handshake_data"); + + if (!qc->client_tp_done) { + /* + * things to do once during handshake: check ALPN and transport + * parameters; we want to break handshake if something is wrong + * here; + */ + +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + + if (alpn_len == 0) { + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } + +#endif + + SSL_get_peer_quic_transport_params(ssl_conn, &client_params, + &client_params_len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_get_peer_quic_transport_params():" + " params_len:%ui", client_params_len); + + if (client_params_len == 0) { + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); + qc->error_reason = "missing transport parameters"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "missing transport parameters"); + return 0; + } + + p = (u_char *) client_params; + end = p + client_params_len; + + /* defaults for parameters not sent by client */ + ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); + + if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; + + return 0; + } + + if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { + return 0; + } + + qc->client_tp_done = 1; + } + + ctx = ngx_quic_get_send_ctx(qc, level); + + out = ngx_quic_copy_buffer(c, (u_char *) data, len); + if (out == NGX_CHAIN_ERROR) { + return 0; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return 0; + } + + frame->data = out; + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = ctx->crypto_sent; + frame->u.crypto.length = len; + + ctx->crypto_sent += len; + + ngx_quic_queue_frame(qc, frame); + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ +#if (NGX_DEBUG) + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_flush_flight()"); +#endif + return 1; +} + + +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_alert() level:%s alert:%d", + ngx_quic_level_name(level), (int) alert); + + /* already closed on regular shutdown */ + + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "handshake failed"; + + return 1; +} + + +ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_chain_t *cl; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + f = &frame->u.crypto; + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) { + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } + + if (last <= ctx->crypto.offset) { + if (pkt->level == ssl_encryption_initial) { + /* speeding up handshake completion */ + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } + } + } + + return NGX_OK; + } + + if (f->offset == ctx->crypto.offset) { + if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_skip_buffer(c, &ctx->crypto, last); + + } else { + if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length, + f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + } + + cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); + + if (cl) { + if (ngx_quic_crypto_input(c, cl) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_free_chain(c, cl); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) +{ + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ssl_conn = c->ssl->connection; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + for (cl = data; cl; cl = cl->next) { + b = cl->buf; + + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + b->pos, b->last - b->pos)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + + return NGX_ERROR; + } + + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + } + + if (n <= 0 || SSL_in_init(ssl_conn)) { + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data) + && qc->client_tp_done) + { + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; + } + +#if (NGX_DEBUG) + ngx_ssl_handshake_log(c); +#endif + + c->ssl->handshaked = 1; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); + + if (qc->conf->retry) { + if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + /* + * RFC 9001, 9.5. Header Protection Timing Side Channels + * + * Generating next keys before a key update is received. + */ + + if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { + return NGX_ERROR; + } + + /* + * RFC 9001, 4.9.2. Discarding Handshake Keys + * + * An endpoint MUST discard its Handshake keys + * when the TLS handshake is confirmed. + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); + + /* start accepting clients on negotiated number of server ids */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c) +{ + u_char *p; + size_t clen; + ssize_t len; + ngx_str_t dcid; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + static SSL_QUIC_METHOD quic_method; + + qc = ngx_quic_get_connection(c); + + if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { + return NGX_ERROR; + } + + c->ssl->no_wait_shutdown = 1; + + ssl_conn = c->ssl->connection; + + if (!quic_method.send_alert) { +#if (NGX_QUIC_BORINGSSL_API) + quic_method.set_read_secret = ngx_quic_set_read_secret; + quic_method.set_write_secret = ngx_quic_set_write_secret; +#else + quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets; +#endif + quic_method.add_handshake_data = ngx_quic_add_handshake_data; + quic_method.flush_flight = ngx_quic_flush_flight; + quic_method.send_alert = ngx_quic_send_alert; + } + + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_method() failed"); + return NGX_ERROR; + } + +#ifdef OPENSSL_INFO_QUIC + if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { + SSL_set_quic_early_data_enabled(ssl_conn, 1); + } +#endif + + qsock = ngx_quic_get_socket(c); + + dcid.data = qsock->sid.id; + dcid.len = qsock->sid.len; + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + /* always succeeds */ + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); + if (len < 0) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic transport parameters len:%uz %*xs", len, len, p); +#endif + + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_early_data_context() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ssl.h b/src/event/quic/ngx_event_quic_ssl.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ssl.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_ + + +#include +#include + +ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); + +ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); + +#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_streams.c @@ -0,0 +1,1807 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_STREAM_GONE (void *) -1 + + +static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, + ngx_uint_t err); +static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); +static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c); +static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id); +static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); +static void ngx_quic_init_stream_handler(ngx_event_t *ev); +static void ngx_quic_init_streams_handler(ngx_connection_t *c); +static ngx_int_t ngx_quic_do_init_streams(ngx_connection_t *c); +static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, + uint64_t id); +static void ngx_quic_empty_handler(ngx_event_t *ev); +static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, + size_t size); +static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, + size_t size); +static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); +static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs); +static void ngx_quic_stream_cleanup_handler(void *data); +static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs); +static ngx_int_t ngx_quic_can_shutdown(ngx_connection_t *c); +static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last); +static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last); +static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs); +static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); +static void ngx_quic_set_event(ngx_event_t *ev); + + +ngx_connection_t * +ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) +{ + uint64_t id; + ngx_connection_t *pc, *sc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + pc = c->quic ? c->quic->parent : c; + qc = ngx_quic_get_connection(pc); + + if (qc->closing) { + return NULL; + } + + if (bidi) { + if (qc->streams.server_streams_bidi + >= qc->streams.server_max_streams_bidi) + { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many server bidi streams:%uL", + qc->streams.server_streams_bidi); + return NULL; + } + + id = (qc->streams.server_streams_bidi << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server bidi stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_bidi, + qc->streams.server_max_streams_bidi, id); + + qc->streams.server_streams_bidi++; + + } else { + if (qc->streams.server_streams_uni + >= qc->streams.server_max_streams_uni) + { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many server uni streams:%uL", + qc->streams.server_streams_uni); + return NULL; + } + + id = (qc->streams.server_streams_uni << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server uni stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_uni, + qc->streams.server_max_streams_uni, id); + + qc->streams.server_streams_uni++; + } + + qs = ngx_quic_create_stream(pc, id); + if (qs == NULL) { + return NULL; + } + + sc = qs->connection; + + sc->write->active = 1; + sc->write->ready = 1; + + if (bidi) { + sc->read->active = 1; + } + + return sc; +} + + +void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_t *qn, *qnt; + + for ( ;; ) { + qn = (ngx_quic_stream_t *) node; + qnt = (ngx_quic_stream_t *) temp; + + p = (qn->id < qnt->id) ? &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); +} + + +ngx_quic_stream_t * +ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) +{ + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_stream_t *qn; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + qn = (ngx_quic_stream_t *) node; + + if (id == qn->id) { + return qn; + } + + node = (id < qn->id) ? node->left : node->right; + } + + return NULL; +} + + +ngx_int_t +ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_pool_t *pool; + ngx_queue_t *q; + ngx_rbtree_t *tree; + ngx_connection_t *sc; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + + while (!ngx_queue_empty(&qc->streams.uninitialized)) { + q = ngx_queue_head(&qc->streams.uninitialized); + ngx_queue_remove(q); + + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + pool = qs->connection->pool; + + ngx_close_connection(qs->connection); + ngx_destroy_pool(pool); + } + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + sc = qs->connection; + + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + + if (sc == NULL) { + ngx_quic_close_stream(qs); + continue; + } + + sc->read->error = 1; + sc->write->error = 1; + + ngx_quic_set_event(sc->read); + ngx_quic_set_event(sc->write); + + sc->close = 1; + sc->read->handler(sc->read); + } + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has active streams"); + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) +{ + return ngx_quic_do_reset_stream(c->quic, err); +} + + +static ngx_int_t +ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + return NGX_OK; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + qs->send_final_size = qs->send_offset; + + if (qs->connection) { + qs->connection->write->error = 1; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL reset", qs->id); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = qs->id; + frame->u.reset_stream.error_code = err; + frame->u.reset_stream.final_size = qs->send_offset; + + ngx_quic_queue_frame(qc, frame); + + ngx_quic_free_buffer(pc, &qs->send); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_shutdown_stream(ngx_connection_t *c, int how) +{ + if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { + if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { + if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_shutdown_stream_send(ngx_connection_t *c) +{ + ngx_quic_stream_t *qs; + + qs = c->quic; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + return NGX_OK; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + qs->send_final_size = c->sent; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL send shutdown", qs->id); + + return ngx_quic_stream_flush(qs); +} + + +static ngx_int_t +ngx_quic_shutdown_stream_recv(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { + return NGX_OK; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->conf->stream_close_code == 0) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv shutdown", qs->id); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = qc->conf->stream_close_code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t min_id; + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + qs = ngx_quic_find_stream(&qc->streams.tree, id); + + if (qs) { + return qs; + } + + if (qc->shutdown || qc->closing) { + return NGX_QUIC_STREAM_GONE; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL is missing", id); + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (id >> 2) + 1; + + } else { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_bidi) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_bidi << 2); + qc->streams.client_streams_bidi = (id >> 2) + 1; + } + + /* + * RFC 9000, 2.1. Stream Types and Identifiers + * + * successive streams of each type are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being opened. + */ + +#if (NGX_SUPPRESS_WARN) + qs = NULL; +#endif + + for ( /* void */ ; min_id <= id; min_id += 0x04) { + + qs = ngx_quic_create_stream(c, min_id); + + if (qs == NULL) { + if (ngx_quic_reject_stream(c, min_id) != NGX_OK) { + return NULL; + } + + continue; + } + + ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); + + rev = qs->connection->read; + rev->handler = ngx_quic_init_stream_handler; + + if (qc->streams.initialized) { + ngx_post_event(rev, &ngx_posted_events); + + if (qc->push.posted) { + /* + * The posted stream can produce output immediately. + * By postponing the push event, we coalesce the stream + * output with queued frames in one UDP datagram. + */ + + ngx_delete_posted_event(&qc->push); + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + } + + if (qs == NULL) { + return NGX_QUIC_STREAM_GONE; + } + + return qs; +} + + +static ngx_int_t +ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t code; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + ? qc->conf->stream_reject_code_uni + : qc->conf->stream_reject_code_bidi; + + if (code == 0) { + return NGX_DECLINED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL reject err:0x%xL", id, code); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = id; + frame->u.reset_stream.error_code = code; + frame->u.reset_stream.final_size = 0; + + ngx_quic_queue_frame(qc, frame); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = id; + frame->u.stop_sending.error_code = code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static void +ngx_quic_init_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_quic_stream_t *qs; + + c = ev->data; + qs = c->quic; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); + + if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + c->write->active = 1; + c->write->ready = 1; + } + + c->read->active = 1; + + ngx_queue_remove(&qs->queue); + + c->listening->handler(c); +} + + +ngx_int_t +ngx_quic_init_streams(ngx_connection_t *c) +{ + ngx_int_t rc; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->streams.initialized) { + return NGX_OK; + } + + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_quic_init_streams_handler; + return NGX_OK; + } + + return ngx_quic_do_init_streams(c); +} + + +static void +ngx_quic_init_streams_handler(ngx_connection_t *c) +{ + if (ngx_quic_do_init_streams(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } +} + + +static ngx_int_t +ngx_quic_do_init_streams(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams"); + + qc = ngx_quic_get_connection(c); + + if (qc->conf->init) { + if (qc->conf->init(c) != NGX_OK) { + return NGX_ERROR; + } + } + + for (q = ngx_queue_head(&qc->streams.uninitialized); + q != ngx_queue_sentinel(&qc->streams.uninitialized); + q = ngx_queue_next(q)) + { + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_post_event(qs->connection->read, &ngx_posted_events); + } + + qc->streams.initialized = 1; + + return NGX_OK; +} + + +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; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL create", id); + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->streams.free)) { + q = ngx_queue_head(&qc->streams.free); + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_queue_remove(&qs->queue); + + } else { + /* + * the number of streams is limited by transport + * parameters and application requirements + */ + + qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t)); + if (qs == NULL) { + return NULL; + } + } + + ngx_memzero(qs, sizeof(ngx_quic_stream_t)); + + qs->node.key = id; + qs->parent = c; + qs->id = id; + qs->send_final_size = (uint64_t) -1; + qs->recv_final_size = (uint64_t) -1; + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + *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; + addr_text.data = NULL; + } + + reusable = c->reusable; + ngx_reusable_connection(c, 0); + + sc = ngx_get_connection(c->fd, log); + if (sc == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); + return NULL; + } + + qs->connection = sc; + + sc->quic = qs; + sc->shared = 1; + sc->type = SOCK_STREAM; + sc->pool = pool; + sc->ssl = c->ssl; + sc->sockaddr = sockaddr; + sc->socklen = c->socklen; + sc->listening = c->listening; + 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); + sc->start_time = c->start_time; + sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + sc->recv = ngx_quic_stream_recv; + sc->send = ngx_quic_stream_send; + sc->send_chain = ngx_quic_stream_send_chain; + + sc->read->log = log; + sc->write->log = log; + + sc->read->handler = ngx_quic_empty_handler; + sc->write->handler = ngx_quic_empty_handler; + + log->connection = sc->number; + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qs->send_max_data = qc->ctp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + + } else { + qs->recv_max_data = qc->tp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + } else { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; + + } else { + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + } + + qs->recv_window = qs->recv_max_data; + + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + ngx_close_connection(sc); + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); + return NULL; + } + + cln->handler = ngx_quic_stream_cleanup_handler; + cln->data = sc; + + ngx_rbtree_insert(&qc->streams.tree, &qs->node); + + return qs; +} + + +void +ngx_quic_cancelable_stream(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (!qs->cancelable) { + qs->cancelable = 1; + + if (ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + } + } + } +} + + +static void +ngx_quic_empty_handler(ngx_event_t *ev) +{ +} + + +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_chain_t *cl, *in; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + + qs = c->quic; + pc = qs->parent; + rev = c->read; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv buf:%uz", qs->id, size); + + if (size == 0) { + return 0; + } + + in = ngx_quic_read_buffer(pc, &qs->recv, size); + if (in == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = 0; + + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = ngx_cpymem(buf, b->pos, b->last - b->pos); + } + + ngx_quic_free_chain(pc, in); + + if (len == 0) { + rev->ready = 0; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD + && qs->recv_offset == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv() not ready", qs->id); + return NGX_AGAIN; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv len:%z", qs->id, len); + + if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) { + return NGX_ERROR; + } + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + ngx_buf_t b; + ngx_chain_t cl; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.memory = 1; + b.pos = buf; + b.last = buf + size; + + cl.buf = &b; + cl.next = NULL; + + if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + if (b.pos == buf) { + return NGX_AGAIN; + } + + return b.pos - buf; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + uint64_t n, flow; + ngx_event_t *wev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + wev = c->write; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + wev->error = 1; + return NGX_CHAIN_ERROR; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + + flow = qs->acked + qc->conf->stream_buffer_size - qs->sent; + + if (flow == 0) { + wev->ready = 0; + return in; + } + + if (limit == 0 || limit > (off_t) flow) { + limit = flow; + } + + n = qs->send.size; + + in = ngx_quic_write_buffer(pc, &qs->send, in, limit, qs->sent); + if (in == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + n = qs->send.size - n; + c->sent += n; + qs->sent += n; + qc->streams.sent += n; + + if (flow == n) { + wev->ready = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uL", n); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + return in; +} + + +static ngx_int_t +ngx_quic_stream_flush(ngx_quic_stream_t *qs) +{ + off_t limit, len; + ngx_uint_t last; + ngx_chain_t *out; + ngx_quic_frame_t *frame; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) { + return NGX_OK; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } + + limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset, + qs->send_max_data - qs->send_offset); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush limit:%O", qs->id, limit); + + len = qs->send.offset; + + out = ngx_quic_read_buffer(pc, &qs->send, limit); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = qs->send.offset - len; + last = 0; + + if (qs->send_final_size != (uint64_t) -1 + && qs->send_final_size == qs->send.offset) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; + last = 1; + } + + if (len == 0 && !last) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM; + frame->data = out; + + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = last; + + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = qs->send_offset; + frame->u.stream.length = len; + + ngx_quic_queue_frame(qc, frame); + + qs->send_offset += len; + qc->streams.send_offset += len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush len:%O last:%ui", + qs->id, len, last); + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + return NGX_OK; +} + + +static void +ngx_quic_stream_cleanup_handler(void *data) +{ + ngx_connection_t *c = data; + + ngx_quic_stream_t *qs; + + qs = c->quic; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL cleanup", qs->id); + + if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + qs->connection = NULL; + + if (ngx_quic_close_stream(qs) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } +} + + +static ngx_int_t +ngx_quic_close_stream(ngx_quic_stream_t *qs) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (!qc->closing) { + /* make sure everything is sent and final size is received */ + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD + && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + return NGX_OK; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL close", qs->id); + + ngx_quic_free_buffer(pc, &qs->send); + ngx_quic_free_buffer(pc, &qs->recv); + + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + + if (qc->closing) { + /* schedule handler call to continue ngx_quic_close_connection() */ + ngx_post_event(&qc->close, &ngx_posted_events); + return NGX_OK; + } + + if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + } + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + return NGX_OK; + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; + frame->u.max_streams.bidi = 0; + + } else { + frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; + frame->u.max_streams.bidi = 1; + } + + ngx_quic_queue_frame(qc, frame); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_can_shutdown(ngx_connection_t *c) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root != tree->sentinel) { + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + if (!qs->cancelable) { + return NGX_DECLINED; + } + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + + qc = ngx_quic_get_connection(c); + f = &frame->u.stream; + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + qs = ngx_quic_get_stream(c, f->stream_id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { + return NGX_OK; + } + + if (ngx_quic_control_flow(qs, last) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (last < qs->recv_offset) { + return NGX_OK; + } + + if (f->fin) { + if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last) + { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (qs->recv_last > last) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->recv_final_size = last; + qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; + } + + if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN + && qs->recv.size == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + if (f->offset <= qs->recv_offset) { + ngx_quic_set_event(qs->connection->read); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + tree = &qc->streams.tree; + + if (f->max_data <= qc->streams.send_max_data) { + return NGX_OK; + } + + if (tree->root == tree->sentinel + || qc->streams.send_offset < qc->streams.send_max_data) + { + /* not blocked on MAX_DATA */ + qc->streams.send_max_data = f->max_data; + return NGX_OK; + } + + qc->streams.send_max_data = f->max_data; + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node && qc->streams.send_offset < qc->streams.send_max_data) { + + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f) +{ + return ngx_quic_update_max_data(c); +} + + +ngx_int_t +ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + return ngx_quic_update_max_stream_data(qs); +} + + +ngx_int_t +ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (f->limit <= qs->send_max_data) { + return NGX_OK; + } + + if (qs->send_offset < qs->send_max_data) { + /* not blocked on MAX_STREAM_DATA */ + qs->send_max_data = f->limit; + return NGX_OK; + } + + qs->send_max_data = f->limit; + + return ngx_quic_stream_flush(qs); +} + + +ngx_int_t +ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) +{ + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + return NGX_OK; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + + if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->recv_final_size != (uint64_t) -1 + && qs->recv_final_size != f->final_size) + { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (qs->recv_last > f->final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->recv_final_size = f->final_size; + + if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + rev = qs->connection->read; + rev->error = 1; + + ngx_quic_set_event(rev); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + ngx_quic_set_event(qs->connection->write); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->bidi) { + if (qc->streams.server_max_streams_bidi < f->limit) { + qc->streams.server_max_streams_bidi = f->limit; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_bidi:%uL", f->limit); + } + + } else { + if (qc->streams.server_max_streams_uni < f->limit) { + qc->streams.server_max_streams_uni = f->limit; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_uni:%uL", f->limit); + } + } + + return NGX_OK; +} + + +void +ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + uint64_t acked; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + switch (f->type) { + + case NGX_QUIC_FT_RESET_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id); + if (qs == NULL) { + return; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack reset final_size:%uL", + qs->id, f->u.reset_stream.final_size); + + break; + + case NGX_QUIC_FT_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (qs == NULL) { + return; + } + + acked = qs->acked; + qs->acked += f->u.stream.length; + + if (f->u.stream.fin) { + qs->fin_acked = 1; + } + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT + && qs->acked == qs->sent && qs->fin_acked) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL", + qs->id, f->u.stream.length, f->u.stream.fin, + qs->sent - qs->acked); + + if (qs->connection + && qs->sent - acked == qc->conf->stream_buffer_size + && f->u.stream.length > 0) + { + ngx_quic_set_event(qs->connection->write); + } + + break; + + default: + return; + } + + if (qs->connection == NULL) { + ngx_quic_close_stream(qs); + } +} + + +static ngx_int_t +ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last) +{ + uint64_t len; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_last) { + return NGX_OK; + } + + len = last - qs->recv_last; + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL", + qs->id, last, qs->recv_max_data, qc->streams.recv_last + len, + qc->streams.recv_max_data); + + qs->recv_last += len; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV + && qs->recv_last > qs->recv_max_data) + { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + qc->streams.recv_last += len; + + if (qc->streams.recv_last > qc->streams.recv_max_data) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last) +{ + uint64_t len; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_offset) { + return NGX_OK; + } + + len = last - qs->recv_offset; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update %uL", qs->id, last); + + qs->recv_offset += len; + + if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { + if (ngx_quic_update_max_stream_data(qs) != NGX_OK) { + return NGX_ERROR; + } + } + + qc->streams.recv_offset += len; + + if (qc->streams.recv_max_data + <= qc->streams.recv_offset + qc->streams.recv_window / 2) + { + if (ngx_quic_update_max_data(pc) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs) +{ + uint64_t recv_max_data; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + recv_max_data = qs->recv_offset + qs->recv_window; + + if (qs->recv_max_data == recv_max_data) { + return NGX_OK; + } + + qs->recv_max_data = recv_max_data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update msd:%uL", + qs->id, qs->recv_max_data); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_max_data(ngx_connection_t *c) +{ + uint64_t recv_max_data; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + recv_max_data = qc->streams.recv_offset + qc->streams.recv_window; + + if (qc->streams.recv_max_data == recv_max_data) { + return NGX_OK; + } + + qc->streams.recv_max_data = recv_max_data; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update md:%uL", qc->streams.recv_max_data); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static void +ngx_quic_set_event(ngx_event_t *ev) +{ + ev->ready = 1; + + if (ev->active) { + ngx_post_event(ev, &ngx_posted_events); + } +} diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_streams.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +void ngx_quic_handle_stream_ack(ngx_connection_t *c, + ngx_quic_frame_t *f); +ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f); +ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); +ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); +ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); +ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f); + +ngx_int_t ngx_quic_init_streams(ngx_connection_t *c); +void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, + uint64_t id); +ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, + ngx_quic_connection_t *qc); + +#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -0,0 +1,289 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]); + + +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t exp, ngx_uint_t is_retry) +{ + int len, iv_len; + u_char *p, *iv; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + ngx_quic_address_hash(sockaddr, socklen, !is_retry, in); + + p = in + 20; + + p = ngx_cpymem(p, &exp, sizeof(time_t)); + + *p++ = is_retry ? 1 : 0; + + if (odcid) { + *p++ = odcid->len; + p = ngx_cpymem(p, odcid->data, odcid->len); + + } else { + *p++ = 0; + } + + len = p - in; + + cipher = EVP_aes_256_cbc(); + iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; + + if ((size_t) (iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) > token->len) + { + ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small"); + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic new token len:%uz %xV", token->len, token); +#endif + + return NGX_OK; +} + + +static void +ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]) +{ + size_t len; + u_char *data; + ngx_sha1_t sha1; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + len = (size_t) socklen; + data = (u_char *) sockaddr; + + if (no_port) { + switch (sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + + case AF_INET: + sin = (struct sockaddr_in *) sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + } + + ngx_sha1_init(&sha1); + ngx_sha1_update(&sha1, data, len); + ngx_sha1_final(buf, &sha1); +} + + +ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, u_char *key, + ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *iv, *p; + time_t now, exp; + size_t total; + ngx_str_t odcid; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char addr_hash[20]; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + +#if NGX_SUPPRESS_WARN + ngx_str_null(&odcid); +#endif + + /* Retry token or NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_cbc(); + iv = pkt->token.data; + iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) { + goto garbage; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { + goto garbage; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len; + + if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total = len; + + if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total += tlen; + + EVP_CIPHER_CTX_free(ctx); + + if (total < (20 + sizeof(time_t) + 2)) { + goto garbage; + } + + p = tdec + 20; + + ngx_memcpy(&exp, p, sizeof(time_t)); + p += sizeof(time_t); + + pkt->retried = (*p++ == 1); + + ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash); + + if (ngx_memcmp(tdec, addr_hash, 20) != 0) { + goto bad_token; + } + + odcid.len = *p++; + if (odcid.len) { + if (odcid.len > NGX_QUIC_MAX_CID_LEN) { + goto bad_token; + } + + if ((size_t)(tdec + total - p) < odcid.len) { + goto bad_token; + } + + odcid.data = p; + } + + now = ngx_time(); + + if (now > exp) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return NGX_DECLINED; + } + + if (odcid.len) { + pkt->odcid.len = odcid.len; + pkt->odcid.data = pkt->odcid_buf; + ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len); + + } else { + pkt->odcid = pkt->dcid; + } + + pkt->validated = 1; + + return NGX_OK; + +garbage: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); + + return NGX_ABORT; + +bad_token: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + + return NGX_DECLINED; +} diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -0,0 +1,35 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ + + +#include +#include + + +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + +/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */ +#define NGX_QUIC_AES_256_CBC_IV_LEN 16 +#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16 + +#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_CBC_IV_LEN \ + + NGX_QUIC_MAX_TOKEN_SIZE \ + + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) + + +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); +ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t expires, ngx_uint_t is_retry); +ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, + u_char *key, ngx_quic_header_t *pkt); + +#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_transport.c @@ -0,0 +1,2199 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 +#define NGX_QUIC_LONG_DCID_OFFSET 6 +#define NGX_QUIC_SHORT_DCID_OFFSET 1 + +#define NGX_QUIC_STREAM_FRAME_FIN 0x01 +#define NGX_QUIC_STREAM_FRAME_LEN 0x02 +#define NGX_QUIC_STREAM_FRAME_OFF 0x04 + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + +#define ngx_quic_write_uint64(p, s) \ + ((p)[0] = (u_char) ((s) >> 56), \ + (p)[1] = (u_char) ((s) >> 48), \ + (p)[2] = (u_char) ((s) >> 40), \ + (p)[3] = (u_char) ((s) >> 32), \ + (p)[4] = (u_char) ((s) >> 24), \ + (p)[5] = (u_char) ((s) >> 16), \ + (p)[6] = (u_char) ((s) >> 8), \ + (p)[7] = (u_char) (s), \ + (p) + sizeof(uint64_t)) + +#define ngx_quic_write_uint24(p, s) \ + ((p)[0] = (u_char) ((s) >> 16), \ + (p)[1] = (u_char) ((s) >> 8), \ + (p)[2] = (u_char) (s), \ + (p) + 3) + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define ngx_quic_build_int_set(p, value, len, bits) \ + (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6)) + + +static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); +static ngx_uint_t ngx_quic_varint_len(uint64_t value); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); +static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); +static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, + u_char **out); +static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, + u_char *dst); + +static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, + size_t dcid_len); +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_supported_version(uint32_t version); +static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); + +static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); +static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); + +static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, + ngx_uint_t frame_type); +static size_t ngx_quic_create_ping(u_char *p); +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, + ngx_chain_t *ranges); +static size_t ngx_quic_create_reset_stream(u_char *p, + ngx_quic_reset_stream_frame_t *rs); +static size_t ngx_quic_create_stop_sending(u_char *p, + ngx_quic_stop_sending_frame_t *ss); +static size_t ngx_quic_create_crypto(u_char *p, + ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); +static size_t ngx_quic_create_hs_done(u_char *p); +static size_t ngx_quic_create_new_token(u_char *p, + ngx_quic_new_token_frame_t *token, ngx_chain_t *data); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data); +static size_t ngx_quic_create_max_streams(u_char *p, + ngx_quic_max_streams_frame_t *ms); +static size_t ngx_quic_create_max_stream_data(u_char *p, + ngx_quic_max_stream_data_frame_t *ms); +static size_t ngx_quic_create_max_data(u_char *p, + ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_path_challenge(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_path_response(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_new_connection_id(u_char *p, + ngx_quic_new_conn_id_frame_t *rcid); +static size_t ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid); +static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f); + +static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, + uint16_t id, ngx_quic_tp_t *dst); + + +uint32_t ngx_quic_versions[] = { + /* QUICv1 */ + 0x00000001, +}; + +#define NGX_QUIC_NVERSIONS \ + (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) + + +static ngx_inline u_char * +ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + if (pos >= end) { + return NULL; + } + + p = pos; + len = 1 << (*p >> 6); + + value = *p++ & 0x3f; + + if ((size_t)(end - p) < (len - 1)) { + return NULL; + } + + while (--len) { + value = (value << 8) + *p++; + } + + *out = value; + + return p; +} + + +static ngx_inline u_char * +ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) +{ + if ((size_t)(end - pos) < 1) { + return NULL; + } + + *value = *pos; + + return pos + 1; +} + + +static ngx_inline u_char * +ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) +{ + if ((size_t)(end - pos) < sizeof(uint32_t)) { + return NULL; + } + + *value = ngx_quic_parse_uint32(pos); + + return pos + sizeof(uint32_t); +} + + +static ngx_inline u_char * +ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + *out = pos; + + return pos + len; +} + + +static u_char * +ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + ngx_memcpy(dst, pos, len); + + return pos + len; +} + + +static ngx_inline ngx_uint_t +ngx_quic_varint_len(uint64_t value) +{ + if (value < (1 << 6)) { + return 1; + } + + if (value < (1 << 14)) { + return 2; + } + + if (value < (1 << 30)) { + return 4; + } + + return 8; +} + + +static ngx_inline void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + + p = *pos; + + if (value < (1 << 6)) { + ngx_quic_build_int_set(p, value, 0, 0); + + } else if (value < (1 << 14)) { + ngx_quic_build_int_set(p, value, 1, 1); + ngx_quic_build_int_set(p, value, 0, 0); + + } else if (value < (1 << 30)) { + ngx_quic_build_int_set(p, value, 3, 2); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + + } else { + ngx_quic_build_int_set(p, value, 7, 3); + ngx_quic_build_int_set(p, value, 6, 0); + ngx_quic_build_int_set(p, value, 5, 0); + ngx_quic_build_int_set(p, value, 4, 0); + ngx_quic_build_int_set(p, value, 3, 0); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + } + + *pos = p; +} + + +ngx_int_t +ngx_quic_parse_packet(ngx_quic_header_t *pkt) +{ + if (!ngx_quic_long_pkt(pkt->flags)) { + pkt->level = ssl_encryption_application; + + if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; + } + + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (!ngx_quic_supported_version(pkt->version)) { + return NGX_ABORT; + } + + if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) +{ + u_char *p, *end; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx short flags:%xd", pkt->flags); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + pkt->dcid.len = dcid_len; + + p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_long_header(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint8_t idlen; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + p = ngx_quic_read_uint32(p, end, &pkt->version); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read version"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx long flags:%xd version:%xD", + pkt->flags, pkt->version); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet dcid is too long"); + return NGX_ERROR; + } + + pkt->dcid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet scid is too long"); + return NGX_ERROR; + } + + pkt->scid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_supported_version(uint32_t version) +{ + ngx_uint_t i; + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + if (ngx_quic_versions[i] == version) { + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint64_t varint; + + p = pkt->raw->pos; + end = pkt->raw->last; + + pkt->log->action = "parsing quic long header"; + + if (ngx_quic_pkt_in(pkt->flags)) { + + if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse token length"); + return NGX_ERROR; + } + + pkt->token.len = varint; + + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + + pkt->level = ssl_encryption_initial; + + } else if (ngx_quic_pkt_zrtt(pkt->flags)) { + pkt->level = ssl_encryption_early_data; + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + pkt->level = ssl_encryption_handshake; + + } else { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic bad packet type"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx %s len:%uL", + ngx_quic_level_name(pkt->level), varint); + + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", + ngx_quic_level_name(pkt->level)); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p + varint - pkt->data; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, + ngx_str_t *dcid) +{ + size_t len, offset; + + if (n == 0) { + goto failed; + } + + if (ngx_quic_long_pkt(*data)) { + if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { + goto failed; + } + + len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; + offset = NGX_QUIC_LONG_DCID_OFFSET; + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + offset = NGX_QUIC_SHORT_DCID_OFFSET; + } + + if (n < len + offset) { + goto failed; + } + + dcid->len = len; + dcid->data = &data[offset]; + + return NGX_OK; + +failed: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); + + return NGX_ERROR; +} + + +size_t +ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) +{ + u_char *p, *start; + ngx_uint_t i; + + p = start = out; + + *p++ = pkt->flags; + + /* + * The Version field of a Version Negotiation packet + * MUST be set to 0x00000000 + */ + p = ngx_quic_write_uint32(p, 0); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); + } + + return p - start; +} + + +/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ +size_t +ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) +{ + size_t len; + + if (ngx_quic_short_pkt(pkt->flags)) { + + len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN; + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; + } + + /* flags, version, dcid and scid with lengths and zero-length token */ + len = 5 + 2 + pkt->dcid.len + pkt->scid.len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + + if (len > pkt_len) { + return 0; + } + + /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ + len += ngx_quic_varint_len(pkt_len - len) + + pkt->num_len + EVP_GCM_TLS_TAG_LEN; + + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; +} + + +size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) +{ + return ngx_quic_short_pkt(pkt->flags) + ? ngx_quic_create_short_header(pkt, out, pnp) + : ngx_quic_create_long_header(pkt, out, pnp); +} + + +static size_t +ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp) +{ + size_t rem_len; + u_char *p, *start; + + rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN; + + if (out == NULL) { + return 5 + 2 + pkt->dcid.len + pkt->scid.len + + ngx_quic_varint_len(rem_len) + pkt->num_len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 0); + } + + ngx_quic_build_int(&p, rem_len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +static size_t +ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp) +{ + u_char *p, *start; + + if (out == NULL) { + return 1 + pkt->dcid.len + pkt->num_len; + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +size_t +ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start) +{ + u_char *p; + + p = out; + + *p++ = pkt->odcid.len; + p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); + + *start = p; + + *p++ = 0xff; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + p = ngx_cpymem(p, pkt->token.data, pkt->token.len); + + return p - out; +} + + +ssize_t +ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *f) +{ + u_char *p; + uint64_t varint; + ngx_buf_t *b; + ngx_uint_t i; + + b = f->data->buf; + + p = start; + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to obtain quic frame type"); + return NGX_ERROR; + } + + if (varint > NGX_QUIC_FT_LAST) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xL", varint); + return NGX_ERROR; + } + + f->type = varint; + + if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + + p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.crypto.length); + if (p == NULL) { + goto error; + } + + p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + break; + + case NGX_QUIC_FT_PADDING: + + while (p < end && *p == NGX_QUIC_FT_PADDING) { + p++; + } + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_quic_parse_int(p, end, &f->u.ack.largest); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.delay); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.range_count); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.first_range); + if (p == NULL) { + goto error; + } + + b->pos = p; + + /* process all ranges to get bounds, values are ignored */ + for (i = 0; i < f->u.ack.range_count; i++) { + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + } + + b->last = p; + + f->u.ack.ranges_length = b->last - b->pos; + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect0); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect1); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ce); + if (p == NULL) { + goto error; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.retire > f->u.ncid.seqnum) { + goto error; + } + + p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); + if (p == NULL) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + + p = ngx_quic_parse_int(p, end, &f->u.close.error_code); + if (p == NULL) { + goto error; + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); + if (p == NULL) { + goto error; + } + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + f->u.close.reason.len = varint; + + p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, + &f->u.close.reason.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0; + + p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); + if (p == NULL) { + goto error; + } + + if (f->type & NGX_QUIC_STREAM_FRAME_OFF) { + f->u.stream.off = 1; + + p = ngx_quic_parse_int(p, end, &f->u.stream.offset); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.off = 0; + f->u.stream.offset = 0; + } + + if (f->type & NGX_QUIC_STREAM_FRAME_LEN) { + f->u.stream.len = 1; + + p = ngx_quic_parse_int(p, end, &f->u.stream.length); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.len = 0; + f->u.stream.length = end - p; /* up to packet end */ + } + + p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + f->type = NGX_QUIC_FT_STREAM; + break; + + case NGX_QUIC_FT_MAX_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); + if (p == NULL) { + goto error; + } + + if (f->u.streams_blocked.limit > 0x1000000000000000) { + goto error; + } + + f->u.streams_blocked.bidi = + (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); + if (p == NULL) { + goto error; + } + + if (f->u.max_streams.limit > 0x1000000000000000) { + goto error; + } + + f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); + if (p == NULL) { + goto error; + } + + break; + + default: + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xi", f->type); + return NGX_ERROR; + } + + f->level = pkt->level; + + return p - start; + +error: + + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse frame type:0x%xi", f->type); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) +{ + uint8_t ptype; + + /* + * RFC 9000, 12.4. Frames and Frame Types: Table 3 + * + * Frame permissions per packet: 4 bits: IH01 + */ + static uint8_t ngx_quic_frame_masks[] = { + /* PADDING */ 0xF, + /* PING */ 0xF, + /* ACK */ 0xD, + /* ACK_ECN */ 0xD, + /* RESET_STREAM */ 0x3, + /* STOP_SENDING */ 0x3, + /* CRYPTO */ 0xD, + /* NEW_TOKEN */ 0x0, /* only sent by server */ + /* STREAM */ 0x3, + /* STREAM1 */ 0x3, + /* STREAM2 */ 0x3, + /* STREAM3 */ 0x3, + /* STREAM4 */ 0x3, + /* STREAM5 */ 0x3, + /* STREAM6 */ 0x3, + /* STREAM7 */ 0x3, + /* MAX_DATA */ 0x3, + /* MAX_STREAM_DATA */ 0x3, + /* MAX_STREAMS */ 0x3, + /* MAX_STREAMS2 */ 0x3, + /* DATA_BLOCKED */ 0x3, + /* STREAM_DATA_BLOCKED */ 0x3, + /* STREAMS_BLOCKED */ 0x3, + /* STREAMS_BLOCKED2 */ 0x3, + /* NEW_CONNECTION_ID */ 0x3, + /* RETIRE_CONNECTION_ID */ 0x3, + /* PATH_CHALLENGE */ 0x3, + /* PATH_RESPONSE */ 0x1, + /* CONNECTION_CLOSE */ 0xF, + /* CONNECTION_CLOSE2 */ 0x3, + /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + }; + + if (ngx_quic_long_pkt(pkt->flags)) { + + if (ngx_quic_pkt_in(pkt->flags)) { + ptype = 8; /* initial */ + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + ptype = 4; /* handshake */ + + } else { + ptype = 2; /* zero-rtt */ + } + + } else { + ptype = 1; /* application data */ + } + + if (ptype & ngx_quic_frame_masks[frame_type]) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic frame type 0x%xi is not " + "allowed in packet with flags 0x%xd", + frame_type, pkt->flags); + + return NGX_DECLINED; +} + + +ssize_t +ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, + uint64_t *gap, uint64_t *range) +{ + u_char *p; + + p = start; + + p = ngx_quic_parse_int(p, end, gap); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame gap"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, range); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame range"); + return NGX_ERROR; + } + + return p - start; +} + + +size_t +ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(gap); + len += ngx_quic_varint_len(range); + return len; + } + + start = p; + + ngx_quic_build_int(&p, gap); + ngx_quic_build_int(&p, range); + + return p - start; +} + + +ssize_t +ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) +{ + /* + * RFC 9002, 2. Conventions and Definitions + * + * Ack-eliciting frames: All frames other than ACK, PADDING, and + * CONNECTION_CLOSE are considered ack-eliciting. + */ + f->need_ack = 1; + + switch (f->type) { + case NGX_QUIC_FT_PING: + return ngx_quic_create_ping(p); + + case NGX_QUIC_FT_ACK: + f->need_ack = 0; + return ngx_quic_create_ack(p, &f->u.ack, f->data); + + case NGX_QUIC_FT_RESET_STREAM: + return ngx_quic_create_reset_stream(p, &f->u.reset_stream); + + case NGX_QUIC_FT_STOP_SENDING: + return ngx_quic_create_stop_sending(p, &f->u.stop_sending); + + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(p, &f->u.crypto, f->data); + + case NGX_QUIC_FT_HANDSHAKE_DONE: + return ngx_quic_create_hs_done(p); + + case NGX_QUIC_FT_NEW_TOKEN: + return ngx_quic_create_new_token(p, &f->u.token, f->data); + + case NGX_QUIC_FT_STREAM: + return ngx_quic_create_stream(p, &f->u.stream, f->data); + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + f->need_ack = 0; + return ngx_quic_create_close(p, f); + + case NGX_QUIC_FT_MAX_STREAMS: + return ngx_quic_create_max_streams(p, &f->u.max_streams); + + case NGX_QUIC_FT_MAX_STREAM_DATA: + return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); + + case NGX_QUIC_FT_MAX_DATA: + return ngx_quic_create_max_data(p, &f->u.max_data); + + case NGX_QUIC_FT_PATH_CHALLENGE: + return ngx_quic_create_path_challenge(p, &f->u.path_challenge); + + case NGX_QUIC_FT_PATH_RESPONSE: + return ngx_quic_create_path_response(p, &f->u.path_response); + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + return ngx_quic_create_new_connection_id(p, &f->u.ncid); + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } +} + + +static size_t +ngx_quic_create_ping(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_PING); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PING); + + return p - start; +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->largest); + len += ngx_quic_varint_len(ack->delay); + len += ngx_quic_varint_len(ack->range_count); + len += ngx_quic_varint_len(ack->first_range); + len += ack->ranges_length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->largest); + ngx_quic_build_int(&p, ack->delay); + ngx_quic_build_int(&p, ack->range_count); + ngx_quic_build_int(&p, ack->first_range); + + while (ranges) { + b = ranges->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + ranges = ranges->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM); + len += ngx_quic_varint_len(rs->id); + len += ngx_quic_varint_len(rs->error_code); + len += ngx_quic_varint_len(rs->final_size); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM); + ngx_quic_build_int(&p, rs->id); + ngx_quic_build_int(&p, rs->error_code); + ngx_quic_build_int(&p, rs->final_size); + + return p - start; +} + + +static size_t +ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); + len += ngx_quic_varint_len(ss->id); + len += ngx_quic_varint_len(ss->error_code); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); + ngx_quic_build_int(&p, ss->id); + ngx_quic_build_int(&p, ss->error_code); + + return p - start; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->length); + len += crypto->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, crypto->offset); + ngx_quic_build_int(&p, crypto->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_hs_done(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); + + return p - start; +} + + +static size_t +ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); + len += ngx_quic_varint_len(token->length); + len += token->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); + ngx_quic_build_int(&p, token->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data) +{ + size_t len; + u_char *start, type; + ngx_buf_t *b; + + type = NGX_QUIC_FT_STREAM; + + if (sf->off) { + type |= NGX_QUIC_STREAM_FRAME_OFF; + } + + if (sf->len) { + type |= NGX_QUIC_STREAM_FRAME_LEN; + } + + if (sf->fin) { + type |= NGX_QUIC_STREAM_FRAME_FIN; + } + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(sf->stream_id); + + if (sf->off) { + len += ngx_quic_varint_len(sf->offset); + } + + if (sf->len) { + len += ngx_quic_varint_len(sf->length); + } + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, sf->stream_id); + + if (sf->off) { + ngx_quic_build_int(&p, sf->offset); + } + + if (sf->len) { + ngx_quic_build_int(&p, sf->length); + } + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) +{ + size_t len; + u_char *start; + ngx_uint_t type; + + type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static ngx_int_t +ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, + ngx_quic_tp_t *dst) +{ + uint64_t varint; + ngx_str_t str; + + varint = 0; + ngx_str_null(&str); + + switch (id) { + + case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: + /* zero-length option */ + if (end - p != 0) { + return NGX_ERROR; + } + dst->disable_active_migration = 1; + return NGX_OK; + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + case NGX_QUIC_TP_INITIAL_MAX_DATA: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + case NGX_QUIC_TP_MAX_ACK_DELAY: + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + return NGX_ERROR; + } + break; + + case NGX_QUIC_TP_INITIAL_SCID: + + str.len = end - p; + str.data = p; + break; + + default: + return NGX_DECLINED; + } + + switch (id) { + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + dst->max_idle_timeout = varint; + break; + + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + dst->max_udp_payload_size = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_DATA: + dst->initial_max_data = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + dst->initial_max_stream_data_bidi_local = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + dst->initial_max_stream_data_bidi_remote = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + dst->initial_max_stream_data_uni = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + dst->initial_max_streams_bidi = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + dst->initial_max_streams_uni = varint; + break; + + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + dst->ack_delay_exponent = varint; + break; + + case NGX_QUIC_TP_MAX_ACK_DELAY: + dst->max_ack_delay = varint; + break; + + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + dst->active_connection_id_limit = varint; + break; + + case NGX_QUIC_TP_INITIAL_SCID: + dst->initial_scid = str; + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, + ngx_log_t *log) +{ + uint64_t id, len; + ngx_int_t rc; + + while (p < end) { + p = ngx_quic_parse_int(p, end, &id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse transport param id"); + return NGX_ERROR; + } + + switch (id) { + case NGX_QUIC_TP_ORIGINAL_DCID: + case NGX_QUIC_TP_PREFERRED_ADDRESS: + case NGX_QUIC_TP_RETRY_SCID: + case NGX_QUIC_TP_SR_TOKEN: + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic client sent forbidden transport param" + " id:0x%xL", id); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL length", id); + return NGX_ERROR; + } + + rc = ngx_quic_parse_transport_param(p, p + len, id, tp); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL data", id); + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic %s transport param id:0x%xL, skipped", + (id % 31 == 27) ? "reserved" : "unknown", id); + } + + p += len; + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic trailing garbage in" + " transport parameters: bytes:%ui", + end - p); + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic transport parameters parsed ok"); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp disable active migration: %ui", + tp->disable_active_migration); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", + tp->max_idle_timeout); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_udp_payload_size:%ui", + tp->max_udp_payload_size); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", + tp->initial_max_data); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_local:%ui", + tp->initial_max_stream_data_bidi_local); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_remote:%ui", + tp->initial_max_stream_data_bidi_remote); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_uni:%ui", + tp->initial_max_stream_data_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_bidi:%ui", + tp->initial_max_streams_bidi); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_uni:%ui", + tp->initial_max_streams_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp ack_delay_exponent:%ui", + tp->ack_delay_exponent); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", + tp->max_ack_delay); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp active_connection_id_limit:%ui", + tp->active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial source_connection_id len:%uz %xV", + tp->initial_scid.len, &tp->initial_scid); + + return NGX_OK; +} + + +static size_t +ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); + len += ngx_quic_varint_len(ms->id); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); + ngx_quic_build_int(&p, ms->id); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static size_t +ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); + len += ngx_quic_varint_len(md->max_data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); + ngx_quic_build_int(&p, md->max_data); + + return p - start; +} + + +static size_t +ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); + len += ngx_quic_varint_len(ncid->seqnum); + len += ngx_quic_varint_len(ncid->retire); + len++; + len += ncid->len; + len += NGX_QUIC_SR_TOKEN_LEN; + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); + ngx_quic_build_int(&p, ncid->seqnum); + ngx_quic_build_int(&p, ncid->retire); + *p++ = ncid->len; + p = ngx_cpymem(p, ncid->cid, ncid->len); + p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); + + return p - start; +} + + +static size_t +ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); + len += ngx_quic_varint_len(rcid->sequence_number); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); + ngx_quic_build_int(&p, rcid->sequence_number); + + return p - start; +} + + +ngx_int_t +ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) +{ + ngx_uint_t nstreams; + + ngx_memzero(tp, sizeof(ngx_quic_tp_t)); + + /* + * set by ngx_memzero(): + * + * tp->disable_active_migration = 0; + * tp->original_dcid = { 0, NULL }; + * tp->initial_scid = { 0, NULL }; + * tp->retry_scid = { 0, NULL }; + * tp->sr_token = { 0 } + * tp->sr_enabled = 0 + * tp->preferred_address = NULL + */ + + tp->max_idle_timeout = qcf->timeout; + + tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + + nstreams = qcf->max_concurrent_streams_bidi + + qcf->max_concurrent_streams_uni; + + tp->initial_max_data = nstreams * qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size; + tp->initial_max_stream_data_uni = qcf->stream_buffer_size; + + tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi; + tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni; + + tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + + tp->active_connection_id_limit = qcf->active_connection_id_limit; + tp->disable_active_migration = qcf->disable_active_migration; + + return NGX_OK; +} + + +ssize_t +ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, + size_t *clen) +{ + u_char *p; + size_t len; + +#define ngx_quic_tp_len(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value) \ + + ngx_quic_varint_len(ngx_quic_varint_len(value)) + +#define ngx_quic_tp_vint(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ + ngx_quic_build_int(&p, value); \ + } while (0) + +#define ngx_quic_tp_strlen(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value.len) \ + + value.len + +#define ngx_quic_tp_str(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, value.len); \ + p = ngx_cpymem(p, value.data, value.len); \ + } while (0) + + len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + + if (tp->disable_active_migration) { + len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + len += ngx_quic_varint_len(0); + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + /* transport parameters listed above will be saved in 0-RTT context */ + if (clen) { + *clen = len; + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); + + if (tp->retry_scid.len) { + len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } + + len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); + len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); + len += NGX_QUIC_SR_TOKEN_LEN; + + if (pos == NULL) { + return len; + } + + p = pos; + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, + tp->initial_max_data); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + + if (tp->disable_active_migration) { + ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + ngx_quic_build_int(&p, 0); + } + + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); + + if (tp->retry_scid.len) { + ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } + + ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); + ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); + p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); + + return p - pos; +} + + +static size_t +ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f) +{ + size_t len; + u_char *start; + ngx_quic_close_frame_t *cl; + + cl = &f->u.close; + + if (p == NULL) { + len = ngx_quic_varint_len(f->type); + len += ngx_quic_varint_len(cl->error_code); + + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { + len += ngx_quic_varint_len(cl->frame_type); + } + + len += ngx_quic_varint_len(cl->reason.len); + len += cl->reason.len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, f->type); + ngx_quic_build_int(&p, cl->error_code); + + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { + ngx_quic_build_int(&p, cl->frame_type); + } + + ngx_quic_build_int(&p, cl->reason.len); + p = ngx_cpymem(p, cl->reason.data, cl->reason.len); + + return p - start; +} + + +void +ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key) +{ + (void) ngx_quic_write_uint64(dcid, key); +} diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_transport.h @@ -0,0 +1,397 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ + + +#include +#include + + +/* + * RFC 9000, 17.2. Long Header Packets + * 17.3. Short Header Packets + * + * QUIC flags in first byte + */ +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_PKT_FIXED_BIT 0x40 +#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ +#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ + +#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) +#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) + +/* Long packet types */ +#define NGX_QUIC_PKT_INITIAL 0x00 +#define NGX_QUIC_PKT_ZRTT 0x10 +#define NGX_QUIC_PKT_HANDSHAKE 0x20 +#define NGX_QUIC_PKT_RETRY 0x30 + +#define ngx_quic_pkt_in(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) +#define ngx_quic_pkt_zrtt(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) +#define ngx_quic_pkt_hs(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) +#define ngx_quic_pkt_retry(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) + +#define ngx_quic_pkt_rb_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) +#define ngx_quic_pkt_hp_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) + +#define ngx_quic_level_name(lvl) \ + (lvl == ssl_encryption_application) ? "app" \ + : (lvl == ssl_encryption_initial) ? "init" \ + : (lvl == ssl_encryption_handshake) ? "hs" : "early" + +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN + +/* 12.4. Frames and Frame Types */ +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A +#define NGX_QUIC_FT_PATH_RESPONSE 0x1B +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C +#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E + +#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE + +/* 22.5. QUIC Transport Error Codes Registry */ +#define NGX_QUIC_ERR_NO_ERROR 0x00 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 +#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A +#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B +#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E +#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F +#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10 + +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 + +#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) + + +/* 22.3. QUIC Transport Parameters Registry */ +#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 +#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 +#define NGX_QUIC_TP_SR_TOKEN 0x02 +#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 +#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 +#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A +#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B +#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C +#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D +#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E +#define NGX_QUIC_TP_INITIAL_SCID 0x0F +#define NGX_QUIC_TP_RETRY_SCID 0x10 + +#define NGX_QUIC_CID_LEN_MIN 8 +#define NGX_QUIC_CID_LEN_MAX 20 + +#define NGX_QUIC_MAX_RANGES 10 + + +typedef struct { + uint64_t gap; + uint64_t range; +} ngx_quic_ack_range_t; + + +typedef struct { + uint64_t largest; + uint64_t delay; + uint64_t range_count; + uint64_t first_range; + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + uint64_t ranges_length; +} ngx_quic_ack_frame_t; + + +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint8_t len; + u_char cid[NGX_QUIC_CID_LEN_MAX]; + u_char srt[NGX_QUIC_SR_TOKEN_LEN]; +} ngx_quic_new_conn_id_frame_t; + + +typedef struct { + uint64_t length; +} ngx_quic_new_token_frame_t; + +/* + * common layout for CRYPTO and STREAM frames; + * conceptually, CRYPTO frame is also a stream + * frame lacking some properties + */ +typedef struct { + uint64_t offset; + uint64_t length; +} ngx_quic_ordered_frame_t; + +typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; + + +typedef struct { + /* initial fields same as in ngx_quic_ordered_frame_t */ + uint64_t offset; + uint64_t length; + + uint64_t stream_id; + unsigned off:1; + unsigned len:1; + unsigned fin:1; +} ngx_quic_stream_frame_t; + + +typedef struct { + uint64_t max_data; +} ngx_quic_max_data_frame_t; + + +typedef struct { + uint64_t error_code; + uint64_t frame_type; + ngx_str_t reason; +} ngx_quic_close_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; + uint64_t final_size; +} ngx_quic_reset_stream_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; +} ngx_quic_stop_sending_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_streams_blocked_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_max_streams_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_max_stream_data_frame_t; + + +typedef struct { + uint64_t limit; +} ngx_quic_data_blocked_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_stream_data_blocked_frame_t; + + +typedef struct { + uint64_t sequence_number; +} ngx_quic_retire_cid_frame_t; + + +typedef struct { + u_char data[8]; +} ngx_quic_path_challenge_frame_t; + + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + enum ssl_encryption_level_t level; + ngx_queue_t queue; + uint64_t pnum; + size_t plen; + ngx_msec_t first; + ngx_msec_t last; + ssize_t len; + unsigned need_ack:1; + unsigned pkt_need_ack:1; + unsigned flush:1; + + ngx_chain_t *data; + union { + ngx_quic_ack_frame_t ack; + ngx_quic_crypto_frame_t crypto; + ngx_quic_ordered_frame_t ord; + ngx_quic_new_conn_id_frame_t ncid; + ngx_quic_new_token_frame_t token; + ngx_quic_stream_frame_t stream; + ngx_quic_max_data_frame_t max_data; + ngx_quic_close_frame_t close; + ngx_quic_reset_stream_frame_t reset_stream; + ngx_quic_stop_sending_frame_t stop_sending; + ngx_quic_streams_blocked_frame_t streams_blocked; + ngx_quic_max_streams_frame_t max_streams; + ngx_quic_max_stream_data_frame_t max_stream_data; + ngx_quic_data_blocked_frame_t data_blocked; + ngx_quic_stream_data_blocked_frame_t stream_data_blocked; + ngx_quic_retire_cid_frame_t retire_cid; + ngx_quic_path_challenge_frame_t path_challenge; + ngx_quic_path_challenge_frame_t path_response; + } u; +}; + + +typedef struct { + ngx_log_t *log; + ngx_quic_path_t *path; + + ngx_quic_keys_t *keys; + + ngx_msec_t received; + uint64_t number; + uint8_t num_len; + uint32_t trunc; + uint8_t flags; + uint32_t version; + ngx_str_t token; + enum ssl_encryption_level_t level; + ngx_uint_t error; + + /* filled in by parser */ + ngx_buf_t *raw; /* udp datagram */ + + u_char *data; /* quic packet */ + size_t len; + + /* cleartext fields */ + ngx_str_t odcid; /* retry packet tag */ + u_char odcid_buf[NGX_QUIC_MAX_CID_LEN]; + ngx_str_t dcid; + ngx_str_t scid; + uint64_t pn; + u_char *plaintext; + ngx_str_t payload; /* decrypted data */ + + unsigned need_ack:1; + unsigned key_phase:1; + unsigned key_update:1; + unsigned parsed:1; + unsigned decrypted:1; + unsigned validated:1; + unsigned retried:1; + unsigned first:1; + unsigned rebound:1; +} ngx_quic_header_t; + + +typedef struct { + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + size_t max_udp_payload_size; + size_t initial_max_data; + size_t initial_max_stream_data_bidi_local; + size_t initial_max_stream_data_bidi_remote; + size_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t active_connection_id_limit; + ngx_flag_t disable_active_migration; + + ngx_str_t original_dcid; + ngx_str_t initial_scid; + ngx_str_t retry_scid; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + + /* TODO */ + void *preferred_address; +} ngx_quic_tp_t; + + +ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); + +size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); + +size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len); + +size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); + +size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start); + +ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *frame); +ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); + +ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, + u_char *end, uint64_t *gap, uint64_t *range); +size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); + +ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp, + ngx_quic_conf_t *qcf); +ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, ngx_log_t *log); +ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, size_t *clen); + +void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); + +#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c new file mode 100644 --- /dev/null +++ b/src/event/quic/ngx_event_quic_udp.c @@ -0,0 +1,420 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static void ngx_quic_close_accepted_connection(ngx_connection_t *c); +static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls, + ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); + + +void +ngx_quic_recvmsg(ngx_event_t *ev) +{ + ssize_t n; + ngx_str_t key; + ngx_buf_t buf; + ngx_log_t *log; + ngx_err_t err; + socklen_t socklen, local_socklen; + ngx_event_t *rev, *wev; + struct iovec iov[1]; + struct msghdr msg; + ngx_sockaddr_t sa, lsa; + struct sockaddr *sockaddr, *local_sockaddr; + ngx_listening_t *ls; + ngx_event_conf_t *ecf; + ngx_connection_t *c, *lc; + ngx_quic_socket_t *qsock; + 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))]; +#endif + + if (ev->timedout) { + if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { + return; + } + + ev->timedout = 0; + } + + ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module); + + if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) { + ev->available = ecf->multi_accept; + } + + lc = ev->data; + ls = lc->listening; + ev->ready = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic recvmsg on %V, ready: %d", + &ls->addr_text, ev->available); + + do { + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov[0].iov_base = (void *) buffer; + iov[0].iov_len = sizeof(buffer); + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(ngx_sockaddr_t); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (ls->wildcard) { + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); + + ngx_memzero(&msg_control, sizeof(msg_control)); + } +#endif + + n = recvmsg(lc->fd, &msg, 0); + + if (n == -1) { + err = ngx_socket_errno; + + if (err == NGX_EAGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err, + "quic recvmsg() not ready"); + return; + } + + ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed"); + + return; + } + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { + ngx_log_error(NGX_LOG_ALERT, ev->log, 0, + "quic recvmsg() truncated data"); + continue; + } +#endif + + sockaddr = msg.msg_name; + socklen = msg.msg_namelen; + + if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { + socklen = sizeof(ngx_sockaddr_t); + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + + if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + || saun->sun_path[0] == '\0') + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "unbound unix socket"); + goto next; + } + } + +#endif + + local_sockaddr = ls->sockaddr; + local_socklen = ls->socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + + if (ls->wildcard) { + struct cmsghdr *cmsg; + + ngx_memcpy(&lsa, local_sockaddr, local_socklen); + local_sockaddr = &lsa.sockaddr; + + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { + break; + } + } + } + +#endif + + if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { + goto next; + } + + c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen); + + if (c) { + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + ngx_log_handler_pt handler; + + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recvmsg: fd:%d n:%z", c->fd, n); + + c->log->handler = handler; + } +#endif + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = buffer; + buf.last = buffer + n; + buf.start = buf.pos; + buf.end = buffer + sizeof(buffer); + + qsock = ngx_quic_get_socket(c); + + ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); + qsock->socklen = socklen; + + c->udp->buffer = &buf; + + rev = c->read; + rev->ready = 1; + rev->active = 0; + + rev->handler(rev); + + if (c->udp) { + c->udp->buffer = NULL; + } + + rev->ready = 0; + rev->active = 1; + + goto next; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); +#endif + + ngx_accept_disabled = ngx_cycle->connection_n / 8 + - ngx_cycle->free_connection_n; + + c = ngx_get_connection(lc->fd, ev->log); + if (c == NULL) { + return; + } + + c->shared = 1; + c->type = SOCK_DGRAM; + c->socklen = socklen; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + c->pool = ngx_create_pool(ls->pool_size, ev->log); + if (c->pool == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); + if (c->sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(c->sockaddr, sockaddr, socklen); + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + *log = ls->log; + + c->log = log; + c->pool->log = log; + c->listening = ls; + + if (local_sockaddr == &lsa.sockaddr) { + local_sockaddr = ngx_palloc(c->pool, local_socklen); + if (local_sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(local_sockaddr, &lsa, local_socklen); + } + + c->local_sockaddr = local_sockaddr; + c->local_socklen = local_socklen; + + c->buffer = ngx_create_temp_buf(c->pool, n); + if (c->buffer == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n); + + rev = c->read; + wev = c->write; + + rev->active = 1; + wev->ready = 1; + + rev->log = log; + wev->log = log; + + /* + * TODO: MT: - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + * + * TODO: MP: - allocated in a shared memory + * - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + */ + + c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + c->start_time = ngx_current_msec; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); +#endif + + if (ls->addr_ntop) { + c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); + if (c->addr_text.data == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + ls->addr_text_max_len, 0); + if (c->addr_text.len == 0) { + ngx_quic_close_accepted_connection(c); + return; + } + } + +#if (NGX_DEBUG) + { + ngx_str_t addr; + u_char text[NGX_SOCKADDR_STRLEN]; + + ngx_debug_accepted_connection(ecf, c); + + if (log->log_level & NGX_LOG_DEBUG_EVENT) { + addr.data = text; + addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, + "*%uA quic recvmsg: %V fd:%d n:%z", + c->number, &addr, c->fd, n); + } + + } +#endif + + log->data = NULL; + log->handler = NULL; + + ls->handler(c); + + next: + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + ev->available -= n; + } + + } while (ev->available); +} + + +static void +ngx_quic_close_accepted_connection(ngx_connection_t *c) +{ + ngx_free_connection(c); + + c->fd = (ngx_socket_t) -1; + + if (c->pool) { + ngx_destroy_pool(c->pool); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif +} + + +static ngx_connection_t * +ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, + struct sockaddr *local_sockaddr, socklen_t local_socklen) +{ + uint32_t hash; + ngx_int_t rc; + ngx_connection_t *c; + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_socket_t *qsock; + + if (key->len == 0) { + return NULL; + } + + node = ls->rbtree.root; + sentinel = ls->rbtree.sentinel; + hash = ngx_crc32_long(key->data, key->len); + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + qsock = (ngx_quic_socket_t *) node; + + rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len); + + c = qsock->udp.connection; + + if (rc == 0 && ls->wildcard) { + rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, + c->local_sockaddr, c->local_socklen, 1); + } + + if (rc == 0) { + c->udp = &qsock->udp; + return c; + } + + node = (rc < 0) ? node->left : node->right; + } + + return NULL; +} 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 @@ -9,6 +9,10 @@ #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); @@ -52,6 +56,10 @@ static char *ngx_http_ssl_conf_command_c void *data); static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); +#if (NGX_QUIC_OPENSSL_COMPAT) +static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, + ngx_http_conf_addr_t *addr); +#endif static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = { @@ -419,16 +427,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { - unsigned int srvlen; - unsigned char *srv; + unsigned int srvlen; + unsigned char *srv; #if (NGX_DEBUG) - unsigned int i; + unsigned int i; #endif -#if (NGX_HTTP_V2) - ngx_http_connection_t *hc; +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) - ngx_connection_t *c; +#if (NGX_HTTP_V3) + ngx_http_v3_srv_conf_t *h3scf; +#endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) + ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); #endif @@ -441,14 +452,41 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) 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) { + + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (h3scf->enable && h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO + NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO) + - 1; + + } else if (h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; + + } else if (h3scf->enable) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; + + } else { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + } else +#endif { srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; @@ -1241,6 +1279,7 @@ static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { ngx_uint_t a, p, s; + const char *name; ngx_http_conf_addr_t *addr; ngx_http_conf_port_t *port; ngx_http_ssl_srv_conf_t *sscf; @@ -1290,22 +1329,44 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { - if (!addr[a].opt.ssl) { + if (!addr[a].opt.ssl && !addr[a].opt.quic) { continue; } + if (addr[a].opt.quic) { + name = "quic"; + +#if (NGX_QUIC_OPENSSL_COMPAT) + if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) { + return NGX_ERROR; + } +#endif + + } else { + name = "ssl"; + } + cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; if (sscf->certificates) { + + if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" must enable TLSv1.3 for " + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + continue; } if (!sscf->reject_handshake) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } @@ -1326,8 +1387,8 @@ ngx_http_ssl_init(ngx_conf_t *cf) ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } } @@ -1335,3 +1396,31 @@ ngx_http_ssl_init(ngx_conf_t *cf) return NGX_OK; } + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +static ngx_int_t +ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) +{ + ngx_uint_t s; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t **cscfp, *cscf; + + cscfp = addr->servers.elts; + for (s = 0; s < addr->servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + +#endif 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 @@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_ port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) + { continue; } @@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_ } port->family = sa->sa_family; + port->type = lsopt->type; port->port = p; port->addrs.elts = NULL; @@ -1237,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_V3) + ngx_uint_t quic; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1278,6 +1285,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n protocols |= lsopt->http2 << 2; protocols_prev |= addr[i].opt.http2 << 2; #endif +#if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; +#endif if (lsopt->set) { @@ -1365,6 +1375,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_V3) + addr[i].opt.quic = quic; +#endif return NGX_OK; } @@ -1831,6 +1844,7 @@ ngx_http_add_listening(ngx_conf_t *cf, n } #endif + ls->type = addr->opt.type; ls->backlog = addr->opt.backlog; ls->rcvbuf = addr->opt.rcvbuf; ls->sndbuf = addr->opt.sndbuf; @@ -1866,6 +1880,12 @@ ngx_http_add_listening(ngx_conf_t *cf, n ls->reuseport = addr->opt.reuseport; #endif + ls->wildcard = addr->opt.wildcard; + +#if (NGX_HTTP_V3) + ls->quic = addr->opt.quic; +#endif + return ls; } @@ -1898,6 +1918,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; +#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL @@ -1963,6 +1986,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_ #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; +#endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ng typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; +typedef struct ngx_http_v3_session_s ngx_http_v3_session_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_p #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_V3) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif @@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t void ngx_http_run_posted_requests(ngx_connection_t *c); ngx_int_t ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr); +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, + ngx_str_t *host); +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); @@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_r #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log); size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, 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 @@ -3005,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx lsopt.socklen = sizeof(struct sockaddr_in); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -3986,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -4184,6 +4186,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx #endif } + if (ngx_strcmp(value[n].data, "quic") == 0) { +#if (NGX_HTTP_V3) + lsopt.quic = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"quic\" parameter requires " + "ngx_http_v3_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[n].data[13], "on") == 0) { @@ -4285,6 +4300,28 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx return NGX_CONF_ERROR; } +#if (NGX_HTTP_V3) + + if (lsopt.quic) { +#if (NGX_HTTP_SSL) + if (lsopt.ssl) { + return "\"ssl\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HTTP_V2) + if (lsopt.http2) { + return "\"http2\" parameter is incompatible with \"quic\""; + } +#endif + + if (lsopt.proxy_protocol) { + return "\"proxy_protocol\" parameter is incompatible with \"quic\""; + } + } + +#endif + for (n = 0; n < u.naddrs; n++) { for (i = 0; i < n; i++) { 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,6 +75,7 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -86,6 +87,7 @@ typedef struct { int backlog; int rcvbuf; int sndbuf; + int type; #if (NGX_HAVE_SETFIB) int setfib; #endif @@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; + unsigned quic:1; unsigned proxy_protocol:1; }; @@ -266,6 +269,7 @@ typedef struct { typedef struct { ngx_int_t family; + ngx_int_t type; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ } ngx_http_conf_port_t; 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 @@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connec static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, - ngx_uint_t alloc); -static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, - ngx_str_t *host); static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c, ngx_http_virtual_names_t *virtual_names, ngx_str_t *host, ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp); @@ -50,7 +46,6 @@ static void ngx_http_keepalive_handler(n static void ngx_http_set_lingering_close(ngx_connection_t *c); static void ngx_http_lingering_close_handler(ngx_event_t *ev); static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); -static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); static void ngx_http_log_request(ngx_http_request_t *r); static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); @@ -329,6 +324,13 @@ ngx_http_init_connection(ngx_connection_ } #endif +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + ngx_http_v3_init_stream(c); + return; + } +#endif + #if (NGX_HTTP_SSL) { ngx_http_ssl_srv_conf_t *sscf; @@ -950,6 +952,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t * #ifdef SSL_OP_NO_RENEGOTIATION SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); #endif + +#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT +#if (NGX_HTTP_V3) + if (c->listening->quic) { + SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + } +#endif +#endif } done: @@ -2095,7 +2105,7 @@ ngx_http_process_request(ngx_http_reques } -static ngx_int_t +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) { u_char *h, ch; @@ -2187,7 +2197,7 @@ ngx_http_validate_host(ngx_str_t *host, } -static ngx_int_t +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host) { ngx_int_t rc; @@ -2710,6 +2720,13 @@ ngx_http_finalize_connection(ngx_http_re } #endif +#if (NGX_HTTP_V3) + if (r->connection->quic) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { @@ -2925,6 +2942,20 @@ ngx_http_test_reading(ngx_http_request_t #endif +#if (NGX_HTTP_V3) + + if (c->quic) { + if (rev->error) { + c->error = 1; + err = 0; + goto closed; + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -3590,7 +3621,7 @@ ngx_http_post_action(ngx_http_request_t } -static void +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_connection_t *c; @@ -3677,7 +3708,12 @@ ngx_http_free_request(ngx_http_request_t log->action = "closing request"; - if (r->connection->timedout) { + if (r->connection->timedout +#if (NGX_HTTP_V3) + && r->connection->quic == NULL +#endif + ) + { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->reset_timedout_connection) { @@ -3750,6 +3786,12 @@ ngx_http_close_connection(ngx_connection #endif +#if (NGX_HTTP_V3) + if (c->quic) { + ngx_http_v3_reset_stream(c); + } +#endif + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, -1); #endif diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_30 3000 #define NGX_HTTP_UNKNOWN 0x00000001 #define NGX_HTTP_GET 0x00000002 @@ -323,6 +324,10 @@ typedef struct { #endif #endif +#if (NGX_HTTP_V3 || NGX_COMPAT) + ngx_http_v3_session_t *v3_session; +#endif + ngx_chain_t *busy; ngx_int_t nbusy; @@ -451,6 +456,7 @@ struct ngx_http_request_s { ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_parse_t *v3_parse; ngx_http_log_handler_pt log_handler; @@ -543,6 +549,7 @@ struct ngx_http_request_s { unsigned request_complete:1; unsigned request_output:1; unsigned header_sent:1; + unsigned response_sent:1; unsigned expect_tested:1; unsigned root_tested:1; unsigned done:1; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_ht } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_request_body(r); + goto done; + } +#endif + preread = r->header_in->last - r->header_in->pos; if (preread) { @@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ng } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_unbuffered_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; + } +#endif + if (r->connection->read->timedout) { r->connection->timedout = 1; return NGX_HTTP_REQUEST_TIME_OUT; @@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_r } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -921,6 +946,9 @@ ngx_http_test_expect(ngx_http_request_t #if (NGX_HTTP_V2) || r->stream != NULL #endif +#if (NGX_HTTP_V3) + || r->connection->quic != NULL +#endif ) { return NGX_OK; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_ } #endif +#if (NGX_HTTP_V3) + if (c->quic) { + ngx_http_upstream_init_request(r); + return; + } +#endif + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connectio } #endif +#if (NGX_HTTP_V3) + + if (c->quic) { + if (c->write->error) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { diff --git a/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c --- a/src/http/ngx_http_write_filter_module.c +++ b/src/http/ngx_http_write_filter_module.c @@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t r->out = NULL; c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + return NGX_OK; } @@ -346,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3.c @@ -0,0 +1,109 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); +static void ngx_http_v3_cleanup_session(void *data); + + +ngx_int_t +ngx_http_v3_init_session(ngx_connection_t *c) +{ + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t)); + if (h3c == NULL) { + goto failed; + } + + ngx_queue_init(&h3c->blocked); + + h3c->keepalive.log = c->log; + h3c->keepalive.data = c; + h3c->keepalive.handler = ngx_http_v3_keepalive_handler; + + h3c->table.send_insert_count.log = c->log; + h3c->table.send_insert_count.data = c; + h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_v3_cleanup_session; + cln->data = h3c; + + hc->v3_session = h3c; + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); + return NGX_ERROR; +} + + +static void +ngx_http_v3_keepalive_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); +} + + +static void +ngx_http_v3_cleanup_session(void *data) +{ + ngx_http_v3_session_t *h3c = data; + + ngx_http_v3_cleanup_table(h3c); + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + if (h3c->table.send_insert_count.posted) { + ngx_delete_posted_event(&h3c->table.send_insert_count); + } +} + + +ngx_int_t +ngx_http_v3_check_flood(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "HTTP/3 flood detected"); + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3.h @@ -0,0 +1,157 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include +#include +#include + +#include +#include +#include +#include + + +#define NGX_HTTP_V3_ALPN_PROTO "\x02h3" +#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" +#define NGX_HTTP_V3_HQ_PROTO "hq-interop" + +#define NGX_HTTP_V3_VARLEN_INT_LEN 4 +#define NGX_HTTP_V3_PREFIX_INT_LEN 11 + +#define NGX_HTTP_V3_STREAM_CONTROL 0x00 +#define NGX_HTTP_V3_STREAM_PUSH 0x01 +#define NGX_HTTP_V3_STREAM_ENCODER 0x02 +#define NGX_HTTP_V3_STREAM_DECODER 0x03 + +#define NGX_HTTP_V3_FRAME_DATA 0x00 +#define NGX_HTTP_V3_FRAME_HEADERS 0x01 +#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 +#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 +#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 +#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 +#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d + +#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01 +#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06 +#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 + +#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096 + +#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0 +#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1 +#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2 +#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3 +#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4 +#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 +#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 +#define NGX_HTTP_V3_MAX_UNI_STREAMS 3 + +/* HTTP/3 errors */ +#define NGX_HTTP_V3_ERR_NO_ERROR 0x100 +#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101 +#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102 +#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103 +#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104 +#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105 +#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106 +#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107 +#define NGX_HTTP_V3_ERR_ID_ERROR 0x108 +#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109 +#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a +#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b +#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c +#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d +#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f +#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110 + +/* QPACK errors */ +#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200 +#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201 +#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 + + +#define ngx_http_quic_get_connection(c) \ + ((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \ + : (c)->data)) + +#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, \ + 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, \ + module) + +#define ngx_http_v3_finalize_connection(c, code, reason) \ + ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) + +#define ngx_http_v3_shutdown_connection(c, code, reason) \ + ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) + + +typedef struct { + ngx_flag_t enable; + ngx_flag_t enable_hq; + size_t max_table_capacity; + ngx_uint_t max_blocked_streams; + ngx_uint_t max_concurrent_streams; + ngx_quic_conf_t quic; +} ngx_http_v3_srv_conf_t; + + +struct ngx_http_v3_parse_s { + size_t header_limit; + ngx_http_v3_parse_headers_t headers; + ngx_http_v3_parse_data_t body; + ngx_array_t *cookies; +}; + + +struct ngx_http_v3_session_s { + ngx_http_v3_dynamic_table_t table; + + ngx_event_t keepalive; + ngx_uint_t nrequests; + + ngx_queue_t blocked; + ngx_uint_t nblocked; + + uint64_t next_request_id; + + off_t total_bytes; + off_t payload_bytes; + + unsigned goaway:1; + unsigned hq:1; + + ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; +}; + + +void ngx_http_v3_init_stream(ngx_connection_t *c); +void ngx_http_v3_reset_stream(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); +ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init(ngx_connection_t *c); +void ngx_http_v3_shutdown(ngx_connection_t *c); + +ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); +ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_encode.c @@ -0,0 +1,304 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +uintptr_t +ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) +{ + if (value <= 63) { + if (p == NULL) { + return 1; + } + + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 16383) { + if (p == NULL) { + return 2; + } + + *p++ = 0x40 | (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 1073741823) { + if (p == NULL) { + return 4; + } + + *p++ = 0x80 | (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (p == NULL) { + return 8; + } + + *p++ = 0xc0 | (value >> 56); + *p++ = (value >> 48); + *p++ = (value >> 40); + *p++ = (value >> 32); + *p++ = (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) +{ + ngx_uint_t thresh, n; + + thresh = (1 << prefix) - 1; + + if (value < thresh) { + if (p == NULL) { + return 1; + } + + *p++ |= value; + return (uintptr_t) p; + } + + value -= thresh; + + if (p == NULL) { + for (n = 2; value >= 128; n++) { + value >>= 7; + } + + return n; + } + + *p++ |= thresh; + + while (value >= 128) { + *p++ = 0x80 | value; + value >>= 7; + } + + *p++ = value; + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count, + ngx_uint_t sign, ngx_uint_t delta_base) +{ + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) + + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); + + *p = sign ? 0x80 : 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) +{ + /* Indexed Field Line */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 6); + } + + *p = dynamic ? 0x80 : 0xc0; + + return ngx_http_v3_encode_prefix_int(p, index, 6); +} + + +uintptr_t +ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, + u_char *data, size_t len) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = dynamic ? 0x40 : 0x50; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p2 = p; + hlen = ngx_http_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Literal Name */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, name->len, 3) + + name->len + + ngx_http_v3_encode_prefix_int(NULL, value->len, 7) + + value->len; + } + + p1 = p; + *p = 0x20; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); + + p2 = p; + hlen = ngx_http_huff_encode(name->data, name->len, p, 1); + + if (hlen) { + p = p1; + *p = 0x28; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + ngx_strlow(p, name->data, name->len); + p += name->len; + } + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + + p2 = p; + hlen = ngx_http_huff_encode(value->data, value->len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, value->data, value->len); + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index) +{ + /* Indexed Field Line With Post-Base Index */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4); + } + + *p = 0x10; + + return ngx_http_v3_encode_prefix_int(p, index, 4); +} + + +uintptr_t +ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, + size_t len) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Post-Base Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 3) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p2 = p; + hlen = ngx_http_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } + } + + return (uintptr_t) p; +} diff --git a/src/http/v3/ngx_http_v3_encode.h b/src/http/v3/ngx_http_v3_encode.h new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_encode.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_ +#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_ + + +#include +#include +#include + + +uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); +uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, + ngx_uint_t prefix); + +uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p, + ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); +uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index); +uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index, u_char *data, size_t len); +uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, + ngx_str_t *value); +uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index); +uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, + u_char *data, size_t len); + + +#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -0,0 +1,851 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* static table indices */ +#define NGX_HTTP_V3_HEADER_AUTHORITY 0 +#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 +#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 +#define NGX_HTTP_V3_HEADER_DATE 6 +#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 +#define NGX_HTTP_V3_HEADER_LOCATION 12 +#define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 +#define NGX_HTTP_V3_HEADER_SERVER 92 +#define NGX_HTTP_V3_HEADER_USER_AGENT 95 + + +typedef struct { + ngx_chain_t *free; + ngx_chain_t *busy; +} ngx_http_v3_filter_ctx_t; + + +static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +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, + ngx_http_v3_filter_ctx_t *ctx); +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_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_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + u_char *p; + size_t len, n; + ngx_buf_t *b; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_chain_t *out, *hl, *cl, **ll; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_header_filter(r); + } + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(r->connection); + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + if (r->headers_out.last_modified_time != -1) { + if (r->headers_out.status != NGX_HTTP_OK + && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT + && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) + { + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + } + + if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { + r->header_only = 1; + ngx_str_null(&r->headers_out.content_type); + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + } + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + r->header_only = 1; + } + + c = r->connection; + + out = NULL; + ll = &out; + + len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + n = sizeof("nginx") - 1; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); + } + + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, NGX_OFF_T_LEN); + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); + p = ngx_cpymem(p, host.data, host.len); + + if (port) { + p = ngx_sprintf(p, ":%ui", port); + } + + p = ngx_cpymem(p, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = p - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \":status: %03ui\"", + r->headers_out.status); + + if (r->headers_out.status == NGX_HTTP_OK) { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + p = (u_char *) NGINX_VER; + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + p = (u_char *) NGINX_VER_BUILD; + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + p = (u_char *) "nginx"; + n = sizeof("nginx") - 1; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"server: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); + } + + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_DATE, + ngx_cached_http_time.data, + ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); + + p = ngx_cpymem(p, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = n; + r->headers_out.content_type.data = p - n; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + r->headers_out.content_type.data, + r->headers_out.content_type.len); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-length: %O\"", + r->headers_out.content_length_n); + + if (r->headers_out.content_length_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", + r->headers_out.content_length_n); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_http_time(p, r->headers_out.last_modified_time); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"last-modified: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, + p, n); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + r->headers_out.location->value.data, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + if (r->header_only) { + b->last_buf = 1; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + 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_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + *ll = hl; + ll = &cl->next; + + if (r->headers_out.content_length_n >= 0 + && !r->header_only && !r->expect_trailers) + { + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + r->headers_out.content_length_n); + + h3c->payload_bytes += r->headers_out.content_length_n; + h3c->total_bytes += r->headers_out.content_length_n; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + *ll = cl; + + } else { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + + return ngx_http_write_filter(r, out); +} + + +static ngx_int_t +ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *chunk; + off_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); + if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + + h3c = ngx_http_v3_get_session(r->connection); + + out = NULL; + ll = &out; + + size = 0; + cl = in; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 chunk: %O", ngx_buf_size(cl->buf)); + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush + || cl->buf->sync + || ngx_buf_in_memory(cl->buf) + || cl->buf->in_file) + { + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + if (cl->next == NULL) { + break; + } + + cl = cl->next; + } + + if (size) { + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + chunk = b->start; + + if (chunk == NULL) { + chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (chunk == NULL) { + return NGX_ERROR; + } + + b->start = chunk; + b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = chunk; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + tl->next = out; + out = tl; + + h3c->payload_bytes += size; + } + + if (cl->buf->last_buf) { + tl = ngx_http_v3_create_trailers(r, ctx); + if (tl == NULL) { + return NGX_ERROR; + } + + cl->buf->last_buf = 0; + + *ll = tl; + + } else { + *ll = NULL; + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + + rc = ngx_http_next_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_filter_module); + + return rc; +} + + +static ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx) +{ + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); + + len = 0; + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NULL; + } + + b = cl->buf; + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->last_buf = 1; + + if (len == 0) { + b->temporary = 0; + b->pos = b->last = NULL; + return cl; + } + + b->temporary = 1; + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + b->pos = ngx_palloc(r->pool, len); + if (b->pos == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, + 0, 0, 0); + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output trailer: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (hl == NULL) { + return NULL; + } + + b = hl->buf; + p = b->start; + + if (p == NULL) { + p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (p == NULL) { + return NULL; + } + + b->start = p; + b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = p; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl->next = cl; + + return hl; +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_v3_body_filter; + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_module.c @@ -0,0 +1,389 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + + +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); +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_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("http3"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable), + NULL }, + + { ngx_string("http3_hq"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable_hq), + NULL }, + + { ngx_string("http3_max_concurrent_streams"), + 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_streams), + NULL }, + + { ngx_string("http3_stream_buffer_size"), + 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.stream_buffer_size), + NULL }, + + { ngx_string("quic_retry"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.retry), + NULL }, + + { ngx_string("quic_gso"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), + NULL }, + + { ngx_string("quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_quic_host_key, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + 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, quic.active_connection_id_limit), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_module_ctx = { + ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* module directives */ + NGX_HTTP_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_http_variable_t ngx_http_v3_vars[] = { + + { ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 }, + + ngx_http_null_variable +}; + +static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); + + +static ngx_int_t +ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_v3_session_t *h3c; + + if (r->connection->quic) { + h3c = ngx_http_v3_get_session(r->connection); + + if (h3c->hq) { + v->len = sizeof("hq") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "hq"; + + return NGX_OK; + } + + v->len = sizeof("h3") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h3"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_vars; v->name.len; v++) { + var = ngx_http_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_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * h3scf->quic.host_key = { 0, NULL } + * h3scf->quic.stream_reject_code_uni = 0; + * h3scf->quic.disable_active_migration = 0; + * h3scf->quic.timeout = 0; + * h3scf->max_blocked_streams = 0; + */ + + h3scf->enable = NGX_CONF_UNSET; + h3scf->enable_hq = NGX_CONF_UNSET; + h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; + h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; + + 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; + h3scf->quic.retry = NGX_CONF_UNSET; + h3scf->quic.gso_enabled = NGX_CONF_UNSET; + h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; + h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + h3scf->quic.init = ngx_http_v3_init; + h3scf->quic.shutdown = ngx_http_v3_shutdown; + + return h3scf; +} + + +static char * +ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_srv_conf_t *prev = parent; + ngx_http_v3_srv_conf_t *conf = child; + + ngx_http_ssl_srv_conf_t *sscf; + + ngx_conf_merge_value(conf->enable, prev->enable, 1); + + ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); + + ngx_conf_merge_uint_value(conf->max_concurrent_streams, + prev->max_concurrent_streams, 128); + + conf->max_blocked_streams = conf->max_concurrent_streams; + + ngx_conf_merge_size_value(conf->quic.stream_buffer_size, + prev->quic.stream_buffer_size, + 65536); + + conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams; + + ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); + ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); + + ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); + + ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, + prev->quic.active_connection_id_limit, + 2); + + if (conf->quic.host_key.len == 0) { + + conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->quic.host_key.data = ngx_palloc(cf->pool, + conf->quic.host_key.len); + if (conf->quic.host_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->quic.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->quic.host_key, &ngx_http_quic_salt, + conf->quic.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->quic.host_key, &ngx_http_quic_salt, + conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + conf->quic.ssl = &sscf->ssl; + + 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; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + ngx_quic_conf_t *qcf; + + qcf = &h3scf->quic; + + 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/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_parse.c @@ -0,0 +1,1931 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_http_v3_is_v2_frame(type) \ + ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) + + +static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t n); +static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t *n); +static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length); + +static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, + ngx_http_v3_parse_literal_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, + ngx_http_v3_parse_control_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, + ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, + ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c, + ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value); + + +static void +ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n) +{ + *loc = *b; + + if ((size_t) (loc->last - loc->pos) > n) { + loc->last = loc->pos + n; + } +} + + +static void +ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn) +{ + *pn -= loc->pos - b->pos; + b->pos = loc->pos; +} + + +static ngx_int_t +ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length) +{ + if ((size_t) (b->last - b->pos) < *length) { + *length -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += *length; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b) +{ + u_char ch; + enum { + sw_start = 0, + sw_length_2, + sw_length_3, + sw_length_4, + sw_length_5, + sw_length_6, + sw_length_7, + sw_length_8 + }; + + for ( ;; ) { + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + switch (st->state) { + + case sw_start: + + st->value = ch; + if (st->value & 0xc0) { + st->state = sw_length_2; + break; + } + + goto done; + + case sw_length_2: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc000) == 0x4000) { + st->value &= 0x3fff; + goto done; + } + + st->state = sw_length_3; + break; + + case sw_length_4: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc0000000) == 0x80000000) { + st->value &= 0x3fffffff; + goto done; + } + + st->state = sw_length_5; + break; + + case sw_length_3: + case sw_length_5: + case sw_length_6: + case sw_length_7: + + st->value = (st->value << 8) + ch; + st->state++; + break; + + case sw_length_8: + + st->value = (st->value << 8) + ch; + st->value &= 0x3fffffffffffffff; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse varlen int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_prefix_int(ngx_connection_t *c, + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b) +{ + u_char ch; + ngx_uint_t mask; + enum { + sw_start = 0, + sw_value + }; + + for ( ;; ) { + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + switch (st->state) { + + case sw_start: + + mask = (1 << prefix) - 1; + st->value = ch & mask; + + if (st->value != mask) { + goto done; + } + + st->shift = 0; + st->state = sw_value; + break; + + case sw_value: + + st->value += (uint64_t) (ch & 0x7f) << st->shift; + + if (st->shift == 56 + && ((ch & 0x80) || (st->value & 0xc000000000000000))) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded integer size limit"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + + if (ch & 0x80) { + st->shift += 7; + break; + } + + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse prefix int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, + ngx_buf_t *b) +{ + ngx_buf_t loc; + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_skip, + sw_prefix, + sw_verify, + sw_field_rep, + sw_done + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->length = st->vlint.value; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { + st->state = st->length > 0 ? sw_skip : sw_type; + break; + } + + if (st->length == 0) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + st->state = sw_prefix; + break; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + + case sw_prefix: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &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; + } + + st->state = sw_verify; + break; + + case sw_verify: + + rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_field_rep; + + /* fall through */ + + case sw_field_rep: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, + &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; + } + + if (st->length == 0) { + goto done; + } + + return NGX_OK; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); + + if (st->prefix.insert_count > 0) { + if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_req_insert_count, + sw_delta_base, + sw_read_delta_base + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix"); + + st->state = sw_req_insert_count; + + /* fall through */ + + case sw_req_insert_count: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b); + if (rc != NGX_DONE) { + return rc; + } + + st->insert_count = st->pint.value; + st->state = sw_delta_base; + break; + + case sw_delta_base: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->sign = (ch & 0x80) ? 1 : 0; + st->state = sw_read_delta_base; + + /* fall through */ + + case sw_read_delta_base: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->delta_base = st->pint.value; + goto done; + } + } + +done: + + rc = ngx_http_v3_decode_insert_count(c, &st->insert_count); + if (rc != NGX_OK) { + return rc; + } + + if (st->sign) { + if (st->insert_count <= st->delta_base) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + st->base = st->insert_count - st->delta_base - 1; + + } else { + st->base = st->insert_count + st->delta_base; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix done " + "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui", + st->insert_count, st->sign, st->delta_base, st->base); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_field_ri, + sw_field_lri, + sw_field_l, + sw_field_pbi, + sw_field_lpbi + }; + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field representation"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); + + st->field.base = base; + + if (ch & 0x80) { + /* Indexed Field Line */ + + st->state = sw_field_ri; + + } else if (ch & 0x40) { + /* Literal Field Line With Name Reference */ + + st->state = sw_field_lri; + + } else if (ch & 0x20) { + /* Literal Field Line With Literal Name */ + + st->state = sw_field_l; + + } else if (ch & 0x10) { + /* Indexed Field Line With Post-Base Index */ + + st->state = sw_field_pbi; + + } else { + /* Literal Field Line With Post-Base Name Reference */ + + st->state = sw_field_lpbi; + } + } + + switch (st->state) { + + case sw_field_ri: + rc = ngx_http_v3_parse_field_ri(c, &st->field, b); + break; + + case sw_field_lri: + rc = ngx_http_v3_parse_field_lri(c, &st->field, b); + break; + + case sw_field_l: + rc = ngx_http_v3_parse_field_l(c, &st->field, b); + break; + + case sw_field_pbi: + rc = ngx_http_v3_parse_field_pbi(c, &st->field, b); + break; + + case sw_field_lpbi: + rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b); + break; + + default: + rc = NGX_OK; + } + + if (rc != NGX_DONE) { + return rc; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field representation done"); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_uint_t n; + ngx_http_core_srv_conf_t *cscf; + enum { + sw_start = 0, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal huff:%ui, len:%ui", + st->huffman, st->length); + + n = st->length; + + cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); + + if (n > cscf->large_client_header_buffers.size) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent too large field line"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + + if (st->huffman) { + n = n * 8 / 5; + st->huffstate = 0; + } + + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } + + st->value.data = st->last; + st->state = sw_value; + + /* fall through */ + + case sw_value: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + if (st->huffman) { + if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, + st->length == 1, c->log) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + *st->last++ = ch; + } + + if (--st->length) { + break; + } + + st->value.len = st->last - st->value.data; + *st->last = '\0'; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal done \"%V\"", &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri done %s%ui]", + st->dynamic ? "dynamic[-" : "static[", st->index); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x10) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri done %s%ui] \"%V\"", + st->dynamic ? "dynamic[-" : "static[", + st->index, &st->value); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x08) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->name = st->literal.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l done \"%V\" \"%V\"", + &st->name, &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_index + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi done dynamic[+%ui]", st->index); + + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi done dynamic[+%ui] \"%V\"", + st->index, &st->value); + + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + + if (!dynamic) { + if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + return NGX_OK; + } + + if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + if (name) { + p = ngx_pnalloc(c->pool, name->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, name->data, name->len); + p[name->len] = '\0'; + name->data = p; + } + + if (value) { + p = ngx_pnalloc(c->pool, value->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + value->data = p; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, + ngx_buf_t *b) +{ + ngx_buf_t loc; + ngx_int_t rc; + enum { + sw_start = 0, + sw_first_type, + sw_type, + sw_length, + sw_settings, + sw_skip + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse control"); + + st->state = sw_first_type; + + /* fall through */ + + case sw_first_type: + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame type:%ui", st->type); + + if (st->state == sw_first_type + && st->type != NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_MISSING_SETTINGS; + } + + if (st->state != sw_first_type + && st->type == NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_HEADERS + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + 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; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame len:%uL", st->vlint.value); + + st->length = st->vlint.value; + if (st->length == 0) { + st->state = sw_type; + break; + } + + switch (st->type) { + + case NGX_HTTP_V3_FRAME_SETTINGS: + st->state = sw_settings; + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse skip unknown frame"); + st->state = sw_skip; + } + + break; + + case sw_settings: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_settings(c, &st->settings, &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + if (st->length == 0) { + st->state = sw_type; + } + + break; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } +} + + +static ngx_int_t +ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_id, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse settings"); + + st->state = sw_id; + + /* fall through */ + + case sw_id: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->id = st->vlint.value; + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } + + goto done; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done"); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_inr, + sw_iln, + sw_capacity, + sw_duplicate + }; + + for ( ;; ) { + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse encoder instruction"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + if (ch & 0x80) { + /* Insert With Name Reference */ + + st->state = sw_inr; + + } else if (ch & 0x40) { + /* Insert With Literal Name */ + + st->state = sw_iln; + + } else if (ch & 0x20) { + /* Set Dynamic Table Capacity */ + + st->state = sw_capacity; + + } else { + /* Duplicate */ + + st->state = sw_duplicate; + } + } + + switch (st->state) { + + case sw_inr: + + rc = ngx_http_v3_parse_field_inr(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_start; + break; + + case sw_iln: + + rc = ngx_http_v3_parse_field_iln(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_start; + break; + + case sw_capacity: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_set_capacity(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + default: /* sw_duplicate */ + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_duplicate(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + } + } +} + + +static ngx_int_t +ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_name_index; + + /* fall through */ + + case sw_name_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr done %s[%ui] \"%V\"", + st->dynamic ? "dynamic" : "static", + st->index, &st->value); + + rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x20) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->name = st->literal.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln done \"%V\":\"%V\"", + &st->name, &st->value); + + rc = ngx_http_v3_insert(c, &st->name, &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_ack_section, + sw_cancel_stream, + sw_inc_insert_count + }; + + for ( ;; ) { + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse decoder instruction"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + if (ch & 0x80) { + /* Section Acknowledgment */ + + st->state = sw_ack_section; + + } else if (ch & 0x40) { + /* Stream Cancellation */ + + st->state = sw_cancel_stream; + + } else { + /* Insert Count Increment */ + + st->state = sw_inc_insert_count; + } + } + + switch (st->state) { + + case sw_ack_section: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_ack_section(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + case sw_cancel_stream: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_cancel_stream(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + case sw_inc_insert_count: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_inc_insert_count(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + } + } +} + + +ngx_int_t +ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, + ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_skip + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { + /* trailers */ + goto done; + } + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->length = st->vlint.value; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse data type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { + st->state = sw_skip; + break; + } + + st->state = sw_type; + return NGX_OK; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, + ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_control, + sw_encoder, + sw_decoder, + sw_unknown + }; + + for ( ;; ) { + + switch (st->state) { + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } + + switch (st->vlint.value) { + case NGX_HTTP_V3_STREAM_CONTROL: + st->state = sw_control; + break; + + case NGX_HTTP_V3_STREAM_ENCODER: + st->state = sw_encoder; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + st->state = sw_decoder; + break; + + default: + st->state = sw_unknown; + } + + break; + + case sw_control: + + return ngx_http_v3_parse_control(c, &st->u.control, b); + + case sw_encoder: + + return ngx_http_v3_parse_encoder(c, &st->u.encoder, b); + + case sw_decoder: + + return ngx_http_v3_parse_decoder(c, &st->u.decoder, b); + + case sw_unknown: + + b->pos = b->last; + return NGX_AGAIN; + } + } +} diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_parse.h @@ -0,0 +1,146 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_ +#define _NGX_HTTP_V3_PARSE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_uint_t state; + uint64_t value; +} ngx_http_v3_parse_varlen_int_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t shift; + uint64_t value; +} ngx_http_v3_parse_prefix_int_t; + + +typedef struct { + ngx_uint_t state; + uint64_t id; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_settings_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t insert_count; + ngx_uint_t delta_base; + ngx_uint_t sign; + ngx_uint_t base; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_field_section_prefix_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t length; + ngx_uint_t huffman; + ngx_str_t value; + u_char *last; + u_char huffstate; +} ngx_http_v3_parse_literal_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t index; + ngx_uint_t base; + ngx_uint_t dynamic; + + ngx_str_t name; + ngx_str_t value; + + ngx_http_v3_parse_prefix_int_t pint; + ngx_http_v3_parse_literal_t literal; +} ngx_http_v3_parse_field_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_field_t field; +} ngx_http_v3_parse_field_rep_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_field_section_prefix_t prefix; + ngx_http_v3_parse_field_rep_t field_rep; +} ngx_http_v3_parse_headers_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_field_t field; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_encoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_decoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_settings_t settings; +} ngx_http_v3_parse_control_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_varlen_int_t vlint; + union { + ngx_http_v3_parse_encoder_t encoder; + ngx_http_v3_parse_decoder_t decoder; + ngx_http_v3_parse_control_t control; + } u; +} ngx_http_v3_parse_uni_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_data_t; + + +/* + * Parse functions return codes: + * NGX_DONE - parsing done + * NGX_OK - sub-element done + * NGX_AGAIN - more data expected + * NGX_BUSY - waiting for external event + * NGX_ERROR - internal error + * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error + */ + +ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, + ngx_http_v3_parse_headers_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, + ngx_http_v3_parse_data_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, + ngx_http_v3_parse_uni_t *st, ngx_buf_t *b); + + +#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_request.c @@ -0,0 +1,1716 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_http_v3_init_request_stream(ngx_connection_t *c); +static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); +static void ngx_http_v3_cleanup_connection(void *data); +static void ngx_http_v3_cleanup_request(void *data); +static void ngx_http_v3_process_request(ngx_event_t *rev); +static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); +static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); + + +static const struct { + ngx_str_t name; + ngx_uint_t method; +} ngx_http_v3_methods[] = { + + { ngx_string("GET"), NGX_HTTP_GET }, + { ngx_string("POST"), NGX_HTTP_POST }, + { ngx_string("HEAD"), NGX_HTTP_HEAD }, + { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, + { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, + { ngx_string("PUT"), NGX_HTTP_PUT }, + { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, + { ngx_string("DELETE"), NGX_HTTP_DELETE }, + { ngx_string("COPY"), NGX_HTTP_COPY }, + { ngx_string("MOVE"), NGX_HTTP_MOVE }, + { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, + { ngx_string("LOCK"), NGX_HTTP_LOCK }, + { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, + { ngx_string("PATCH"), NGX_HTTP_PATCH }, + { ngx_string("TRACE"), NGX_HTTP_TRACE }, + { ngx_string("CONNECT"), NGX_HTTP_CONNECT } +}; + + +void +ngx_http_v3_init_stream(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_connection_t *hc, *phc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + + hc = c->data; + + hc->ssl = 1; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (c->quic == NULL) { + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + h3c = hc->v3_session; + ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); + + h3scf->quic.timeout = clcf->keepalive_timeout; + ngx_quic_run(c, &h3scf->quic); + return; + } + + phc = ngx_http_quic_get_connection(c); + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; +#if (NGX_PCRE) + hc->ssl_servername_regex = phc->ssl_servername_regex; +#endif + hc->conf_ctx = phc->conf_ctx; + + ngx_set_connection_log(c, clcf->error_log); + } + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + + } else { + ngx_http_v3_init_request_stream(c); + } +} + + +ngx_int_t +ngx_http_v3_init(ngx_connection_t *c) +{ + unsigned int len; + const unsigned char *data; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + + h3c = ngx_http_v3_get_session(c); + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->enable_hq) { + if (!h3scf->enable) { + h3c->hq = 1; + return NGX_OK; + } + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1 + && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0) + { + h3c->hq = 1; + return NGX_OK; + } + } + + return ngx_http_v3_send_settings(c); +} + + +void +ngx_http_v3_shutdown(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown"); + + h3c = ngx_http_v3_get_session(c); + + if (h3c == NULL) { + ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + return; + } + + if (!h3c->goaway) { + h3c->goaway = 1; + + if (!h3c->hq) { + (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + } +} + + +static void +ngx_http_v3_init_request_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_event_t *rev; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + hc = c->data; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests * 2) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "too many requests per connection"); + ngx_http_close_connection(c); + return; + } + + h3c = ngx_http_v3_get_session(c); + + if (h3c->goaway) { + c->close = 1; + ngx_http_close_connection(c); + return; + } + + h3c->next_request_id = c->quic->id + 0x04; + + if (n + 1 == clcf->keepalive_requests + || ngx_current_msec - c->start_time > clcf->keepalive_time) + { + h3c->goaway = 1; + + if (!h3c->hq) { + if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "reached maximum number of requests"); + } + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_connection; + cln->data = c; + + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + rev = c->read; + + if (!h3c->hq) { + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + } + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } +} + + +static void +ngx_http_v3_wait_request_handler(ngx_event_t *rev) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + hc = c->data; + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + size = cscf->client_header_buffer_size; + + b = c->buffer; + + if (b == NULL) { + b = ngx_create_temp_buf(c->pool, size); + if (b == NULL) { + ngx_http_close_connection(c); + return; + } + + c->buffer = b; + + } else if (b->start == NULL) { + + b->start = ngx_palloc(c->pool, size); + if (b->start == NULL) { + ngx_http_close_connection(c); + return; + } + + b->pos = b->start; + b->last = b->start; + b->end = b->last + size; + } + + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + + if (!rev->timer_set) { + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + 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; + } + + return; + } + + if (n == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client closed connection"); + ngx_http_close_connection(c); + return; + } + + b->last += n; + + c->log->action = "reading client request"; + + ngx_reusable_connection(c, 0); + + r = ngx_http_create_request(c); + if (r == NULL) { + ngx_http_close_connection(c); + return; + } + + r->http_version = NGX_HTTP_VERSION_30; + + r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); + if (r->v3_parse == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + r->v3_parse->header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + + c->data = r; + c->requests = (c->quic->id >> 2) + 1; + + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = r; + + rev->handler = ngx_http_v3_process_request; + ngx_http_v3_process_request(rev); +} + + +void +ngx_http_v3_reset_stream(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + h3c = ngx_http_v3_get_session(c); + + if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq + && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); + } + + if (c->timedout) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); + + } else if (c->close) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + + } else if (c->requests == 0 || c->error) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); + } +} + + +static void +ngx_http_v3_cleanup_connection(void *data) +{ + ngx_connection_t *c = data; + + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + + h3c = ngx_http_v3_get_session(c); + + if (--h3c->nrequests == 0) { + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + } +} + + +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_http_request_t *r = data; + + if (!r->response_sent) { + r->connection->error = 1; + } +} + + +static void +ngx_http_v3_process_request(ngx_event_t *rev) +{ + u_char *p; + ssize_t n; + ngx_buf_t *b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_v3_session_t *h3c; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_headers_t *st; + + c = rev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + h3c = ngx_http_v3_get_session(c); + + st = &r->v3_parse->headers; + + b = r->header_in; + + for ( ;; ) { + + if (b->pos == b->last) { + + if (rev->ready) { + n = c->recv(c, b->start, b->end - b->start); + + } else { + n = NGX_AGAIN; + } + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + c->log->action = "reading client request"; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + b->pos = b->start; + b->last = b->start + n; + } + + p = b->pos; + + rc = ngx_http_v3_parse_headers(c, st, b); + + if (rc > 0) { + ngx_quic_reset_stream(c, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + break; + } + + r->request_length += b->pos - p; + h3c->total_bytes += b->pos - p; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } + + if (rc == NGX_BUSY) { + if (rev->error) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (rc == NGX_AGAIN) { + continue; + } + + /* rc == NGX_OK || rc == NGX_DONE */ + + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) + != NGX_OK) + { + break; + } + + if (rc == NGX_DONE) { + if (ngx_http_v3_process_request_header(r) != NGX_OK) { + break; + } + + ngx_http_process_request(r); + break; + } + } + + ngx_http_run_posted_requests(c); + + return; +} + + +static ngx_int_t +ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + size_t len; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + len = name->len + value->len; + + if (len > r->v3_parse->header_limit) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large header"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return NGX_ERROR; + } + + r->v3_parse->header_limit -= len; + + if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", name); + + return NGX_OK; + } + } + + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } + + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (name->len == cookie.len + && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, value) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + } else { + 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 = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + 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; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", name, value); + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch <= 0x20 || ch == 0x7f || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != value->len; i++) { + ch = value->data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", name, value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch, c; + ngx_uint_t i; + + if (r->request_line.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent out of order pseudo-headers"); + goto failed; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":method\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":method\" header"); + goto failed; + } + + r->method_name = *value; + + for (i = 0; i < sizeof(ngx_http_v3_methods) + / sizeof(ngx_http_v3_methods[0]); i++) + { + if (value->len == ngx_http_v3_methods[i].name.len + && ngx_strncmp(value->data, + ngx_http_v3_methods[i].name.data, value->len) + == 0) + { + r->method = ngx_http_v3_methods[i].method; + break; + } + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", value); + goto failed; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 method \"%V\" %ui", value, r->method); + return NGX_OK; + } + + if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + + if (r->uri_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":path\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":path\" header"); + goto failed; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":path\" header: \"%V\"", + value); + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 path \"%V\"", value); + return NGX_OK; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":scheme\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":scheme\" header"); + goto failed; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') + || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":scheme\" header: \"%V\"", + value); + goto failed; + } + + r->schema = *value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 schema \"%V\"", value); + return NGX_OK; + } + + if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + + if (r->host_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":authority\" header"); + goto failed; + } + + r->host_start = value->data; + r->host_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 authority \"%V\"", value); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \"%V\"", name); + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) +{ + size_t len; + u_char *p; + ngx_int_t rc; + ngx_str_t host; + + if (r->request_line.len) { + return NGX_OK; + } + + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":method\" header"); + goto failed; + } + + if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":scheme\" header"); + goto failed; + } + + if (r->uri_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":path\" header"); + goto failed; + } + + len = r->method_name.len + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3.0") - 1; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + + r->request_line.len = p - r->request_line.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + if (ngx_http_process_request_uri(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->host_end) { + + host.len = r->host_end - r->host_start; + host.data = r->host_start; + + rc = ngx_http_validate_host(&host, r->pool, 0); + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid host in request line"); + goto failed; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { + return NGX_ERROR; + } + + r->headers_in.server = host; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_process_request_header(ngx_http_request_t *r) +{ + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + c = r->connection; + + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + 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) { + return NGX_ERROR; + } + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent neither \":authority\" nor \"Host\" header"); + goto failed; + } + + if (r->headers_in.host) { + if (r->headers_in.host->value.len != r->headers_in.server.len + || ngx_memcmp(r->headers_in.host->value.data, + r->headers_in.server.data, + r->headers_in.server.len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \":authority\" and \"Host\" headers " + "with different values"); + goto failed; + } + } + + if (r->headers_in.content_length) { + r->headers_in.content_length_n = + ngx_atoof(r->headers_in.content_length->value.data, + r->headers_in.content_length->value.len); + + if (r->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid \"Content-Length\" header"); + goto failed; + } + + } else { + b = r->header_in; + n = b->last - b->pos; + + if (n == 0) { + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (n > 0) { + b->pos = b->start; + b->last = b->start + n; + } + } + + if (n != 0) { + r->headers_in.chunked = 1; + } + } + + if (r->method == NGX_HTTP_CONNECT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->method == NGX_HTTP_TRACE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->v3_parse->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + *val = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + 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->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_read_request_body(ngx_http_request_t *r) +{ + size_t preread; + ngx_int_t rc; + ngx_chain_t *cl, out; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = r->request_body; + + preread = r->header_in->last - r->header_in->pos; + + if (preread) { + + /* there is the pre-read part of the request body */ + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 client request body preread %uz", preread); + + out.buf = r->header_in; + out.next = NULL; + cl = &out; + + } else { + cl = NULL; + } + + rc = ngx_http_v3_request_body_filter(r, cl); + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0 && rb->last_saved) { + /* the whole request body was pre-read */ + r->request_body_no_buffering = 0; + rb->post_handler(r); + return NGX_OK; + } + + if (rb->rest < 0) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "negative request body rest"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); + if (rb->buf == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->read_event_handler = ngx_http_v3_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return ngx_http_v3_do_read_client_request_body(r); +} + + +static void +ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + } +} + + +ngx_int_t +ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + return NGX_HTTP_REQUEST_TIME_OUT; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; +} + + +static ngx_int_t +ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) +{ + off_t rest; + size_t size; + ssize_t n; + ngx_int_t rc; + ngx_uint_t flush; + ngx_chain_t out; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + rb = r->request_body; + flush = 1; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read client request body"); + + for ( ;; ) { + for ( ;; ) { + if (rb->rest == 0) { + break; + } + + if (rb->buf->last == rb->buf->end) { + + /* update chains */ + + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->busy != NULL) { + if (r->request_body_no_buffering) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + if (rb->filter_need_buffering) { + clcf = ngx_http_get_module_loc_conf(r, + ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "busy buffers after request body flush"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + flush = 0; + rb->buf->pos = rb->buf->start; + rb->buf->last = rb->buf->start; + } + + size = rb->buf->end - rb->buf->last; + rest = rb->rest - (rb->buf->last - rb->buf->pos); + + if ((off_t) size > rest) { + size = (size_t) rest; + } + + if (size == 0) { + break; + } + + n = c->recv(c, rb->buf->last, size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body recv %z", n); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0) { + rb->buf->last_buf = 1; + } + + if (n == NGX_ERROR) { + c->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rb->buf->last += n; + + /* pass buffer to request body filter chain */ + + flush = 0; + out.buf = rb->buf; + out.next = NULL; + + rc = ngx_http_v3_request_body_filter(r, &out); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + break; + } + + if (rb->buf->last < rb->buf->end) { + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body rest %O", rb->rest); + + if (flush) { + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + } + + if (rb->rest == 0 && rb->last_saved) { + break; + } + + if (!c->read->ready || rb->rest == 0) { + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (!r->request_body_no_buffering) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + off_t max; + size_t size; + u_char *p; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t last; + ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_data_t *st; + + rb = r->request_body; + st = &r->v3_parse->body; + + h3c = ngx_http_v3_get_session(r->connection); + + if (rb->rest == -1) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = cscf->large_client_header_buffers.size; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + max = r->headers_in.content_length_n; + + if (max == -1 && clcf->client_max_body_size) { + max = clcf->client_max_body_size; + } + + out = NULL; + ll = &out; + last = 0; + + for (cl = in; cl; cl = cl->next) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http3 body buf " + "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + + if (cl->buf->last_buf) { + last = 1; + } + + b = NULL; + + while (cl->buf->pos < cl->buf->last) { + + if (st->length == 0) { + p = cl->buf->pos; + + rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); + + r->request_length += cl->buf->pos - p; + h3c->total_bytes += cl->buf->pos - p; + + if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { + return NGX_HTTP_CLOSE; + } + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + last = 1; + goto done; + } + + if (rc > 0) { + ngx_quic_reset_stream(r->connection, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid body"); + return NGX_HTTP_BAD_REQUEST; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* rc == NGX_OK */ + + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%ui bytes", + rb->received, st->length); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + continue; + } + + if (b + && st->length <= 128 + && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length) + { + rb->received += st->length; + r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; + + if (st->length < 8) { + + while (st->length) { + *b->last++ = *cl->buf->pos++; + st->length--; + } + + } else { + ngx_memmove(b->last, cl->buf->pos, st->length); + b->last += st->length; + cl->buf->pos += st->length; + st->length = 0; + } + + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + *ll = tl; + ll = &tl->next; + + size = cl->buf->last - cl->buf->pos; + + if (size > st->length) { + cl->buf->pos += (size_t) st->length; + rb->received += st->length; + r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; + st->length = 0; + + } else { + st->length -= size; + rb->received += size; + r->request_length += size; + h3c->total_bytes += size; + h3c->payload_bytes += size; + cl->buf->pos = cl->buf->last; + } + + b->last = cl->buf->pos; + } + } + +done: + + if (last) { + + if (st->length > 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + if (r->headers_in.content_length_n == -1) { + r->headers_in.content_length_n = rb->received; + + } else if (r->headers_in.content_length_n != rb->received) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent less body data than expected: " + "%O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + return NGX_HTTP_BAD_REQUEST; + } + + rb->rest = 0; + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->last_buf = 1; + + *ll = tl; + + } else { + + /* set rb->rest, amount of data we want to see next time */ + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = (off_t) cscf->large_client_header_buffers.size; + } + + rc = ngx_http_top_request_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); + + return rc; +} diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_table.c @@ -0,0 +1,715 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) + + +static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target); +static void ngx_http_v3_unblock(void *data); +static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); + + +typedef struct { + ngx_queue_t queue; + ngx_connection_t *connection; + ngx_uint_t *nblocked; +} ngx_http_v3_block_t; + + +static ngx_http_v3_field_t ngx_http_v3_static_table[] = { + + { ngx_string(":authority"), ngx_string("") }, + { ngx_string(":path"), ngx_string("/") }, + { ngx_string("age"), ngx_string("0") }, + { ngx_string("content-disposition"), ngx_string("") }, + { ngx_string("content-length"), ngx_string("0") }, + { ngx_string("cookie"), ngx_string("") }, + { ngx_string("date"), ngx_string("") }, + { ngx_string("etag"), ngx_string("") }, + { ngx_string("if-modified-since"), ngx_string("") }, + { ngx_string("if-none-match"), ngx_string("") }, + { ngx_string("last-modified"), ngx_string("") }, + { ngx_string("link"), ngx_string("") }, + { ngx_string("location"), ngx_string("") }, + { ngx_string("referer"), ngx_string("") }, + { ngx_string("set-cookie"), ngx_string("") }, + { ngx_string(":method"), ngx_string("CONNECT") }, + { ngx_string(":method"), ngx_string("DELETE") }, + { ngx_string(":method"), ngx_string("GET") }, + { ngx_string(":method"), ngx_string("HEAD") }, + { ngx_string(":method"), ngx_string("OPTIONS") }, + { ngx_string(":method"), ngx_string("POST") }, + { ngx_string(":method"), ngx_string("PUT") }, + { ngx_string(":scheme"), ngx_string("http") }, + { ngx_string(":scheme"), ngx_string("https") }, + { ngx_string(":status"), ngx_string("103") }, + { ngx_string(":status"), ngx_string("200") }, + { ngx_string(":status"), ngx_string("304") }, + { ngx_string(":status"), ngx_string("404") }, + { ngx_string(":status"), ngx_string("503") }, + { ngx_string("accept"), ngx_string("*/*") }, + { ngx_string("accept"), + ngx_string("application/dns-message") }, + { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, + { ngx_string("accept-ranges"), ngx_string("bytes") }, + { ngx_string("access-control-allow-headers"), + ngx_string("cache-control") }, + { ngx_string("access-control-allow-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-allow-origin"), + ngx_string("*") }, + { ngx_string("cache-control"), ngx_string("max-age=0") }, + { ngx_string("cache-control"), ngx_string("max-age=2592000") }, + { ngx_string("cache-control"), ngx_string("max-age=604800") }, + { ngx_string("cache-control"), ngx_string("no-cache") }, + { ngx_string("cache-control"), ngx_string("no-store") }, + { ngx_string("cache-control"), + ngx_string("public, max-age=31536000") }, + { ngx_string("content-encoding"), ngx_string("br") }, + { ngx_string("content-encoding"), ngx_string("gzip") }, + { ngx_string("content-type"), + ngx_string("application/dns-message") }, + { ngx_string("content-type"), + ngx_string("application/javascript") }, + { ngx_string("content-type"), ngx_string("application/json") }, + { ngx_string("content-type"), + ngx_string("application/x-www-form-urlencoded") }, + { ngx_string("content-type"), ngx_string("image/gif") }, + { ngx_string("content-type"), ngx_string("image/jpeg") }, + { ngx_string("content-type"), ngx_string("image/png") }, + { ngx_string("content-type"), ngx_string("text/css") }, + { ngx_string("content-type"), + ngx_string("text/html;charset=utf-8") }, + { ngx_string("content-type"), ngx_string("text/plain") }, + { ngx_string("content-type"), + ngx_string("text/plain;charset=utf-8") }, + { ngx_string("range"), ngx_string("bytes=0-") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains;preload") }, + { ngx_string("vary"), ngx_string("accept-encoding") }, + { ngx_string("vary"), ngx_string("origin") }, + { ngx_string("x-content-type-options"), + ngx_string("nosniff") }, + { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, + { ngx_string(":status"), ngx_string("100") }, + { ngx_string(":status"), ngx_string("204") }, + { ngx_string(":status"), ngx_string("206") }, + { ngx_string(":status"), ngx_string("302") }, + { ngx_string(":status"), ngx_string("400") }, + { ngx_string(":status"), ngx_string("403") }, + { ngx_string(":status"), ngx_string("421") }, + { ngx_string(":status"), ngx_string("425") }, + { ngx_string(":status"), ngx_string("500") }, + { ngx_string("accept-language"), ngx_string("") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("FALSE") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("TRUE") }, + { ngx_string("access-control-allow-headers"), + ngx_string("*") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get, post, options") }, + { ngx_string("access-control-allow-methods"), + ngx_string("options") }, + { ngx_string("access-control-expose-headers"), + ngx_string("content-length") }, + { ngx_string("access-control-request-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-request-method"), + ngx_string("get") }, + { ngx_string("access-control-request-method"), + ngx_string("post") }, + { ngx_string("alt-svc"), ngx_string("clear") }, + { ngx_string("authorization"), ngx_string("") }, + { ngx_string("content-security-policy"), + ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, + { ngx_string("early-data"), ngx_string("1") }, + { ngx_string("expect-ct"), ngx_string("") }, + { ngx_string("forwarded"), ngx_string("") }, + { ngx_string("if-range"), ngx_string("") }, + { ngx_string("origin"), ngx_string("") }, + { ngx_string("purpose"), ngx_string("prefetch") }, + { ngx_string("server"), ngx_string("") }, + { ngx_string("timing-allow-origin"), ngx_string("*") }, + { ngx_string("upgrade-insecure-requests"), + ngx_string("1") }, + { ngx_string("user-agent"), ngx_string("") }, + { ngx_string("x-forwarded-for"), ngx_string("") }, + { ngx_string("x-frame-options"), ngx_string("deny") }, + { ngx_string("x-frame-options"), ngx_string("sameorigin") } +}; + + +ngx_int_t +ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value) +{ + ngx_str_t name; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + if (dynamic) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert dynamic[%ui] \"%V\"", index, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert static[%ui] \"%V\"", index, value); + + if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + } + + return ngx_http_v3_insert(c, &name, value); +} + + +ngx_int_t +ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + size_t size; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + size = ngx_http_v3_table_entry_size(name, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (size > dt->capacity) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "not enough dynamic table capacity"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 insert [%ui] \"%V\":\"%V\", size:%uz", + dt->base + dt->nelts, name, value, size); + + p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, + c->log); + if (p == NULL) { + return NGX_ERROR; + } + + field = (ngx_http_v3_field_t *) p; + + field->name.data = p + sizeof(ngx_http_v3_field_t); + field->name.len = name->len; + field->value.data = ngx_cpymem(field->name.data, name->data, name->len); + field->value.len = value->len; + ngx_memcpy(field->value.data, value->data, value->len); + + dt->elts[dt->nelts++] = field; + dt->size += size; + + dt->insert_count++; + + if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(&dt->send_insert_count, &ngx_posted_events); + + if (ngx_http_v3_new_entry(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +void +ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 inc insert count handler"); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->insert_count > dt->ack_insert_count) { + if (ngx_http_v3_send_inc_insert_count(c, + dt->insert_count - dt->ack_insert_count) + != NGX_OK) + { + return; + } + + dt->ack_insert_count = dt->insert_count; + } +} + + +ngx_int_t +ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +{ + ngx_uint_t max, prev_max; + ngx_http_v3_field_t **elts; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 set capacity %ui", capacity); + + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (capacity > h3scf->max_table_capacity) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_table_capacity limit"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + if (ngx_http_v3_evict(c, capacity) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + dt = &h3c->table; + max = capacity / 32; + prev_max = dt->capacity / 32; + + if (max > prev_max) { + elts = ngx_alloc(max * sizeof(void *), c->log); + if (elts == NULL) { + return NGX_ERROR; + } + + if (dt->elts) { + ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); + ngx_free(dt->elts); + } + + dt->elts = elts; + } + + dt->capacity = capacity; + + return NGX_OK; +} + + +void +ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) +{ + ngx_uint_t n; + ngx_http_v3_dynamic_table_t *dt; + + dt = &h3c->table; + + if (dt->elts == NULL) { + return; + } + + for (n = 0; n < dt->nelts; n++) { + ngx_free(dt->elts[n]); + } + + ngx_free(dt->elts); +} + + +static ngx_int_t +ngx_http_v3_evict(ngx_connection_t *c, size_t target) +{ + size_t size; + ngx_uint_t n; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + n = 0; + + while (dt->size > target) { + field = dt->elts[n++]; + size = ngx_http_v3_table_entry_size(&field->name, &field->value); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 evict [%ui] \"%V\":\"%V\" size:%uz", + dt->base, &field->name, &field->value, size); + + ngx_free(field); + dt->size -= size; + } + + if (n) { + dt->nelts -= n; + dt->base += n; + ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + ngx_str_t name, value; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + return ngx_http_v3_insert(c, &name, &value); +} + + +ngx_int_t +ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ack section %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 increment insert count %ui", inc); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value) +{ + ngx_uint_t nelts; + ngx_http_v3_field_t *field; + + nelts = sizeof(ngx_http_v3_static_table) + / sizeof(ngx_http_v3_static_table[0]); + + if (index >= nelts) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup out of bounds: %ui", + index, nelts); + return NGX_ERROR; + } + + field = &ngx_http_v3_static_table[index]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (index < dt->base || index - dt->base >= dt->nelts) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", + index, dt->base, dt->base + dt->nelts); + return NGX_ERROR; + } + + field = dt->elts[index - dt->base]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) +{ + ngx_uint_t max_entries, full_range, max_value, + max_wrapped, req_insert_count; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + /* QPACK 4.5.1.1. Required Insert Count */ + + if (*insert_count == 0) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + max_entries = h3scf->max_table_capacity / 32; + full_range = 2 * max_entries; + + if (*insert_count > full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + max_value = dt->base + dt->nelts + max_entries; + max_wrapped = (max_value / full_range) * full_range; + req_insert_count = max_wrapped + *insert_count - 1; + + if (req_insert_count > max_value) { + if (req_insert_count <= full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + req_insert_count -= full_range; + } + + if (req_insert_count == 0) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decode insert_count %ui -> %ui", + *insert_count, req_insert_count); + + *insert_count = req_insert_count; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) +{ + size_t n; + ngx_pool_cleanup_t *cln; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + n = dt->base + dt->nelts; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 check insert count req:%ui, have:%ui", + insert_count, n); + + if (n >= insert_count) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); + + block = NULL; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_v3_unblock) { + block = cln->data; + break; + } + } + + if (block == NULL) { + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_unblock; + + block = cln->data; + block->queue.prev = NULL; + block->connection = c; + block->nblocked = &h3c->nblocked; + } + + if (block->queue.prev == NULL) { + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3c->nblocked == h3scf->max_blocked_streams) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_blocked_streams limit"); + + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, + "too many blocked streams"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + h3c->nblocked++; + ngx_queue_insert_tail(&h3c->blocked, &block->queue); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 blocked:%ui", h3c->nblocked); + + return NGX_BUSY; +} + + +void +ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->ack_insert_count < insert_count) { + dt->ack_insert_count = insert_count; + } +} + + +static void +ngx_http_v3_unblock(void *data) +{ + ngx_http_v3_block_t *block = data; + + if (block->queue.prev) { + ngx_queue_remove(&block->queue); + block->queue.prev = NULL; + (*block->nblocked)--; + } +} + + +static ngx_int_t +ngx_http_v3_new_entry(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_connection_t *bc; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 new dynamic entry, blocked:%ui", h3c->nblocked); + + while (!ngx_queue_empty(&h3c->blocked)) { + q = ngx_queue_head(&h3c->blocked); + block = (ngx_http_v3_block_t *) q; + bc = block->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); + + ngx_http_v3_unblock(block); + ngx_post_event(bc->read, &ngx_posted_events); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) +{ + switch (id) { + + case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL", + value); + break; + + case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_BLOCKED_STREAMS:%uL", value); + break; + + default: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param #%uL:%uL", id, value); + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_table.h @@ -0,0 +1,58 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_ +#define _NGX_HTTP_V3_TABLE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_field_t; + + +typedef struct { + ngx_http_v3_field_t **elts; + ngx_uint_t nelts; + ngx_uint_t base; + size_t size; + size_t capacity; + uint64_t insert_count; + uint64_t ack_insert_count; + ngx_event_t send_insert_count; +} ngx_http_v3_dynamic_table_t; + + +void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); +void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); +ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); +ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); +ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, + ngx_uint_t *insert_count); +ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, + ngx_uint_t insert_count); +void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count); +ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, + uint64_t value); + + +#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_uni.c @@ -0,0 +1,624 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_http_v3_parse_uni_t parse; + ngx_int_t index; +} ngx_http_v3_uni_stream_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 ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, + ngx_uint_t type); + + +void +ngx_http_v3_init_uni_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + if (h3c->hq) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "uni stream in hq mode"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); + + n = c->quic->id >> 2; + + if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "reached maximum number of uni streams"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_quic_cancelable_stream(c); + + us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "memory allocation error"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + us->index = -1; + + c->data = us; + + c->read->handler = ngx_http_v3_uni_read_handler; + c->write->handler = ngx_http_v3_uni_dummy_write_handler; + + ngx_http_v3_uni_read_handler(c->read); +} + + +static void +ngx_http_v3_close_uni_stream(ngx_connection_t *c) +{ + ngx_pool_t *pool; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); + + us = c->data; + + if (us && us->index >= 0) { + h3c = ngx_http_v3_get_session(c); + h3c->known_streams[us->index] = NULL; + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +ngx_int_t +ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) +{ + ngx_int_t index; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + + switch (type) { + + case NGX_HTTP_V3_STREAM_ENCODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 encoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + break; + + case NGX_HTTP_V3_STREAM_CONTROL: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 control stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; + + break; + + default: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream 0x%02xL", type); + + if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + index = -1; + } + + if (index >= 0) { + if (h3c->known_streams[index]) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + h3c->known_streams[index] = c; + + us = c->data; + us->index = index; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_uni_read_handler(ngx_event_t *rev) +{ + u_char buf[128]; + ssize_t n; + ngx_buf_t b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + c = rev->data; + us = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); + + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + while (rev->ready) { + + n = c->recv(c, buf, sizeof(buf)); + + if (n == NGX_ERROR) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + if (n == 0) { + if (us->index >= 0) { + rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (n == NGX_AGAIN) { + break; + } + + b.pos = buf; + b.last = buf + n; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + return; + } + + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); + + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read done"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rc > 0) { + goto failed; + } + + if (rc != NGX_AGAIN) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; + goto failed; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + return; + +failed: + + ngx_http_v3_finalize_connection(c, rc, "stream error"); + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev) +{ + u_char ch; + ngx_connection_t *c; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler"); + + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rev->ready) { + if (c->recv(c, &ch, 1) != 0) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL); + ngx_http_v3_close_uni_stream(c); + return; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +static void +ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + + c = wev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +static ngx_connection_t * +ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) +{ + u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; + size_t n; + ngx_int_t index; + ngx_connection_t *sc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + switch (type) { + case NGX_HTTP_V3_STREAM_ENCODER: + index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + break; + case NGX_HTTP_V3_STREAM_DECODER: + index = NGX_HTTP_V3_STREAM_SERVER_DECODER; + break; + case NGX_HTTP_V3_STREAM_CONTROL: + index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + break; + default: + index = -1; + } + + h3c = ngx_http_v3_get_session(c); + + if (index >= 0) { + if (h3c->known_streams[index]) { + return h3c->known_streams[index]; + } + } + + sc = ngx_quic_open_stream(c, 0); + if (sc == NULL) { + goto failed; + } + + ngx_quic_cancelable_stream(sc); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create uni stream, type:%ui", type); + + us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + goto failed; + } + + us->index = index; + + sc->data = us; + + sc->read->handler = ngx_http_v3_uni_dummy_read_handler; + sc->write->handler = ngx_http_v3_uni_dummy_write_handler; + + if (index >= 0) { + h3c->known_streams[index] = sc; + } + + n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + ngx_post_event(sc->read, &ngx_posted_events); + + return sc; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create server stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } + + return NULL; +} + + +ngx_int_t +ngx_http_v3_send_settings(ngx_connection_t *c) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + n = ngx_http_v3_encode_varlen_int(NULL, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity); + n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams); + + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, + NGX_HTTP_V3_FRAME_SETTINGS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send settings"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + n = ngx_http_v3_encode_varlen_int(NULL, id); + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send goaway"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send section acknowledgement %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x80; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send section acknowledgement"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send section acknowledgement"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send stream cancellation %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x40; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send stream cancellation"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send insert count increment %ui", inc); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send insert count increment"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send insert count increment"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +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, + "http3 cancel stream %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h new file mode 100644 --- /dev/null +++ b/src/http/v3/ngx_http_v3_uni.h @@ -0,0 +1,32 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_ +#define _NGX_HTTP_V3_UNI_H_INCLUDED_ + + +#include +#include +#include + + +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_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); + +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); +ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); +ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, + ngx_uint_t inc); + + +#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */ diff --git a/src/os/unix/ngx_socket.h b/src/os/unix/ngx_socket.h --- a/src/os/unix/ngx_socket.h +++ b/src/os/unix/ngx_socket.h @@ -13,6 +13,8 @@ #define NGX_WRITE_SHUTDOWN SHUT_WR +#define NGX_READ_SHUTDOWN SHUT_RD +#define NGX_RDWR_SHUTDOWN SHUT_RDWR typedef int ngx_socket_t; diff --git a/src/os/win32/ngx_socket.h b/src/os/win32/ngx_socket.h --- a/src/os/win32/ngx_socket.h +++ b/src/os/win32/ngx_socket.h @@ -14,6 +14,8 @@ #define NGX_WRITE_SHUTDOWN SD_SEND +#define NGX_READ_SHUTDOWN SD_RECEIVE +#define NGX_RDWR_SHUTDOWN SD_BOTH typedef SOCKET ngx_socket_t; From mdounin at mdounin.ru Fri May 19 17:42:02 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 19 May 2023 20:42:02 +0300 Subject: QUIC branch merge In-Reply-To: <20230519173339.o5c7v4sucyrs5qcj@N00W24XTQX> References: <20230519173339.o5c7v4sucyrs5qcj@N00W24XTQX> Message-ID: Hello! On Fri, May 19, 2023 at 09:33:39PM +0400, Roman Arutyunyan wrote: > Following the push of quic branch, here's the merge diff. [...] Looks good. -- Maxim Dounin http://mdounin.ru/ From arut at nginx.com Fri May 19 17:51:16 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 19 May 2023 17:51:16 +0000 Subject: [nginx] Merged with the quic branch. Message-ID: details: https://hg.nginx.org/nginx/rev/235d482ef6bc branches: changeset: 9110:235d482ef6bc user: Roman Arutyunyan date: Fri May 19 21:46:36 2023 +0400 description: Merged with the quic branch. diffstat: auto/lib/openssl/conf | 43 +- auto/make | 5 +- auto/modules | 99 +- auto/options | 12 + auto/os/linux | 44 + auto/sources | 2 +- auto/unix | 48 + src/core/nginx.c | 3 + src/core/ngx_bpf.c | 143 + src/core/ngx_bpf.h | 43 + src/core/ngx_connection.c | 78 + src/core/ngx_connection.h | 5 + src/core/ngx_core.h | 7 + src/event/ngx_event.c | 34 +- src/event/ngx_event_openssl.c | 12 +- src/event/ngx_event_openssl.h | 11 + src/event/ngx_event_udp.c | 13 +- src/event/ngx_event_udp.h | 8 + src/event/quic/bpf/bpfgen.sh | 113 + src/event/quic/bpf/makefile | 30 + src/event/quic/bpf/ngx_quic_reuseport_helper.c | 140 + src/event/quic/ngx_event_quic.c | 1445 +++++++++++++++ src/event/quic/ngx_event_quic.h | 128 + src/event/quic/ngx_event_quic_ack.c | 1192 +++++++++++++ src/event/quic/ngx_event_quic_ack.h | 30 + src/event/quic/ngx_event_quic_bpf.c | 657 +++++++ src/event/quic/ngx_event_quic_bpf_code.c | 88 + src/event/quic/ngx_event_quic_connection.h | 283 +++ src/event/quic/ngx_event_quic_connid.c | 502 +++++ src/event/quic/ngx_event_quic_connid.h | 29 + src/event/quic/ngx_event_quic_frames.c | 894 +++++++++ src/event/quic/ngx_event_quic_frames.h | 45 + src/event/quic/ngx_event_quic_migration.c | 711 +++++++ src/event/quic/ngx_event_quic_migration.h | 42 + src/event/quic/ngx_event_quic_openssl_compat.c | 646 +++++++ src/event/quic/ngx_event_quic_openssl_compat.h | 60 + src/event/quic/ngx_event_quic_output.c | 1293 ++++++++++++++ src/event/quic/ngx_event_quic_output.h | 40 + src/event/quic/ngx_event_quic_protection.c | 1087 +++++++++++ src/event/quic/ngx_event_quic_protection.h | 114 + src/event/quic/ngx_event_quic_socket.c | 238 ++ src/event/quic/ngx_event_quic_socket.h | 28 + src/event/quic/ngx_event_quic_ssl.c | 600 ++++++ src/event/quic/ngx_event_quic_ssl.h | 19 + src/event/quic/ngx_event_quic_streams.c | 1807 +++++++++++++++++++ src/event/quic/ngx_event_quic_streams.h | 44 + src/event/quic/ngx_event_quic_tokens.c | 289 +++ src/event/quic/ngx_event_quic_tokens.h | 35 + src/event/quic/ngx_event_quic_transport.c | 2199 ++++++++++++++++++++++++ src/event/quic/ngx_event_quic_transport.h | 397 ++++ src/event/quic/ngx_event_quic_udp.c | 420 ++++ src/http/modules/ngx_http_ssl_module.c | 115 +- src/http/ngx_http.c | 28 +- src/http/ngx_http.h | 12 +- src/http/ngx_http_core_module.c | 37 + src/http/ngx_http_core_module.h | 4 + src/http/ngx_http_request.c | 60 +- src/http/ngx_http_request.h | 7 + src/http/ngx_http_request_body.c | 28 + src/http/ngx_http_upstream.c | 20 + src/http/ngx_http_write_filter_module.c | 8 + src/http/v3/ngx_http_v3.c | 109 + src/http/v3/ngx_http_v3.h | 157 + src/http/v3/ngx_http_v3_encode.c | 304 +++ src/http/v3/ngx_http_v3_encode.h | 34 + src/http/v3/ngx_http_v3_filter_module.c | 851 +++++++++ src/http/v3/ngx_http_v3_module.c | 389 ++++ src/http/v3/ngx_http_v3_parse.c | 1931 +++++++++++++++++++++ src/http/v3/ngx_http_v3_parse.h | 146 + src/http/v3/ngx_http_v3_request.c | 1716 ++++++++++++++++++ src/http/v3/ngx_http_v3_table.c | 715 +++++++ src/http/v3/ngx_http_v3_table.h | 58 + src/http/v3/ngx_http_v3_uni.c | 624 ++++++ src/http/v3/ngx_http_v3_uni.h | 32 + src/os/unix/ngx_socket.h | 2 + src/os/win32/ngx_socket.h | 2 + 76 files changed, 23593 insertions(+), 51 deletions(-) diffs (truncated from 24605 to 1000 lines): diff -r b71e69247483 -r 235d482ef6bc auto/lib/openssl/conf --- a/auto/lib/openssl/conf Mon May 01 19:16:05 2023 +0400 +++ b/auto/lib/openssl/conf Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc auto/make --- a/auto/make Mon May 01 19:16:05 2023 +0400 +++ b/auto/make Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc auto/modules --- a/auto/modules Mon May 01 19:16:05 2023 +0400 +++ b/auto/modules Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc auto/options --- a/auto/options Mon May 01 19:16:05 2023 +0400 +++ b/auto/options Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc auto/os/linux --- a/auto/os/linux Mon May 01 19:16:05 2023 +0400 +++ b/auto/os/linux Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc auto/sources --- a/auto/sources Mon May 01 19:16:05 2023 +0400 +++ b/auto/sources Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc auto/unix --- a/auto/unix Mon May 01 19:16:05 2023 +0400 +++ b/auto/unix Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/core/nginx.c --- a/src/core/nginx.c Mon May 01 19:16:05 2023 +0400 +++ b/src/core/nginx.c Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/core/ngx_bpf.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/ngx_bpf.c Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/core/ngx_bpf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/ngx_bpf.h Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/core/ngx_connection.c --- a/src/core/ngx_connection.c Mon May 01 19:16:05 2023 +0400 +++ b/src/core/ngx_connection.c Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/core/ngx_connection.h --- a/src/core/ngx_connection.h Mon May 01 19:16:05 2023 +0400 +++ b/src/core/ngx_connection.h Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/core/ngx_core.h --- a/src/core/ngx_core.h Mon May 01 19:16:05 2023 +0400 +++ b/src/core/ngx_core.h Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/event/ngx_event.c --- a/src/event/ngx_event.c Mon May 01 19:16:05 2023 +0400 +++ b/src/event/ngx_event.c Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c Mon May 01 19:16:05 2023 +0400 +++ b/src/event/ngx_event_openssl.c Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h Mon May 01 19:16:05 2023 +0400 +++ b/src/event/ngx_event_openssl.h Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c Mon May 01 19:16:05 2023 +0400 +++ b/src/event/ngx_event_udp.c Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/event/ngx_event_udp.h --- a/src/event/ngx_event_udp.h Mon May 01 19:16:05 2023 +0400 +++ b/src/event/ngx_event_udp.h Fri May 19 21:46:36 2023 +0400 @@ -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 -r b71e69247483 -r 235d482ef6bc src/event/quic/bpf/bpfgen.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/event/quic/bpf/bpfgen.sh Fri May 19 21:46:36 2023 +0400 @@ -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, From xeioex at nginx.com Sat May 20 04:31:00 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 20 May 2023 04:31:00 +0000 Subject: [njs] Added support of regular expressions not supported directly by PCRE2. Message-ID: details: https://hg.nginx.org/njs/rev/3ec3e7d2ce5f branches: changeset: 2124:3ec3e7d2ce5f user: Dmitry Volyntsev date: Fri May 19 20:22:14 2023 -0700 description: Added support of regular expressions not supported directly by PCRE2. The following patterns were fixed: `[]` - matches nothing, previously was rejected as invalid expression. `[^]` - matched any character, unlike `.` this syntax matches new line, previously was rejected as invalid expression. `++`, `*+`, `?+` - are rejected now, whereas in PCRE2 they are considered valid possessive quantifiers. diffstat: external/njs_regex.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ src/njs_regexp.c | 45 +++++++++++++++++++++++++++++++- src/test/njs_unit_test.c | 41 +++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletions(-) diffs (206 lines): diff -r 4d26300ddc64 -r 3ec3e7d2ce5f external/njs_regex.c --- a/external/njs_regex.c Thu May 18 18:33:36 2023 -0700 +++ b/external/njs_regex.c Fri May 19 20:22:14 2023 -0700 @@ -94,6 +94,73 @@ njs_int_t njs_regex_escape(njs_mp_t *mp, njs_str_t *text) { #ifdef NJS_HAVE_PCRE2 + size_t anychars, nomatches; + u_char *p, *dst, *start, *end; + + /* + * 1) [^] is a valid regexp expression in JavaScript, but PCRE2 + * rejects it as invalid, replacing it with equivalent PCRE2 [\s\S] + * expression. + * 2) [] is a valid regexp expression in JavaScript, but PCRE2 + * rejects it as invalid, replacing it with equivalent PCRE2 (?!) + * expression which matches nothing. + */ + + start = text->start; + end = text->start + text->length; + + anychars = 0; + nomatches = 0; + + for (p = start; p < end; p++) { + switch (*p) { + case '[': + if (p + 1 < end && p[1] == ']') { + p += 1; + nomatches += 1; + + } else if (p + 2 < end && p[1] == '^' && p[2] == ']') { + p += 2; + anychars += 1; + } + + break; + } + } + + if (!anychars && !nomatches) { + return NJS_OK; + } + + text->length = text->length + + anychars * (njs_length("\\s\\S") - njs_length("^")) + + nomatches * (njs_length("?!")); + + text->start = njs_mp_alloc(mp, text->length); + if (njs_slow_path(text->start == NULL)) { + return NJS_ERROR; + } + + dst = text->start; + + for (p = start; p < end; p++) { + + switch (*p) { + case '[': + if (p + 1 < end && p[1] == ']') { + p += 1; + dst = njs_cpymem(dst, "(?!)", 4); + continue; + + } else if (p + 2 < end && p[1] == '^' && p[2] == ']') { + p += 2; + dst = njs_cpymem(dst, "[\\s\\S]", 6); + continue; + } + } + + *dst++ = *p; + } return NJS_OK; diff -r 4d26300ddc64 -r 3ec3e7d2ce5f src/njs_regexp.c --- a/src/njs_regexp.c Thu May 18 18:33:36 2023 -0700 +++ b/src/njs_regexp.c Fri May 19 20:22:14 2023 -0700 @@ -263,9 +263,10 @@ njs_regexp_pattern_create(njs_vm_t *vm, njs_regex_flags_t flags) { int ret; - u_char *p; + u_char *p, *end; size_t size; njs_str_t text; + njs_bool_t in; njs_uint_t n; njs_regex_t *regex; njs_regexp_group_t *group; @@ -274,6 +275,42 @@ njs_regexp_pattern_create(njs_vm_t *vm, text.start = start; text.length = length; + in = 0; + end = start + length; + + for (p = start; p < end; p++) { + + switch (*p) { + case '[': + in = 1; + break; + + case ']': + in = 0; + break; + + case '\\': + p++; + break; + + case '+': + if (njs_slow_path(!in + && (p - 1 > start) + && (p[-1] == '+'|| p[-1] == '*' || p[-1] == '?')) + && (p - 2 >= start && p[-2] != '\\')) + { + /** + * PCRE possessive quantifiers `++`, `*+`, `?+` + * are not allowed in JavaScript. Whereas `[++]` or `\?+` are + * allowed. + */ + goto nothing_to_repeat; + } + + break; + } + } + ret = njs_regex_escape(vm->mem_pool, &text); if (njs_slow_path(ret != NJS_OK)) { njs_memory_error(vm); @@ -370,6 +407,12 @@ fail: njs_mp_free(vm->mem_pool, pattern); return NULL; + +nothing_to_repeat: + + njs_syntax_error(vm, "Invalid regular expression \"%V\" nothing to repeat", + &text); + return NULL; } diff -r 4d26300ddc64 -r 3ec3e7d2ce5f src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Thu May 18 18:33:36 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 19 20:22:14 2023 -0700 @@ -11810,6 +11810,38 @@ static njs_unit_test_t njs_test[] = { njs_str("var r = /./; r"), njs_str("/./") }, + { njs_str("/[^]+|[^]+/.test('\\n| ')"), + njs_str("true") }, + + { njs_str("/[^]+|[^][^]/.test('|aa')"), + njs_str("true") }, + + { njs_str("/a[]/.test('a')"), + njs_str("false") }, + + { njs_str("/[]a/.test('a')"), + njs_str("false") }, + +#ifdef NJS_HAVE_PCRE2 + { njs_str("/[]*a/.test('a')"), + njs_str("true") }, +#endif + + { njs_str("/Ca++BB/"), + njs_str("SyntaxError: Invalid regular expression \"Ca++BB\" nothing to repeat in 1") }, + + { njs_str("/a*+/"), + njs_str("SyntaxError: Invalid regular expression \"a*+\" nothing to repeat in 1") }, + + { njs_str("/a?+/"), + njs_str("SyntaxError: Invalid regular expression \"a?+\" nothing to repeat in 1") }, + + { njs_str(" /\\[[]++\\]/"), + njs_str("SyntaxError: Invalid regular expression \"\\[[]++\\]\" nothing to repeat in 1") }, + + { njs_str("/\\?+/"), + njs_str("/\\?+/") }, + { njs_str("var r = new RegExp(); r"), njs_str("/(?:)/") }, @@ -11870,6 +11902,15 @@ static njs_unit_test_t njs_test[] = { njs_str("RegExp(new RegExp('expr'))"), njs_str("/expr/") }, + { njs_str("RegExp(RegExp('[^]+|[^][^]')).test('| \\na')"), + njs_str("true") }, + + { njs_str("RegExp('a++')"), + njs_str("SyntaxError: Invalid regular expression \"a++\" nothing to repeat") }, + + { njs_str("RegExp('[a++]')"), + njs_str("/[a++]/") }, + { njs_str("RegExp(new RegExp('expr')).multiline"), njs_str("false") }, From xeioex at nginx.com Sat May 20 04:31:02 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 20 May 2023 04:31:02 +0000 Subject: [njs] FS: added support of OpenBSD for fs.stat() and friends. Message-ID: details: https://hg.nginx.org/njs/rev/7cd80c77433b branches: changeset: 2125:7cd80c77433b user: Dmitry Volyntsev date: Fri May 19 20:22:15 2023 -0700 description: FS: added support of OpenBSD for fs.stat() and friends. diffstat: auto/stat | 18 ++++++++++++++++++ external/njs_fs_module.c | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) diffs (50 lines): diff -r 3ec3e7d2ce5f -r 7cd80c77433b auto/stat --- a/auto/stat Fri May 19 20:22:14 2023 -0700 +++ b/auto/stat Fri May 19 20:22:15 2023 -0700 @@ -40,6 +40,24 @@ njs_feature_test="#include . auto/feature +njs_feature="stat.__st_birthtim" +njs_feature_name=NJS_HAVE__STAT_BIRTHTIM +njs_feature_incs= +njs_feature_libs= +njs_feature_test="#include + + int main(void) { + struct stat st; + + if (fstat(0, &st) != 0) { + return 1; + } + + return (int) st.__st_birthtim.tv_sec; + }" +. auto/feature + + njs_feature="stat.st_atim" njs_feature_name=NJS_HAVE_STAT_ATIM njs_feature_incs= diff -r 3ec3e7d2ce5f -r 7cd80c77433b external/njs_fs_module.c --- a/external/njs_fs_module.c Fri May 19 20:22:14 2023 -0700 +++ b/external/njs_fs_module.c Fri May 19 20:22:15 2023 -0700 @@ -3399,8 +3399,6 @@ njs_fs_to_stat(njs_stat_t *dst, struct s dst->st_mtim.tv_nsec = st->st_mtimespec.tv_nsec; dst->st_ctim.tv_sec = st->st_ctimespec.tv_sec; dst->st_ctim.tv_nsec = st->st_ctimespec.tv_nsec; - dst->st_birthtim.tv_sec = st->st_birthtimespec.tv_sec; - dst->st_birthtim.tv_nsec = st->st_birthtimespec.tv_nsec; #elif (NJS_HAVE_STAT_ATIM) @@ -3414,6 +3412,9 @@ njs_fs_to_stat(njs_stat_t *dst, struct s #if (NJS_HAVE_STAT_BIRTHTIM) dst->st_birthtim.tv_sec = st->st_birthtim.tv_sec; dst->st_birthtim.tv_nsec = st->st_birthtim.tv_nsec; +#elif (NJS_HAVE__STAT_BIRTHTIM) + dst->st_birthtim.tv_sec = st->__st_birthtim.tv_sec; + dst->st_birthtim.tv_nsec = st->__st_birthtim.tv_nsec; #else dst->st_birthtim.tv_sec = st->st_ctim.tv_sec; dst->st_birthtim.tv_nsec = st->st_ctim.tv_nsec; From xeioex at nginx.com Sat May 20 04:31:04 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 20 May 2023 04:31:04 +0000 Subject: [njs] Shell: improved working with libedit. Message-ID: details: https://hg.nginx.org/njs/rev/5889afe2a4b7 branches: changeset: 2126:5889afe2a4b7 user: Dmitry Volyntsev date: Fri May 19 20:22:16 2023 -0700 description: Shell: improved working with libedit. Previously, libedit unlike GNU readline does not reinstall rl_callback_handler_install handler after the handler was called. As a result make shell_test executed ~20 times longer with libedit. The fix is to reinstall the rl_callback_handler_install handler explicitely every time the handler is invoked. diffstat: external/njs_shell.c | 21 ++++++++++++++++----- test/shell_test.exp | 10 +++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diffs (81 lines): diff -r 7cd80c77433b -r 5889afe2a4b7 external/njs_shell.c --- a/external/njs_shell.c Fri May 19 20:22:15 2023 -0700 +++ b/external/njs_shell.c Fri May 19 20:22:16 2023 -0700 @@ -1087,6 +1087,7 @@ njs_cb_line_handler(char *line_in) line.length = njs_strlen(line.start); if (line.length == 0) { + rl_callback_handler_install(">> ", njs_cb_line_handler); return; } @@ -1097,6 +1098,10 @@ njs_cb_line_handler(char *line_in) njs_running = NJS_ERROR; } + if (ret == NJS_OK) { + rl_callback_handler_install(">> ", njs_cb_line_handler); + } + free(line.start); } @@ -1104,9 +1109,11 @@ njs_cb_line_handler(char *line_in) static njs_int_t njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options) { - fd_set fds; - njs_vm_t *vm; - njs_int_t ret; + int flags; + fd_set fds; + njs_vm_t *vm; + njs_int_t ret; + struct timeval timeout; if (njs_editline_init() != NJS_OK) { njs_stderror("failed to init completions\n"); @@ -1126,13 +1133,17 @@ njs_interactive_shell(njs_opts_t *opts, rl_callback_handler_install(">> ", njs_cb_line_handler); + flags = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); + njs_running = NJS_OK; while (njs_running == NJS_OK) { FD_ZERO(&fds); - FD_SET(fileno(rl_instream), &fds); + FD_SET(STDIN_FILENO, &fds); + timeout = (struct timeval) {1, 0}; - ret = select(FD_SETSIZE, &fds, NULL, NULL, NULL); + ret = select(FD_SETSIZE, &fds, NULL, NULL, &timeout); if (ret < 0 && errno != EINTR) { njs_stderror("select() failed\n"); njs_running = NJS_ERROR; diff -r 7cd80c77433b -r 5889afe2a4b7 test/shell_test.exp --- a/test/shell_test.exp Fri May 19 20:22:15 2023 -0700 +++ b/test/shell_test.exp Fri May 19 20:22:16 2023 -0700 @@ -100,15 +100,15 @@ njs_test { } njs_test { - {"O\t" - "O\a*bject"} + {"Type\t" + "Type\a*Error"} {"\t\t" - "Object.create*Object.isSealed"} + "TypeError.length"} } njs_test { - {"Object.\t\t" - "Object.create*Object.isSealed"} + {"TypeError.\t\t" + "TypeError.length*TypeError.prototype"} } njs_test { From xeioex at nginx.com Sat May 20 04:31:06 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 20 May 2023 04:31:06 +0000 Subject: [njs] Shell: removed support for building with GNU readline. Message-ID: details: https://hg.nginx.org/njs/rev/9b4d3514d68d branches: changeset: 2127:9b4d3514d68d user: Dmitry Volyntsev date: Fri May 19 20:22:16 2023 -0700 description: Shell: removed support for building with GNU readline. diffstat: auto/readline | 23 ++++------------------- external/njs_shell.c | 3 --- 2 files changed, 4 insertions(+), 22 deletions(-) diffs (56 lines): diff -r 5889afe2a4b7 -r 9b4d3514d68d auto/readline --- a/auto/readline Fri May 19 20:22:16 2023 -0700 +++ b/auto/readline Fri May 19 20:22:16 2023 -0700 @@ -6,14 +6,13 @@ NJS_READLINE_LIB= njs_found=no -njs_feature="GNU readline library" -njs_feature_name=NJS_HAVE_GNU_READLINE +njs_feature="editline library in editline/readline.h" njs_feature_run=no njs_feature_incs= -njs_feature_libs="-lreadline" +njs_feature_name=NJS_HAVE_EDITLINE +njs_feature_libs="-ledit" njs_feature_test="#include - #include - #include + #include int main(void) { add_history(NULL); @@ -22,20 +21,6 @@ njs_feature_test="#include . auto/feature if [ $njs_found = no ]; then - njs_feature="editline library in editline/readline.h" - njs_feature_name=NJS_HAVE_EDITLINE - njs_feature_libs="-ledit" - njs_feature_test="#include - #include - - int main(void) { - add_history(NULL); - return 0; - }" - . auto/feature -fi - -if [ $njs_found = no ]; then # FreeBSD port diff -r 5889afe2a4b7 -r 9b4d3514d68d external/njs_shell.c --- a/external/njs_shell.c Fri May 19 20:22:16 2023 -0700 +++ b/external/njs_shell.c Fri May 19 20:22:16 2023 -0700 @@ -25,9 +25,6 @@ #include #else #include -#if (NJS_HAVE_GNU_READLINE) -#include -#endif #endif #endif From xeioex at nginx.com Sat May 20 04:31:07 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 20 May 2023 04:31:07 +0000 Subject: [njs] Removed leftover String.prototype.toBytes() from benchmark test. Message-ID: details: https://hg.nginx.org/njs/rev/e7aedbc18246 branches: changeset: 2128:e7aedbc18246 user: Dmitry Volyntsev date: Fri May 19 21:29:23 2023 -0700 description: Removed leftover String.prototype.toBytes() from benchmark test. The String.prototype.toBytes() and friends were removed in 4df790f42ce7. diffstat: src/test/njs_benchmark.c | 11 ----------- 1 files changed, 0 insertions(+), 11 deletions(-) diffs (21 lines): diff -r 9b4d3514d68d -r e7aedbc18246 src/test/njs_benchmark.c --- a/src/test/njs_benchmark.c Fri May 19 20:22:16 2023 -0700 +++ b/src/test/njs_benchmark.c Fri May 19 21:29:23 2023 -0700 @@ -259,17 +259,6 @@ static njs_benchmark_test_t njs_test[] njs_str("3524578"), 1 }, - { "fibobench byte strings", - njs_str("var a = '\\x80'.toBytes();" - "function fibo(n) {" - " if (n > 1)" - " return fibo(n - 1) + fibo(n - 2);" - " return 'a'" - "}" - "fibo(32).length"), - njs_str("3524578"), - 1 }, - { "fibobench utf8 strings", njs_str("function fibo(n) {" " if (n > 1)" From mdounin at mdounin.ru Sun May 21 01:42:30 2023 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 21 May 2023 04:42:30 +0300 Subject: [PATCH] QUIC: better sockaddr initialization Message-ID: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1684633125 -10800 # Sun May 21 04:38:45 2023 +0300 # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 QUIC: better sockaddr initialization. The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used to access appropriate variant of the sockaddr). It is better to set it via qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static analyzers won't complain about out-of-bounds access. Prodded by Coverity (CID 1530403). 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 @@ -183,7 +183,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) qsock = ngx_quic_get_socket(c); - ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); qsock->socklen = socklen; c->udp->buffer = &buf; From arut at nginx.com Sun May 21 09:06:59 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Sun, 21 May 2023 13:06:59 +0400 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> Message-ID: <20230521090659.g7sh5lgqszyuyu6p@N00W24XTQX> Hi, On Sun, May 21, 2023 at 04:42:30AM +0300, Maxim Dounin wrote: > # HG changeset patch > # User Maxim Dounin > # Date 1684633125 -10800 > # Sun May 21 04:38:45 2023 +0300 > # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > QUIC: better sockaddr initialization. > > The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold > any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used > to access appropriate variant of the sockaddr). It is better to set it via > qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static > analyzers won't complain about out-of-bounds access. > > Prodded by Coverity (CID 1530403). > > 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 > @@ -183,7 +183,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) > > qsock = ngx_quic_get_socket(c); > > - ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); > + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); > qsock->socklen = socklen; > > c->udp->buffer = &buf; > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks good From arut at nginx.com Sun May 21 09:20:13 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 21 May 2023 13:20:13 +0400 Subject: [PATCH] QUIC: fixed post-close use-after-free Message-ID: <321b69f47a0a9874a063.1684660813@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1684659758 -14400 # Sun May 21 13:02:38 2023 +0400 # Node ID 321b69f47a0a9874a063e464d13706339a706bb8 # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 QUIC: fixed post-close use-after-free. Previously, ngx_quic_close_connection() could be called in a way that QUIC connection was accessed after the call. In most cases the connection is not closed right away, but close timeout is scheduled. However, it's not always the case. Also, if the close process started earlier for a different reason, calling ngx_quic_close_connection() may actually close the connection. The connection object should not be accessed after that. Particularly, accessing c->pool can result in null pointer dereference. Now, when possible, return statement is added to eliminate post-close connection object access. In other places ngx_quic_close_connection() is substituted with posting close event. Prodded by Coverity (CID 1530402). 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 @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t "quic stateless reset packet detected"); qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); + ngx_post_event(&qc->close, &ngx_posted_events); return NGX_OK; } @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t if (do_close) { qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); + ngx_post_event(&qc->close, &ngx_posted_events); } if (pkt->path != qc->path && nonprobing) { 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 @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t * if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); + return; } ngx_quic_connstate_dbg(c); 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 @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da { ngx_connection_t *c = data; - ngx_quic_stream_t *qs; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; qs = c->quic; @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da "quic stream id:0x%xL cleanup", qs->id); if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + goto failed; } qs->connection = NULL; if (ngx_quic_close_stream(qs) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + goto failed; } + + return; + +failed: + + qc = ngx_quic_get_connection(qs->parent); + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + + ngx_post_event(&qc->close, &ngx_posted_events); } From alx.manpages at gmail.com Sun May 21 09:31:32 2023 From: alx.manpages at gmail.com (Alejandro Colomar) Date: Sun, 21 May 2023 11:31:32 +0200 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> Message-ID: Hi Maxim, On 5/21/23 03:42, Maxim Dounin wrote: > # HG changeset patch > # User Maxim Dounin > # Date 1684633125 -10800 > # Sun May 21 04:38:45 2023 +0300 > # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > QUIC: better sockaddr initialization. > > The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold > any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used > to access appropriate variant of the sockaddr). It is better to set it via > qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static > analyzers won't complain about out-of-bounds access. Correct. The previous code was UB, due to memcpy(3) writing to the 'struct sockaddr' member. By writing to sockaddr, you were only allowed to alias via other members the sa_family_t field, but no others. Only if you had written to a 'struct sockaddr_storage' (which nginx doesn't use in the union), you'd have guarantees that it's storage is accessible via any other sockaddr_* structures. This broke with aliasing rules, but the wording in POSIX has been improved for the upcoming Issue 8 to allow this. See: So, for doing things right, when multiple sockaddr_* structures are involved, you need to write to a sockaddr_storage, or write to a union containing all of the structures you're interested in. Otherwise, the only field that you can safely alias from any other sockaddr_* is the sa_family_t one, and aliasing anything else is UB. > > Prodded by Coverity (CID 1530403). > > 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 > @@ -183,7 +183,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) > > qsock = ngx_quic_get_socket(c); > > - ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); > + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); Yes, since you write to the union, you can later use any of the members. Reviewed-by: Alejandro Colomar Cheers, Alex > qsock->socklen = socklen; > > c->udp->buffer = &buf; > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- GPG key fingerprint: A9348594CE31283A826FBDD8D57633D441E25BB5 -------------- next part -------------- A non-text attachment was scrubbed... Name: OpenPGP_signature Type: application/pgp-signature Size: 833 bytes Desc: OpenPGP digital signature URL: From mdounin at mdounin.ru Sun May 21 12:57:52 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sun, 21 May 2023 15:57:52 +0300 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: <20230521090659.g7sh5lgqszyuyu6p@N00W24XTQX> References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> <20230521090659.g7sh5lgqszyuyu6p@N00W24XTQX> Message-ID: Hello! On Sun, May 21, 2023 at 01:06:59PM +0400, Roman Arutyunyan wrote: > On Sun, May 21, 2023 at 04:42:30AM +0300, Maxim Dounin wrote: > > # HG changeset patch > > # User Maxim Dounin > > # Date 1684633125 -10800 > > # Sun May 21 04:38:45 2023 +0300 > > # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec > > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > > QUIC: better sockaddr initialization. > > > > The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold > > any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used > > to access appropriate variant of the sockaddr). It is better to set it via > > qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static > > analyzers won't complain about out-of-bounds access. > > > > Prodded by Coverity (CID 1530403). > > > > 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 > > @@ -183,7 +183,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) > > > > qsock = ngx_quic_get_socket(c); > > > > - ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); > > + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); > > qsock->socklen = socklen; > > > > c->udp->buffer = &buf; > > Looks good Thanks, pushed to http://mdounin.ru/hg/nginx. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Sun May 21 13:09:57 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sun, 21 May 2023 16:09:57 +0300 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> Message-ID: Hello! On Sun, May 21, 2023 at 11:31:32AM +0200, Alejandro Colomar wrote: > On 5/21/23 03:42, Maxim Dounin wrote: > > # HG changeset patch > > # User Maxim Dounin > > # Date 1684633125 -10800 > > # Sun May 21 04:38:45 2023 +0300 > > # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec > > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > > QUIC: better sockaddr initialization. > > > > The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold > > any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used > > to access appropriate variant of the sockaddr). It is better to set it via > > qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static > > analyzers won't complain about out-of-bounds access. > > Correct. The previous code was UB, due to memcpy(3) writing to the > 'struct sockaddr' member. By writing to sockaddr, you were only > allowed to alias via other members the sa_family_t field, but no > others. Well, not really. There is no UB in the previous code, it simply uses a valid (void *) address to fill the sockaddr (and does so without breaking strict aliasing rules). But the code might confuse static analyzers, since they have no way to know that the whole ngx_sockaddr_t is being set, and not just the (struct sockaddr) union member which is referenced. -- Maxim Dounin http://mdounin.ru/ From alx.manpages at gmail.com Sun May 21 14:35:00 2023 From: alx.manpages at gmail.com (Alejandro Colomar) Date: Sun, 21 May 2023 16:35:00 +0200 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> Message-ID: <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> Hello Maxim! On 5/21/23 15:09, Maxim Dounin wrote: > Hello! > > On Sun, May 21, 2023 at 11:31:32AM +0200, Alejandro Colomar wrote: > >> On 5/21/23 03:42, Maxim Dounin wrote: >>> # HG changeset patch >>> # User Maxim Dounin >>> # Date 1684633125 -10800 >>> # Sun May 21 04:38:45 2023 +0300 >>> # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec >>> # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 >>> QUIC: better sockaddr initialization. >>> >>> The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold >>> any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used >>> to access appropriate variant of the sockaddr). It is better to set it via >>> qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static >>> analyzers won't complain about out-of-bounds access. >> >> Correct. The previous code was UB, due to memcpy(3) writing to the >> 'struct sockaddr' member. By writing to sockaddr, you were only >> allowed to alias via other members the sa_family_t field, but no >> others. > > Well, not really. There is no UB in the previous code, it simply > uses a valid (void *) address to fill the sockaddr (and does so > without breaking strict aliasing rules). While the data being written was correctly written via memcpy(3), you wouldn't be allowed to access it later as anything that is not 'struct sockaddr'. For example, the following is a strict-aliasing violation: struct s { int a; int b; }; struct t { int a; }; union u { struct s s; struct t t; }; struct s x = {42, 42}; union u y; int z; memcpy(&y.t, &x, sizeof(x)); // This is fine // We created an object of type 'struct t' in the union. // Unions allow aliasing, so we're allowed to reinterpret // that object as a 'struct s' via the other member. z = y.s.a; // This is fine. // But we're not allowed to reinterpret bytes that are // officially uninitialized (even though we know they are // initialized). z = y.s.b; // UB here. The reason for the UB is that the compiler is free to assume that since you wrote to the struct t member, the write can't possibly write to the second member of the struct (even if the size passed to memcpy(3) is larger than that). In other words, the compiler may assume that anything past sizeof(struct t) is uninitialized. Also, writing past an object is very dubious, even via memcpy(3), even if you know that the storage is there (thanks to the union). It's just safer writing to the union itself, or to the field that has the correct object type. Although the trailing flexible array in sockaddr might be enough to signal that any amount of bytes may have been written, since it's defined as: struct sockaddr { sa_family_t sa_family; /* Address family */ char sa_data[]; /* Socket address */ }; > > But the code might confuse static analyzers, since they have no > way to know that the whole ngx_sockaddr_t is being set, and not > just the (struct sockaddr) union member which is referenced. And in a similar way, it would be worrying (and possible) to confuse an optimizing compiler. :) I happened to get an internal compiler error earlier this month with GCC 13's -fanalyzer and some dubious use of flexible array members, with code that was bordering UB. Cheers, Alex -- GPG key fingerprint: A9348594CE31283A826FBDD8D57633D441E25BB5 -------------- next part -------------- A non-text attachment was scrubbed... Name: OpenPGP_signature Type: application/pgp-signature Size: 833 bytes Desc: OpenPGP digital signature URL: From mdounin at mdounin.ru Sun May 21 21:22:01 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 00:22:01 +0300 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> Message-ID: Hello! On Sun, May 21, 2023 at 04:35:00PM +0200, Alejandro Colomar wrote: > On 5/21/23 15:09, Maxim Dounin wrote: > > Hello! > > > > On Sun, May 21, 2023 at 11:31:32AM +0200, Alejandro Colomar wrote: > > > >> On 5/21/23 03:42, Maxim Dounin wrote: > >>> # HG changeset patch > >>> # User Maxim Dounin > >>> # Date 1684633125 -10800 > >>> # Sun May 21 04:38:45 2023 +0300 > >>> # Node ID 68fa4b86ed46138dd1a8fcf2cfd80206de068bec > >>> # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > >>> QUIC: better sockaddr initialization. > >>> > >>> The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold > >>> any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used > >>> to access appropriate variant of the sockaddr). It is better to set it via > >>> qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static > >>> analyzers won't complain about out-of-bounds access. > >> > >> Correct. The previous code was UB, due to memcpy(3) writing to the > >> 'struct sockaddr' member. By writing to sockaddr, you were only > >> allowed to alias via other members the sa_family_t field, but no > >> others. > > > > Well, not really. There is no UB in the previous code, it simply > > uses a valid (void *) address to fill the sockaddr (and does so > > without breaking strict aliasing rules). > > While the data being written was correctly written via memcpy(3), > you wouldn't be allowed to access it later as anything that is > not 'struct sockaddr'. For example, the following is a > strict-aliasing violation: > > struct s { int a; int b; }; > struct t { int a; }; > union u { struct s s; struct t t; }; > > struct s x = {42, 42}; > union u y; > int z; > > memcpy(&y.t, &x, sizeof(x)); // This is fine > > // We created an object of type 'struct t' in the union. > // Unions allow aliasing, so we're allowed to reinterpret > // that object as a 'struct s' via the other member. > > z = y.s.a; // This is fine. > > // But we're not allowed to reinterpret bytes that are > // officially uninitialized (even though we know they are > // initialized). > > z = y.s.b; // UB here. > > The reason for the UB is that the compiler is free to assume > that since you wrote to the struct t member, the write can't > possibly write to the second member of the struct (even if > the size passed to memcpy(3) is larger than that). In other > words, the compiler may assume that anything past > sizeof(struct t) is uninitialized. You haven't wrote to the struct t member, you wrote to the address using memcpy(). There is a difference, see C99 (or C11, whichever you prefer), 6.5 Expressions. > Also, writing past an > object is very dubious, even via memcpy(3), even if you know > that the storage is there (thanks to the union). It's just > safer writing to the union itself, or to the field that has > the correct object type. And that's why the patch. While it is correct to write to the memory with any pointer, using the union itself is more obvious and causes less confusion. [...] -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Sun May 21 21:40:08 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 00:40:08 +0300 Subject: [PATCH] Added $http2_stream_id In-Reply-To: References: Message-ID: Hello! On Sun, May 14, 2023 at 11:59:35PM +0100, J Carter wrote: > 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. In HTTP/2, for each stream nginx creates a new connection, and r->connection->requests as seen by $connection_requests will be frozen for the request lifetime. That is, it essentially shows the request sequence number. > I'd ask that this patch is reconsidered. While $connection_requests is certainly not exactly equivalent to HTTP/2 stream id, the $connection_requests is believed to be enough for user-level tasks, as well as for most debugging tasks. -- Maxim Dounin http://mdounin.ru/ From alx.manpages at gmail.com Sun May 21 23:06:53 2023 From: alx.manpages at gmail.com (Alejandro Colomar) Date: Mon, 22 May 2023 01:06:53 +0200 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> Message-ID: Hello! On 5/21/23 23:22, Maxim Dounin wrote: >> While the data being written was correctly written via memcpy(3), >> you wouldn't be allowed to access it later as anything that is >> not 'struct sockaddr'. For example, the following is a >> strict-aliasing violation: >> >> struct s { int a; int b; }; >> struct t { int a; }; >> union u { struct s s; struct t t; }; >> >> struct s x = {42, 42}; >> union u y; >> int z; >> >> memcpy(&y.t, &x, sizeof(x)); // This is fine >> >> // We created an object of type 'struct t' in the union. >> // Unions allow aliasing, so we're allowed to reinterpret >> // that object as a 'struct s' via the other member. >> >> z = y.s.a; // This is fine. >> >> // But we're not allowed to reinterpret bytes that are >> // officially uninitialized (even though we know they are >> // initialized). >> >> z = y.s.b; // UB here. >> >> The reason for the UB is that the compiler is free to assume >> that since you wrote to the struct t member, the write can't >> possibly write to the second member of the struct (even if >> the size passed to memcpy(3) is larger than that). In other >> words, the compiler may assume that anything past >> sizeof(struct t) is uninitialized. > > You haven't wrote to the struct t member, you wrote to the address > using memcpy(). There is a difference, see C99 (or C11, whichever > you prefer), 6.5 Expressions. I assume you refer to this: C11::6.5p6: < The effective type of an object for an access to its stored < value is the declared type of the object, if any.87) If a value < is stored into an object having no declared type through an < lvalue having a type that is not a character type, then the type < of the lvalue becomes the effective type of the object for that < access and for subsequent accesses that do not modify the stored < value. If a value is copied into an object having no declared < type using memcpy or memmove, or is copied as an array of < character type, then the effective type of the modified object < for that access and for subsequent accesses that do not modify < the value is the effective type of the object from which the < value is copied, if it has one. For all other accesses to an < object having no declared type, the effective type of the object < is simply the type of the lvalue used for the access. < [...] < < 87) Allocated objects have no declared type. Let's break it into sentences: < 87) Allocated objects have no declared type. malloc(3)d memory has no declared type; all other memory has a declared type. < The effective type of an object for an access to its stored < value is the declared type of the object, if any.89) 'y.t' has a declared type of 'struct t', so its effective type is also 'struct t'. < If a value is copied into an object having no declared < type using memcpy or memmove, [...] It doesn't apply, since 'y.t' has declared type. memcpy(3) can't change the effective type in this case. I think my example has UB (not 100% sure, though). However, I notice now that it's slightly different from the one in nginx, since nginx wasn't using a named variable (or at least it's not obvious in ngx_quic_recvmsg(), since it's just receiving a pointer), and it's instead probably using malloc(3)'d memory, so the memory didn't have declared type. Only if a local temporary variable had been used at some point upper in the call chain, there could be UB. This is something that a static analyzer will have a hard time checking (and a human reviewer too), though. > >> Also, writing past an >> object is very dubious, even via memcpy(3), even if you know >> that the storage is there (thanks to the union). It's just >> safer writing to the union itself, or to the field that has >> the correct object type. > > And that's why the patch. While it is correct to write to the > memory with any pointer, Only if the memory is malloc(3)'d memory. But yes, probably that's the case in patch. If local/global variables are involved that's not allowed. > using the union itself is more obvious > and causes less confusion. Yup; thanks for improving that. Thanks, Alex > > [...] > -- GPG key fingerprint: A9348594CE31283A826FBDD8D57633D441E25BB5 -------------- next part -------------- A non-text attachment was scrubbed... Name: OpenPGP_signature Type: application/pgp-signature Size: 833 bytes Desc: OpenPGP digital signature URL: From mdounin at mdounin.ru Mon May 22 02:35:53 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 05:35:53 +0300 Subject: [PATCH] QUIC: better sockaddr initialization In-Reply-To: References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> Message-ID: Hello! On Mon, May 22, 2023 at 01:06:53AM +0200, Alejandro Colomar wrote: > Hello! > > On 5/21/23 23:22, Maxim Dounin wrote: > >> While the data being written was correctly written via memcpy(3), > >> you wouldn't be allowed to access it later as anything that is > >> not 'struct sockaddr'. For example, the following is a > >> strict-aliasing violation: > >> > >> struct s { int a; int b; }; > >> struct t { int a; }; > >> union u { struct s s; struct t t; }; > >> > >> struct s x = {42, 42}; > >> union u y; > >> int z; > >> > >> memcpy(&y.t, &x, sizeof(x)); // This is fine > >> > >> // We created an object of type 'struct t' in the union. > >> // Unions allow aliasing, so we're allowed to reinterpret > >> // that object as a 'struct s' via the other member. > >> > >> z = y.s.a; // This is fine. > >> > >> // But we're not allowed to reinterpret bytes that are > >> // officially uninitialized (even though we know they are > >> // initialized). > >> > >> z = y.s.b; // UB here. > >> > >> The reason for the UB is that the compiler is free to assume > >> that since you wrote to the struct t member, the write can't > >> possibly write to the second member of the struct (even if > >> the size passed to memcpy(3) is larger than that). In other > >> words, the compiler may assume that anything past > >> sizeof(struct t) is uninitialized. > > > > You haven't wrote to the struct t member, you wrote to the address > > using memcpy(). There is a difference, see C99 (or C11, whichever > > you prefer), 6.5 Expressions. > > I assume you refer to this: > > C11::6.5p6: > < The effective type of an object for an access to its stored > < value is the declared type of the object, if any.87) If a value > < is stored into an object having no declared type through an > < lvalue having a type that is not a character type, then the type > < of the lvalue becomes the effective type of the object for that > < access and for subsequent accesses that do not modify the stored > < value. If a value is copied into an object having no declared > < type using memcpy or memmove, or is copied as an array of > < character type, then the effective type of the modified object > < for that access and for subsequent accesses that do not modify > < the value is the effective type of the object from which the > < value is copied, if it has one. For all other accesses to an > < object having no declared type, the effective type of the object > < is simply the type of the lvalue used for the access. > < > [...] > < > < 87) Allocated objects have no declared type. > > > Let's break it into sentences: > > < 87) Allocated objects have no declared type. > > malloc(3)d memory has no declared type; all other memory has a > declared type. > > < The effective type of an object for an access to its stored > < value is the declared type of the object, if any.89) > > 'y.t' has a declared type of 'struct t', so its effective type is > also 'struct t'. > > < If a value is copied into an object having no declared > < type using memcpy or memmove, [...] > > It doesn't apply, since 'y.t' has declared type. memcpy(3) > can't change the effective type in this case. > > > I think my example has UB (not 100% sure, though). However, I > notice now that it's slightly different from the one in nginx, > since nginx wasn't using a named variable (or at least it's not > obvious in ngx_quic_recvmsg(), since it's just receiving a > pointer), and it's instead probably using malloc(3)'d memory, so > the memory didn't have declared type. Only if a local temporary > variable had been used at some point upper in the call chain, > there could be UB. This is something that a static analyzer > will have a hard time checking (and a human reviewer too), > though. > > As you've correctly noticed, your example is different from the nginx code, since it uses a local variable (and not an allocated object, like nginx does). The next question you might consider is: what difference it makes? And if at all? In particular, you might want to consider what memcpy() actually does. As per the quoted paragraph, it does two things: 1. Modifies the object as pointed out by (void *) &y.t; 2. Changes the effective type of the modified object for subsequent accesses (to the effective type of the object from which the value is copied) if there is no declared type. For your example, (2) is not relevant, since there is a declared type. So the remaining part is (1). What actually memcpy() modifies, and what it is allowed to modify? What makes you think that compiler is free to assume that you've wrote only to the y.t member, and not just some bytes in the y object? [...] -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon May 22 03:22:35 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 06:22:35 +0300 Subject: [PATCH] QUIC: fixed post-close use-after-free In-Reply-To: <321b69f47a0a9874a063.1684660813@arut-laptop> References: <321b69f47a0a9874a063.1684660813@arut-laptop> Message-ID: Hello! On Sun, May 21, 2023 at 01:20:13PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1684659758 -14400 > # Sun May 21 13:02:38 2023 +0400 > # Node ID 321b69f47a0a9874a063e464d13706339a706bb8 > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > QUIC: fixed post-close use-after-free. > > Previously, ngx_quic_close_connection() could be called in a way that QUIC > connection was accessed after the call. In most cases the connection is not > closed right away, but close timeout is scheduled. However, it's not always > the case. Also, if the close process started earlier for a different reason, > calling ngx_quic_close_connection() may actually close the connection. The > connection object should not be accessed after that. Particularly, accessing > c->pool can result in null pointer dereference. > > Now, when possible, return statement is added to eliminate post-close connection > object access. In other places ngx_quic_close_connection() is substituted with > posting close event. > > Prodded by Coverity (CID 1530402). Note that "Prodded by..." is generally used for changes which aren't real bug fixes (that is, for mostly style changes like the one I've submitted for sockaddr initialization). For bugs found by Coverity which are indeed bugs we usually prefer "Found by ...". > > 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 > @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t > "quic stateless reset packet detected"); > > qc->draining = 1; > - ngx_quic_close_connection(c, NGX_OK); > + ngx_post_event(&qc->close, &ngx_posted_events); > > return NGX_OK; > } > @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t > > if (do_close) { > qc->draining = 1; > - ngx_quic_close_connection(c, NGX_OK); > + ngx_post_event(&qc->close, &ngx_posted_events); > } > > if (pkt->path != qc->path && nonprobing) { > 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 > @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t * > > if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { > ngx_quic_close_connection(c, NGX_ERROR); > + return; > } > > ngx_quic_connstate_dbg(c); > 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 > @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da > { > ngx_connection_t *c = data; > > - ngx_quic_stream_t *qs; > + ngx_quic_stream_t *qs; > + ngx_quic_connection_t *qc; > > qs = c->quic; > > @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da > "quic stream id:0x%xL cleanup", qs->id); > > if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { > - ngx_quic_close_connection(c, NGX_ERROR); > - return; > + goto failed; > } > > qs->connection = NULL; > > if (ngx_quic_close_stream(qs) != NGX_OK) { > - ngx_quic_close_connection(c, NGX_ERROR); > - return; > + goto failed; > } > + > + return; > + > +failed: > + > + qc = ngx_quic_get_connection(qs->parent); > + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; > + > + ngx_post_event(&qc->close, &ngx_posted_events); > } > > I'm not really familiar with the code, but looks good to me. -- Maxim Dounin http://mdounin.ru/ From kadhar2006 at gmail.com Mon May 22 07:13:18 2023 From: kadhar2006 at gmail.com (abdul kadhar) Date: Mon, 22 May 2023 12:43:18 +0530 Subject: How to create same location to point different proxy server In-Reply-To: References: Message-ID: Hi, I need to know how to configure nginx for below scenario. I have configured the Load Balancer(Nginx) for ignition. I have created two upstream(ignapp & ignProdapp) server. For ignapp upstream it is working fine. I need to configure the below code for upstream server(ignProdapp) in the same configuration file how to do this. Below is the sample configuration for the server ignapp. Can you please help me how to configure the below settings for another upstream server. Below is the config setting for ignapp same way I need to do it for creating location ~ /data/, location ~ /system/ , location ~ /res/ , location ~ /idp/, location ~ /.well-known/ for ignappPro Upstream server . Upstream ignapp{ ip_hash; server 10.10.10.11:8088; } upstream ignappPro { ip_hash; server 10.10.10.12:8088; } server { listen 80; server_name www.abdul.com; location ~ /data/ { proxy_pass ignapp/data/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /system/ { proxy_pass ​ignapp/system/; tp_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /res/ { proxy_pass ignapp/res/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /idp/ { proxy_pass ​ignapp/idp/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /.well-known/ { proxy_pass ignapp/.well-known/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } # Here we are setting the protocol type, address, port, and uri (optional) that will be the destination of our proxied server: location / { proxy_pass ​ignapp/data/perspective/client/demo/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } } -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Mon May 22 11:28:24 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 22 May 2023 15:28:24 +0400 Subject: [PATCH] QUIC: fixed post-close use-after-free In-Reply-To: <321b69f47a0a9874a063.1684660813@arut-laptop> References: <321b69f47a0a9874a063.1684660813@arut-laptop> Message-ID: > On 21 May 2023, at 13:20, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1684659758 -14400 > # Sun May 21 13:02:38 2023 +0400 > # Node ID 321b69f47a0a9874a063e464d13706339a706bb8 > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > QUIC: fixed post-close use-after-free. > > Previously, ngx_quic_close_connection() could be called in a way that QUIC > connection was accessed after the call. In most cases the connection is not > closed right away, but close timeout is scheduled. However, it's not always > the case. Also, if the close process started earlier for a different reason, > calling ngx_quic_close_connection() may actually close the connection. The > connection object should not be accessed after that. Particularly, accessing > c->pool can result in null pointer dereference. The last sentence doesn't seem correct. For that, c->pool should be NULL on destroy; not sure if we do this. > > Now, when possible, return statement is added to eliminate post-close connection > object access. In other places ngx_quic_close_connection() is substituted with > posting close event. > > Prodded by Coverity (CID 1530402). > > 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 > @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t > "quic stateless reset packet detected"); > > qc->draining = 1; > - ngx_quic_close_connection(c, NGX_OK); > + ngx_post_event(&qc->close, &ngx_posted_events); > > return NGX_OK; > } Stateless reset mimics a short packet and uses an entire datagram. We handle short packets accordingly, expecting no further packets. So there should be no issues with synchronous connection close, except perhaps that we return NGX_OK here for seemingly no reason. This theoretically may involve additional actions leading to post-close use-after-free. > @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t > > if (do_close) { > qc->draining = 1; > - ngx_quic_close_connection(c, NGX_OK); > + ngx_post_event(&qc->close, &ngx_posted_events); > } > > if (pkt->path != qc->path && nonprobing) { Not sure if this one is practicable (but I don't object). To make this call close connection immediately, qc->closing should be already set but close timer unset. To speculate, ngx_quic_close_connection() could be previously called with some other rc, and ngx_quic_close_streams() returned NGX_AGAIN. > 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 > @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t * > > if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { > ngx_quic_close_connection(c, NGX_ERROR); > + return; > } > > ngx_quic_connstate_dbg(c); > 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 > @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da > { > ngx_connection_t *c = data; > > - ngx_quic_stream_t *qs; > + ngx_quic_stream_t *qs; > + ngx_quic_connection_t *qc; > > qs = c->quic; > > @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da > "quic stream id:0x%xL cleanup", qs->id); > > if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { > - ngx_quic_close_connection(c, NGX_ERROR); > - return; > + goto failed; > } > > qs->connection = NULL; > > if (ngx_quic_close_stream(qs) != NGX_OK) { > - ngx_quic_close_connection(c, NGX_ERROR); > - return; > + goto failed; > } > + > + return; > + > +failed: > + > + qc = ngx_quic_get_connection(qs->parent); > + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; > + > + ngx_post_event(&qc->close, &ngx_posted_events); > } > > Another problem here is that ngx_quic_close_connection() was called with stream connection, with unpleasant consequences. You might want to reflect this in the changelog. -- Sergey Kandaurov From arut at nginx.com Mon May 22 12:00:52 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 22 May 2023 16:00:52 +0400 Subject: [PATCH] QUIC: fixed post-close use-after-free In-Reply-To: References: <321b69f47a0a9874a063.1684660813@arut-laptop> Message-ID: <20230522120051.nmgpoh3obivplffb@N00W24XTQX> On Mon, May 22, 2023 at 03:28:24PM +0400, Sergey Kandaurov wrote: > > > On 21 May 2023, at 13:20, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1684659758 -14400 > > # Sun May 21 13:02:38 2023 +0400 > > # Node ID 321b69f47a0a9874a063e464d13706339a706bb8 > > # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 > > QUIC: fixed post-close use-after-free. > > > > Previously, ngx_quic_close_connection() could be called in a way that QUIC > > connection was accessed after the call. In most cases the connection is not > > closed right away, but close timeout is scheduled. However, it's not always > > the case. Also, if the close process started earlier for a different reason, > > calling ngx_quic_close_connection() may actually close the connection. The > > connection object should not be accessed after that. Particularly, accessing > > c->pool can result in null pointer dereference. > > The last sentence doesn't seem correct. > For that, c->pool should be NULL on destroy; not sure if we do this. Indeed. Removed this sentence. > > Now, when possible, return statement is added to eliminate post-close connection > > object access. In other places ngx_quic_close_connection() is substituted with > > posting close event. > > > > Prodded by Coverity (CID 1530402). > > > > 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 > > @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t > > "quic stateless reset packet detected"); > > > > qc->draining = 1; > > - ngx_quic_close_connection(c, NGX_OK); > > + ngx_post_event(&qc->close, &ngx_posted_events); > > > > return NGX_OK; > > } > > Stateless reset mimics a short packet and uses an entire datagram. > We handle short packets accordingly, expecting no further packets. > So there should be no issues with synchronous connection close, > except perhaps that we return NGX_OK here for seemingly no reason. > This theoretically may involve additional actions leading to post-close > use-after-free. We can return NGX_DONE, but it's still not good for calling functions to reference connection that's already freed. In current code this will result in logging packet handling result in a freed c->log. But even if we fix this, having a freed connection will one day lead to a problem. > > @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t > > > > if (do_close) { > > qc->draining = 1; > > - ngx_quic_close_connection(c, NGX_OK); > > + ngx_post_event(&qc->close, &ngx_posted_events); > > } > > > > if (pkt->path != qc->path && nonprobing) { > > Not sure if this one is practicable (but I don't object). > To make this call close connection immediately, qc->closing > should be already set but close timer unset. To speculate, > ngx_quic_close_connection() could be previously called with > some other rc, and ngx_quic_close_streams() returned NGX_AGAIN. Yes, that's unlikely, but possible. > > 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 > > @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t * > > > > if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { > > ngx_quic_close_connection(c, NGX_ERROR); > > + return; > > } > > > > ngx_quic_connstate_dbg(c); > > 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 > > @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da > > { > > ngx_connection_t *c = data; > > > > - ngx_quic_stream_t *qs; > > + ngx_quic_stream_t *qs; > > + ngx_quic_connection_t *qc; > > > > qs = c->quic; > > > > @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da > > "quic stream id:0x%xL cleanup", qs->id); > > > > if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { > > - ngx_quic_close_connection(c, NGX_ERROR); > > - return; > > + goto failed; > > } > > > > qs->connection = NULL; > > > > if (ngx_quic_close_stream(qs) != NGX_OK) { > > - ngx_quic_close_connection(c, NGX_ERROR); > > - return; > > + goto failed; > > } > > + > > + return; > > + > > +failed: > > + > > + qc = ngx_quic_get_connection(qs->parent); > > + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; > > + > > + ngx_post_event(&qc->close, &ngx_posted_events); > > } > > > > > > Another problem here is that ngx_quic_close_connection() was > called with stream connection, with unpleasant consequences. > You might want to reflect this in the changelog. Thanks for noticing this. The full commit log: QUIC: fixed post-close use-after-free. Previously, ngx_quic_close_connection() could be called in a way that QUIC connection was accessed after the call. In most cases the connection is not closed right away, but close timeout is scheduled. However, it's not always the case. Also, if the close process started earlier for a different reason, calling ngx_quic_close_connection() may actually close the connection. The connection object should not be accessed after that. Now, when possible, return statement is added to eliminate post-close connection object access. In other places ngx_quic_close_connection() is substituted with posting close event. Also, the new way of closing connection in ngx_quic_stream_cleanup_handler() fixes another problem in this function. Previously it passed stream connection instead of QUIC connection to ngx_quic_close_connection(). This could result in incomplete connection shutdown. One consequence of that could be that QUIC streams were freed without shutting down their application contexts. This could result in another use-after-free. Found by Coverity (CID 1530402). -- Roman Arutyunyan From pluknet at nginx.com Mon May 22 12:21:39 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 22 May 2023 12:21:39 +0000 Subject: [nginx] QUIC: better sockaddr initialization. Message-ID: details: https://hg.nginx.org/nginx/rev/68fa4b86ed46 branches: changeset: 9111:68fa4b86ed46 user: Maxim Dounin date: Sun May 21 04:38:45 2023 +0300 description: QUIC: better sockaddr initialization. The qsock->sockaddr field is a ngx_sockaddr_t union, and therefore can hold any sockaddr (and union members, such qsock->sockaddr.sockaddr, can be used to access appropriate variant of the sockaddr). It is better to set it via qsock->sockaddr itself though, and not qsock->sockaddr.sockaddr, so static analyzers won't complain about out-of-bounds access. Prodded by Coverity (CID 1530403). diffstat: src/event/quic/ngx_event_quic_udp.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 235d482ef6bc -r 68fa4b86ed46 src/event/quic/ngx_event_quic_udp.c --- a/src/event/quic/ngx_event_quic_udp.c Fri May 19 21:46:36 2023 +0400 +++ b/src/event/quic/ngx_event_quic_udp.c Sun May 21 04:38:45 2023 +0300 @@ -183,7 +183,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) qsock = ngx_quic_get_socket(c); - ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); qsock->socklen = socklen; c->udp->buffer = &buf; From pluknet at nginx.com Mon May 22 12:45:16 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 22 May 2023 16:45:16 +0400 Subject: [PATCH] QUIC: fixed post-close use-after-free In-Reply-To: <20230522120051.nmgpoh3obivplffb@N00W24XTQX> References: <321b69f47a0a9874a063.1684660813@arut-laptop> <20230522120051.nmgpoh3obivplffb@N00W24XTQX> Message-ID: <731924D5-C431-40D1-A8B7-904BBF1333FD@nginx.com> > On 22 May 2023, at 16:00, Roman Arutyunyan wrote: > > On Mon, May 22, 2023 at 03:28:24PM +0400, Sergey Kandaurov wrote: >> >>> On 21 May 2023, at 13:20, Roman Arutyunyan wrote: >>> >>> # HG changeset patch >>> # User Roman Arutyunyan >>> # Date 1684659758 -14400 >>> # Sun May 21 13:02:38 2023 +0400 >>> # Node ID 321b69f47a0a9874a063e464d13706339a706bb8 >>> # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05 >>> QUIC: fixed post-close use-after-free. >>> >>> Previously, ngx_quic_close_connection() could be called in a way that QUIC >>> connection was accessed after the call. In most cases the connection is not >>> closed right away, but close timeout is scheduled. However, it's not always >>> the case. Also, if the close process started earlier for a different reason, >>> calling ngx_quic_close_connection() may actually close the connection. The >>> connection object should not be accessed after that. Particularly, accessing >>> c->pool can result in null pointer dereference. >> >> The last sentence doesn't seem correct. >> For that, c->pool should be NULL on destroy; not sure if we do this. > > Indeed. Removed this sentence. > >>> Now, when possible, return statement is added to eliminate post-close connection >>> object access. In other places ngx_quic_close_connection() is substituted with >>> posting close event. >>> >>> Prodded by Coverity (CID 1530402). >>> >>> 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 >>> @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t >>> "quic stateless reset packet detected"); >>> >>> qc->draining = 1; >>> - ngx_quic_close_connection(c, NGX_OK); >>> + ngx_post_event(&qc->close, &ngx_posted_events); >>> >>> return NGX_OK; >>> } >> >> Stateless reset mimics a short packet and uses an entire datagram. >> We handle short packets accordingly, expecting no further packets. >> So there should be no issues with synchronous connection close, >> except perhaps that we return NGX_OK here for seemingly no reason. >> This theoretically may involve additional actions leading to post-close >> use-after-free. > > We can return NGX_DONE, but it's still not good for calling functions to > reference connection that's already freed. In current code this will > result in logging packet handling result in a freed c->log. But even if > we fix this, having a freed connection will one day lead to a problem. > >>> @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t >>> >>> if (do_close) { >>> qc->draining = 1; >>> - ngx_quic_close_connection(c, NGX_OK); >>> + ngx_post_event(&qc->close, &ngx_posted_events); >>> } >>> >>> if (pkt->path != qc->path && nonprobing) { >> >> Not sure if this one is practicable (but I don't object). >> To make this call close connection immediately, qc->closing >> should be already set but close timer unset. To speculate, >> ngx_quic_close_connection() could be previously called with >> some other rc, and ngx_quic_close_streams() returned NGX_AGAIN. > > Yes, that's unlikely, but possible. > >>> 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 >>> @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t * >>> >>> if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { >>> ngx_quic_close_connection(c, NGX_ERROR); >>> + return; >>> } >>> >>> ngx_quic_connstate_dbg(c); >>> 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 >>> @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da >>> { >>> ngx_connection_t *c = data; >>> >>> - ngx_quic_stream_t *qs; >>> + ngx_quic_stream_t *qs; >>> + ngx_quic_connection_t *qc; >>> >>> qs = c->quic; >>> >>> @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da >>> "quic stream id:0x%xL cleanup", qs->id); >>> >>> if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { >>> - ngx_quic_close_connection(c, NGX_ERROR); >>> - return; >>> + goto failed; >>> } >>> >>> qs->connection = NULL; >>> >>> if (ngx_quic_close_stream(qs) != NGX_OK) { >>> - ngx_quic_close_connection(c, NGX_ERROR); >>> - return; >>> + goto failed; >>> } >>> + >>> + return; >>> + >>> +failed: >>> + >>> + qc = ngx_quic_get_connection(qs->parent); >>> + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; >>> + >>> + ngx_post_event(&qc->close, &ngx_posted_events); >>> } >>> >>> >> >> Another problem here is that ngx_quic_close_connection() was >> called with stream connection, with unpleasant consequences. >> You might want to reflect this in the changelog. > > Thanks for noticing this. The full commit log: > > QUIC: fixed post-close use-after-free. > > Previously, ngx_quic_close_connection() could be called in a way that QUIC > connection was accessed after the call. In most cases the connection is not > closed right away, but close timeout is scheduled. However, it's not always > the case. Also, if the close process started earlier for a different reason, > calling ngx_quic_close_connection() may actually close the connection. The > connection object should not be accessed after that. > > Now, when possible, return statement is added to eliminate post-close connection > object access. In other places ngx_quic_close_connection() is substituted with > posting close event. > > Also, the new way of closing connection in ngx_quic_stream_cleanup_handler() > fixes another problem in this function. Previously it passed stream connection > instead of QUIC connection to ngx_quic_close_connection(). This could result > in incomplete connection shutdown. One consequence of that could be that QUIC > streams were freed without shutting down their application contexts. This could > result in another use-after-free. > > Found by Coverity (CID 1530402). > Good for me. -- Sergey Kandaurov From arut at nginx.com Mon May 22 12:47:48 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 22 May 2023 12:47:48 +0000 Subject: [nginx] QUIC: fixed post-close use-after-free. Message-ID: details: https://hg.nginx.org/nginx/rev/d59277dd3d8c branches: changeset: 9112:d59277dd3d8c user: Roman Arutyunyan date: Mon May 22 15:59:42 2023 +0400 description: QUIC: fixed post-close use-after-free. Previously, ngx_quic_close_connection() could be called in a way that QUIC connection was accessed after the call. In most cases the connection is not closed right away, but close timeout is scheduled. However, it's not always the case. Also, if the close process started earlier for a different reason, calling ngx_quic_close_connection() may actually close the connection. The connection object should not be accessed after that. Now, when possible, return statement is added to eliminate post-close connection object access. In other places ngx_quic_close_connection() is substituted with posting close event. Also, the new way of closing connection in ngx_quic_stream_cleanup_handler() fixes another problem in this function. Previously it passed stream connection instead of QUIC connection to ngx_quic_close_connection(). This could result in incomplete connection shutdown. One consequence of that could be that QUIC streams were freed without shutting down their application contexts. This could result in another use-after-free. Found by Coverity (CID 1530402). diffstat: src/event/quic/ngx_event_quic.c | 4 ++-- src/event/quic/ngx_event_quic_ack.c | 1 + src/event/quic/ngx_event_quic_streams.c | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diffs (73 lines): diff -r 68fa4b86ed46 -r d59277dd3d8c src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Sun May 21 04:38:45 2023 +0300 +++ b/src/event/quic/ngx_event_quic.c Mon May 22 15:59:42 2023 +0400 @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t "quic stateless reset packet detected"); qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); + ngx_post_event(&qc->close, &ngx_posted_events); return NGX_OK; } @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t if (do_close) { qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); + ngx_post_event(&qc->close, &ngx_posted_events); } if (pkt->path != qc->path && nonprobing) { diff -r 68fa4b86ed46 -r d59277dd3d8c src/event/quic/ngx_event_quic_ack.c --- a/src/event/quic/ngx_event_quic_ack.c Sun May 21 04:38:45 2023 +0300 +++ b/src/event/quic/ngx_event_quic_ack.c Mon May 22 15:59:42 2023 +0400 @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t * if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); + return; } ngx_quic_connstate_dbg(c); diff -r 68fa4b86ed46 -r d59277dd3d8c src/event/quic/ngx_event_quic_streams.c --- a/src/event/quic/ngx_event_quic_streams.c Sun May 21 04:38:45 2023 +0300 +++ b/src/event/quic/ngx_event_quic_streams.c Mon May 22 15:59:42 2023 +0400 @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da { ngx_connection_t *c = data; - ngx_quic_stream_t *qs; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; qs = c->quic; @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da "quic stream id:0x%xL cleanup", qs->id); if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + goto failed; } qs->connection = NULL; if (ngx_quic_close_stream(qs) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + goto failed; } + + return; + +failed: + + qc = ngx_quic_get_connection(qs->parent); + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + + ngx_post_event(&qc->close, &ngx_posted_events); } From mdounin at mdounin.ru Mon May 22 12:48:23 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 15:48:23 +0300 Subject: [PATCH] Added $realip_add_x_forwarded_for In-Reply-To: References: Message-ID: Hello! On Sun, May 14, 2023 at 04:51:58AM +0100, J Carter wrote: > # 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; > } > } Thanks for the patch, but I can't say I like the idea of introducing yet another variable and asking users to change it manually. This is essentially equivalent to using proxy_set_header X-Forwarded-For "$http_x_forwarded_for, $realip_remote_addr"; as the ticket suggests. Also, it is an open question if $realip_remote_addr should be used, or X-Forwarded-For should be left unmodified if remote addr was set from X-Forwarded-For. The realip module instructs nginx to use the address as obtained from the header, and not using it for some purposes looks at least questionable. Also, it seems incorrect to use $realip_remote_addr (or keep X-Forwarded-For unmodified) if remote addr was set from other sources, such as PROXY protocol headers. Overall, current behaviour might actually be optimal. [...] -- Maxim Dounin http://mdounin.ru/ From alx.manpages at gmail.com Mon May 22 14:23:56 2023 From: alx.manpages at gmail.com (Alejandro Colomar) Date: Mon, 22 May 2023 16:23:56 +0200 Subject: memcpy(3), strict aliasing, pointer provenance rules (was: [PATCH] QUIC: better sockaddr initialization) In-Reply-To: References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> Message-ID: <80a96722-5bf1-2567-89fd-880d482a644c@gmail.com> Hello! On 5/22/23 04:35, Maxim Dounin wrote: [...] > As you've correctly noticed, your example is different from the > nginx code, since it uses a local variable (and not an allocated > object, like nginx does). > > The next question you might consider is: what difference it makes? > And if at all? In particular, you might want to consider what > memcpy() actually does. As per the quoted paragraph, it does two > things: > > 1. Modifies the object as pointed out by (void *) &y.t; > > 2. Changes the effective type of the modified object for > subsequent accesses (to the effective type of the object from > which the value is copied) if there is no declared type. > > For your example, (2) is not relevant, since there is a > declared type. So the remaining part is (1). Agree. In fact, I'm now closer to thinking that the same thing via malloc(3) was similarly UB. > > What actually memcpy() modifies, and what it is allowed to modify? According to ISO C, memcpy() isn't special in this regard. 6.5.6 (Additive operators) applies: So accesses outside the bounds of the (sub)object to which the pointer points, are UB, even via memcpy(3). You won't find any specification that memcpy(3) is allowed to cross subobject boundaries in order to copy bytes. However, GCC relaxes the rules for mem*() functions in an undocumented way, so that memcpy() can cross subobject boundaries to perform the copy. I asked that GCC documents this, and specifies under which rules this works, and what are the limits. str*() functions seem to have gotten the same exception recently in GCC. > What makes you think that compiler is free to assume that you've > wrote only to the y.t member, and not just some bytes in the y > object? Even if memcpy(3) is allowed to write bytes to subobjects beyond the one pointed to by the pointer passed to it, I'm not convinced that this has a valid meaning when there is no subobject which to write. Since in this case, the write happens to padding bytes in the parent object, I'm not sure if the GCC extension applies to padding bytes. So, according to ISO C, I'm now convinced that it's UB, even via memcpy(3). According to the more relaxed GNU C rules, I'm not sure, since it's not even documented. We only know that it seems to work so far. Cheers, Alex -- GPG key fingerprint: A9348594CE31283A826FBDD8D57633D441E25BB5 -------------- next part -------------- A non-text attachment was scrubbed... Name: OpenPGP_signature Type: application/pgp-signature Size: 833 bytes Desc: OpenPGP digital signature URL: From arut at nginx.com Mon May 22 15:26:39 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Mon, 22 May 2023 19:26:39 +0400 Subject: [PATCH] Post-merge README with a disclaimer Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1684568499 -14400 # Sat May 20 11:41:39 2023 +0400 # Branch quic # Node ID a83071314a22652d4a552e72e13d18aa4c01d79e # Parent e92a03d1d6dad22866e9e5e09fac8545cbdb15e4 Post-merge README with a disclaimer. diff --git a/README b/README new file mode 100644 --- /dev/null +++ b/README @@ -0,0 +1,5 @@ +This code was merged[1] into mainline nginx[2]. +This repository is considered read-only now. Don’t use it for anything. + +[1] https://hg.nginx.org/nginx/rev/235d482ef6bc +[2] https://hg.nginx.org/nginx From maxim at nginx.com Mon May 22 17:19:12 2023 From: maxim at nginx.com (Maxim Konovalov) Date: Mon, 22 May 2023 10:19:12 -0700 Subject: [PATCH] Post-merge README with a disclaimer In-Reply-To: References: Message-ID: On 22.05.2023 08:26, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1684568499 -14400 > # Sat May 20 11:41:39 2023 +0400 > # Branch quic > # Node ID a83071314a22652d4a552e72e13d18aa4c01d79e > # Parent e92a03d1d6dad22866e9e5e09fac8545cbdb15e4 > Post-merge README with a disclaimer. > > diff --git a/README b/README > new file mode 100644 > --- /dev/null > +++ b/README > @@ -0,0 +1,5 @@ > +This code was merged[1] into mainline nginx[2]. > +This repository is considered read-only now. Don’t use it for anything. > + > +[1] https://hg.nginx.org/nginx/rev/235d482ef6bc > +[2] https://hg.nginx.org/nginx Go ahead. -- Maxim Konovalov From pluknet at nginx.com Mon May 22 19:52:12 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:12 +0400 Subject: [PATCH 0 of 6] SSL tests refactoring fixes In-Reply-To: References: Message-ID: This series fixes various issues arised post SSL tests simplification, seen on various distributions with old OpenSSL and/or Perl, most notably: - Amazon Linux (LTS) ssl_certificate.t ssl_certificates.t stream_ssl_certificate.t stream_ssl_variables.t - CentOS 7 ssl_certificate.t stream_ssl_certificate.t stream_ssl_variables.t - SLES 12 mail_ssl.t ssl_certificates.t ssl_stapling.t stream_ssl_variables.t mail_ssl_session_reuse.t stream_ssl_session_reuse.t - LibreSSL ssl_stapling.t From pluknet at nginx.com Mon May 22 19:52:13 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:13 +0400 Subject: [PATCH 1 of 6] Tests: unbreak ssl_stapling.t after IO::Socket::SSL refactoring In-Reply-To: References: Message-ID: <231b14e2041afed10f5c.1684785133@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1684773873 -14400 # Mon May 22 20:44:33 2023 +0400 # Node ID 231b14e2041afed10f5c2e6f62aad298854a6379 # Parent a797d7428fa529e843181d802dd85bc4306b89e9 Tests: unbreak ssl_stapling.t after IO::Socket::SSL refactoring. Previously, the test used to check the established SSL/TLS protocol version using Net::SSLeay on the client side. Now it is provided on the server side within a server response. Added missing bits to make this actually work. diff --git a/ssl_stapling.t b/ssl_stapling.t --- a/ssl_stapling.t +++ b/ssl_stapling.t @@ -57,6 +57,8 @@ http { ssl_ciphers DEFAULT:ECCdraft; + add_header X-SSL-Protocol $ssl_protocol always; + server { listen 127.0.0.1:8443 ssl; listen 127.0.0.1:8080; @@ -341,7 +343,7 @@ sub staple { } sub test_tls13 { - return http_get('/', start => 1, SSL => 1) =~ /TLSv1.3/; + return http_get('/', SSL => 1) =~ /TLSv1.3/; } ############################################################################### From pluknet at nginx.com Mon May 22 19:52:14 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:14 +0400 Subject: [PATCH 2 of 6] Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key In-Reply-To: References: Message-ID: <4a3a451716ba26f8fc4b.1684785134@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1684773874 -14400 # Mon May 22 20:44:34 2023 +0400 # Node ID 4a3a451716ba26f8fc4be1ccc88dd8596101a6b2 # Parent 231b14e2041afed10f5c2e6f62aad298854a6379 Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key. diff --git a/ssl_certificate.t b/ssl_certificate.t --- a/ssl_certificate.t +++ b/ssl_certificate.t @@ -171,6 +171,8 @@ local $TODO = 'no TLSv1.3 sessions, old 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(); +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' + if $IO::Socket::SSL::VERSION < 1.965; like(get('default', 8080, $s), qr/default:r/, 'session reused'); diff --git a/stream_ssl_certificate.t b/stream_ssl_certificate.t --- a/stream_ssl_certificate.t +++ b/stream_ssl_certificate.t @@ -148,6 +148,8 @@ local $TODO = 'no TLSv1.3 sessions, old 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(); +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' + if $IO::Socket::SSL::VERSION < 1.965; like(get('default', 8080, $s), qr/default:r/, 'session reused'); From pluknet at nginx.com Mon May 22 19:52:15 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:15 +0400 Subject: [PATCH 3 of 6] Tests: unbreak stream_ssl_variables.t with old IO::Socket::SSL In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1684773874 -14400 # Mon May 22 20:44:34 2023 +0400 # Node ID e60c76cbf2a5b0d9e1d235770d68f260cf1a4e3e # Parent 4a3a451716ba26f8fc4be1ccc88dd8596101a6b2 Tests: unbreak stream_ssl_variables.t with old IO::Socket::SSL. Do not clobber a stream object in test_tls13(). diff --git a/stream_ssl_variables.t b/stream_ssl_variables.t --- a/stream_ssl_variables.t +++ b/stream_ssl_variables.t @@ -141,7 +141,7 @@ undef $s; ############################################################################### sub test_tls13 { - $s = stream(PeerAddr => '127.0.0.1:' . port(8443), SSL => 1); + my $s = stream(PeerAddr => '127.0.0.1:' . port(8443), SSL => 1); $s->read() =~ /TLSv1.3/; } From pluknet at nginx.com Mon May 22 19:52:16 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:16 +0400 Subject: [PATCH 4 of 6] Tests: avoid specifying PSS in sigalgs unless in TLSv1.3 In-Reply-To: References: Message-ID: <42066e126d2ca0f6d509.1684785136@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1684774417 -14400 # Mon May 22 20:53:37 2023 +0400 # Node ID 42066e126d2ca0f6d5095d818910559adf5d4bdc # Parent e60c76cbf2a5b0d9e1d235770d68f260cf1a4e3e Tests: avoid specifying PSS in sigalgs unless in TLSv1.3. It might happen that TLSv1.3 is disabled and PSS isn't supported as seen on Amazon Linux (LTS). The change restores old logic before 0e1865aa9b33. diff --git a/ssl_certificates.t b/ssl_certificates.t --- a/ssl_certificates.t +++ b/ssl_certificates.t @@ -120,8 +120,8 @@ sub get_socket { return unless defined $type; my $ssleay = Net::SSLeay::SSLeay(); return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; - $sigalgs = $type . '+SHA256' unless $type eq 'RSA'; + my $sigalgs = $type eq 'RSA' && test_tls13() + ? 'RSA+SHA256:PSS+SHA256' : $type . '+SHA256'; # SSL_CTRL_SET_SIGALGS_LIST Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) or die("Failed to set sigalgs"); diff --git a/ssl_stapling.t b/ssl_stapling.t --- a/ssl_stapling.t +++ b/ssl_stapling.t @@ -321,8 +321,8 @@ sub staple { return unless defined $ciphers; my $ssleay = Net::SSLeay::SSLeay(); return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; - $sigalgs = $ciphers . '+SHA256' unless $ciphers eq 'RSA'; + my $sigalgs = $ciphers eq 'RSA' && test_tls13() + ? 'RSA+SHA256:PSS+SHA256' : $ciphers . '+SHA256'; # SSL_CTRL_SET_SIGALGS_LIST Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) or die("Failed to set sigalgs"); From pluknet at nginx.com Mon May 22 19:52:17 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:17 +0400 Subject: [PATCH 5 of 6] Tests: added missing socket_ssl_alpn guard in mail_ssl.t In-Reply-To: References: Message-ID: <633613b924b957fc18dd.1684785137@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1684784660 -14400 # Mon May 22 23:44:20 2023 +0400 # Node ID 633613b924b957fc18dde972ff346fe92b9e823e # Parent 42066e126d2ca0f6d5095d818910559adf5d4bdc Tests: added missing socket_ssl_alpn guard in mail_ssl.t. diff --git a/mail_ssl.t b/mail_ssl.t --- a/mail_ssl.t +++ b/mail_ssl.t @@ -164,6 +164,10 @@ like($s->socket()->dump_peer_certificate # alpn +TODO: { +local $TODO = 'no ALPN support in IO::Socket::SSL' + unless $t->has_feature('socket_ssl_alpn'); + $s = Test::Nginx::IMAP->new( PeerAddr => '127.0.0.1:' . port(8148), SSL => 1, @@ -171,6 +175,8 @@ like($s->socket()->dump_peer_certificate ); $s->ok('alpn'); +} + SKIP: { skip 'LibreSSL too old', 1 if $t->has_module('LibreSSL') From pluknet at nginx.com Mon May 22 19:52:18 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Mon, 22 May 2023 23:52:18 +0400 Subject: [PATCH 6 of 6] Tests: added missing socket_ssl_reused prerequisites In-Reply-To: References: Message-ID: <193b129568708d6689c7.1684785138@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1684784712 -14400 # Mon May 22 23:45:12 2023 +0400 # Node ID 193b129568708d6689c7cb8707f46bca7280a0d0 # Parent 633613b924b957fc18dde972ff346fe92b9e823e Tests: added missing socket_ssl_reused prerequisites. diff --git a/mail_ssl_session_reuse.t b/mail_ssl_session_reuse.t --- a/mail_ssl_session_reuse.t +++ b/mail_ssl_session_reuse.t @@ -27,7 +27,7 @@ select STDOUT; $| = 1; local $SIG{PIPE} = 'IGNORE'; my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap socket_ssl_sslversion/) - ->has_daemon('openssl')->plan(7); + ->has(qw/socket_ssl_reused/)->has_daemon('openssl')->plan(7); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/stream_ssl_session_reuse.t b/stream_ssl_session_reuse.t --- a/stream_ssl_session_reuse.t +++ b/stream_ssl_session_reuse.t @@ -27,7 +27,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/stream stream_ssl socket_ssl_sslversion/) - ->has_daemon('openssl')->plan(7); + ->has(qw/socket_ssl_reused/)->has_daemon('openssl')->plan(7); $t->write_file_expand('nginx.conf', <<'EOF'); From mdounin at mdounin.ru Mon May 22 20:04:09 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 23:04:09 +0300 Subject: memcpy(3), strict aliasing, pointer provenance rules (was: [PATCH] QUIC: better sockaddr initialization) In-Reply-To: <80a96722-5bf1-2567-89fd-880d482a644c@gmail.com> References: <68fa4b86ed46138dd1a8.1684633350@vm-bsd.mdounin.ru> <59eb353e-dcf4-ef2b-b215-ed7a557d8f12@gmail.com> <80a96722-5bf1-2567-89fd-880d482a644c@gmail.com> Message-ID: Hello! On Mon, May 22, 2023 at 04:23:56PM +0200, Alejandro Colomar wrote: > Hello! > > On 5/22/23 04:35, Maxim Dounin wrote: > [...] > > > As you've correctly noticed, your example is different from the > > nginx code, since it uses a local variable (and not an allocated > > object, like nginx does). > > > > The next question you might consider is: what difference it makes? > > And if at all? In particular, you might want to consider what > > memcpy() actually does. As per the quoted paragraph, it does two > > things: > > > > 1. Modifies the object as pointed out by (void *) &y.t; > > > > 2. Changes the effective type of the modified object for > > subsequent accesses (to the effective type of the object from > > which the value is copied) if there is no declared type. > > > > For your example, (2) is not relevant, since there is a > > declared type. So the remaining part is (1). > > Agree. > > In fact, I'm now closer to thinking that the same thing via > malloc(3) was similarly UB. > > > > > What actually memcpy() modifies, and what it is allowed to modify? > > According to ISO C, memcpy() isn't special in this regard. 6.5.6 > (Additive operators) applies: > > > So accesses outside the bounds of the (sub)object to which the > pointer points, are UB, even via memcpy(3). > > You won't find any specification that memcpy(3) is allowed to > cross subobject boundaries in order to copy bytes. > > However, GCC relaxes the rules for mem*() functions in an > undocumented way, so that memcpy() can cross subobject boundaries > to perform the copy. I asked that GCC documents this, and > specifies under which rules this works, and what are the limits. > > str*() functions seem to have gotten the same exception recently > in GCC. > > While this is an interesting ticket, I don't think that Martin Sebor's interpretation of the standard that pointers are not allowed to cross subobject boundaries is correct. As outlined in the comments, this makes it impossible to obtain a pointer to the outer object, which is a common task in C. [...] -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon May 22 20:36:44 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 22 May 2023 23:36:44 +0300 Subject: [PATCH 1 of 6] Tests: unbreak ssl_stapling.t after IO::Socket::SSL refactoring In-Reply-To: <231b14e2041afed10f5c.1684785133@enoparse.local> References: <231b14e2041afed10f5c.1684785133@enoparse.local> Message-ID: Hello! On Mon, May 22, 2023 at 11:52:13PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1684773873 -14400 > # Mon May 22 20:44:33 2023 +0400 > # Node ID 231b14e2041afed10f5c2e6f62aad298854a6379 > # Parent a797d7428fa529e843181d802dd85bc4306b89e9 > Tests: unbreak ssl_stapling.t after IO::Socket::SSL refactoring. > > Previously, the test used to check the established SSL/TLS protocol version > using Net::SSLeay on the client side. Now it is provided on the server side > within a server response. Added missing bits to make this actually work. > > diff --git a/ssl_stapling.t b/ssl_stapling.t > --- a/ssl_stapling.t > +++ b/ssl_stapling.t > @@ -57,6 +57,8 @@ http { > > ssl_ciphers DEFAULT:ECCdraft; > > + add_header X-SSL-Protocol $ssl_protocol always; > + > server { > listen 127.0.0.1:8443 ssl; > listen 127.0.0.1:8080; > @@ -341,7 +343,7 @@ sub staple { > } > > sub test_tls13 { > - return http_get('/', start => 1, SSL => 1) =~ /TLSv1.3/; > + return http_get('/', SSL => 1) =~ /TLSv1.3/; > } > > ############################################################################### Looks good. -- Maxim Dounin http://mdounin.ru/ From pluknet at nginx.com Mon May 22 20:52:42 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 22 May 2023 20:52:42 +0000 Subject: [nginx] QUIC: fixed OpenSSL compat layer with OpenSSL master branch. Message-ID: details: https://hg.nginx.org/nginx/rev/bddd3f76e3e5 branches: changeset: 9113:bddd3f76e3e5 user: Sergey Kandaurov date: Tue May 23 00:45:18 2023 +0400 description: 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. diffstat: src/event/quic/ngx_event_quic_openssl_compat.h | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diffs (13 lines): diff -r d59277dd3d8c -r bddd3f76e3e5 src/event/quic/ngx_event_quic_openssl_compat.h --- a/src/event/quic/ngx_event_quic_openssl_compat.h Mon May 22 15:59:42 2023 +0400 +++ b/src/event/quic/ngx_event_quic_openssl_compat.h Tue May 23 00:45:18 2023 +0400 @@ -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 From mdounin at mdounin.ru Mon May 22 23:43:47 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 02:43:47 +0300 Subject: [PATCH 2 of 6] Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key In-Reply-To: <4a3a451716ba26f8fc4b.1684785134@enoparse.local> References: <4a3a451716ba26f8fc4b.1684785134@enoparse.local> Message-ID: Hello! On Mon, May 22, 2023 at 11:52:14PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1684773874 -14400 > # Mon May 22 20:44:34 2023 +0400 > # Node ID 4a3a451716ba26f8fc4be1ccc88dd8596101a6b2 > # Parent 231b14e2041afed10f5c2e6f62aad298854a6379 > Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key. > > diff --git a/ssl_certificate.t b/ssl_certificate.t > --- a/ssl_certificate.t > +++ b/ssl_certificate.t > @@ -171,6 +171,8 @@ local $TODO = 'no TLSv1.3 sessions, old > 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(); > +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' > + if $IO::Socket::SSL::VERSION < 1.965; > > like(get('default', 8080, $s), qr/default:r/, 'session reused'); > Should be: diff -r a797d7428fa5 ssl_certificate.t --- a/ssl_certificate.t Thu May 18 18:07:19 2023 +0300 +++ b/ssl_certificate.t Mon May 22 22:20:59 2023 +0000 @@ -177,6 +177,8 @@ TODO: { # ticket key name mismatch prevents session resumption local $TODO = 'not yet' unless $t->has_version('1.23.2'); +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' + if $IO::Socket::SSL::VERSION < 1.965; like(get('default', 8081, $s), qr/default:r/, 'session id context match'); as the "session reused" is passed regardless of SSL_session_key presence. > diff --git a/stream_ssl_certificate.t b/stream_ssl_certificate.t > --- a/stream_ssl_certificate.t > +++ b/stream_ssl_certificate.t > @@ -148,6 +148,8 @@ local $TODO = 'no TLSv1.3 sessions, old > 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(); > +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' > + if $IO::Socket::SSL::VERSION < 1.965; > > like(get('default', 8080, $s), qr/default:r/, 'session reused'); > The same here. Additionally, ssl_session_ticket_key.t also uses SSL_session_key, but it currently happens to be skipped at least on CentOS 7 due to old Net::SSLeay. Given IO::Socket::SSL changes, appropriate version for it is probably 2.029. diff --git a/ssl_session_ticket_key.t b/ssl_session_ticket_key.t --- a/ssl_session_ticket_key.t +++ b/ssl_session_ticket_key.t @@ -24,6 +24,8 @@ select STDOUT; $| = 1; eval { require Net::SSLeay; die if $Net::SSLeay::VERSION < 1.86; }; plan(skip_all => 'Net::SSLeay version => 1.86 required') if $@; +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 2.029; }; +plan(skip_all => 'IO::Socket::SSL version => 2.029 required') if $@; my $t = Test::Nginx->new()->has(qw/http http_ssl socket_ssl/) ->has_daemon('openssl')->plan(2) -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon May 22 23:49:54 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 02:49:54 +0300 Subject: [PATCH 3 of 6] Tests: unbreak stream_ssl_variables.t with old IO::Socket::SSL In-Reply-To: References: Message-ID: Hello! On Mon, May 22, 2023 at 11:52:15PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1684773874 -14400 > # Mon May 22 20:44:34 2023 +0400 > # Node ID e60c76cbf2a5b0d9e1d235770d68f260cf1a4e3e > # Parent 4a3a451716ba26f8fc4be1ccc88dd8596101a6b2 > Tests: unbreak stream_ssl_variables.t with old IO::Socket::SSL. > > Do not clobber a stream object in test_tls13(). > > diff --git a/stream_ssl_variables.t b/stream_ssl_variables.t > --- a/stream_ssl_variables.t > +++ b/stream_ssl_variables.t > @@ -141,7 +141,7 @@ undef $s; > ############################################################################### > > sub test_tls13 { > - $s = stream(PeerAddr => '127.0.0.1:' . port(8443), SSL => 1); > + my $s = stream(PeerAddr => '127.0.0.1:' . port(8443), SSL => 1); > $s->read() =~ /TLSv1.3/; > } > Looks good. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue May 23 01:07:38 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 04:07:38 +0300 Subject: [PATCH 4 of 6] Tests: avoid specifying PSS in sigalgs unless in TLSv1.3 In-Reply-To: <42066e126d2ca0f6d509.1684785136@enoparse.local> References: <42066e126d2ca0f6d509.1684785136@enoparse.local> Message-ID: Hello! On Mon, May 22, 2023 at 11:52:16PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1684774417 -14400 > # Mon May 22 20:53:37 2023 +0400 > # Node ID 42066e126d2ca0f6d5095d818910559adf5d4bdc > # Parent e60c76cbf2a5b0d9e1d235770d68f260cf1a4e3e > Tests: avoid specifying PSS in sigalgs unless in TLSv1.3. > > It might happen that TLSv1.3 is disabled and PSS isn't supported as seen > on Amazon Linux (LTS). The change restores old logic before 0e1865aa9b33. > > diff --git a/ssl_certificates.t b/ssl_certificates.t > --- a/ssl_certificates.t > +++ b/ssl_certificates.t > @@ -120,8 +120,8 @@ sub get_socket { > return unless defined $type; > my $ssleay = Net::SSLeay::SSLeay(); > return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); > - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; > - $sigalgs = $type . '+SHA256' unless $type eq 'RSA'; > + my $sigalgs = $type eq 'RSA' && test_tls13() > + ? 'RSA+SHA256:PSS+SHA256' : $type . '+SHA256'; > # SSL_CTRL_SET_SIGALGS_LIST > Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) > or die("Failed to set sigalgs"); > diff --git a/ssl_stapling.t b/ssl_stapling.t > --- a/ssl_stapling.t > +++ b/ssl_stapling.t > @@ -321,8 +321,8 @@ sub staple { > return unless defined $ciphers; > my $ssleay = Net::SSLeay::SSLeay(); > return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); > - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; > - $sigalgs = $ciphers . '+SHA256' unless $ciphers eq 'RSA'; > + my $sigalgs = $ciphers eq 'RSA' && test_tls13() > + ? 'RSA+SHA256:PSS+SHA256' : $ciphers . '+SHA256'; > # SSL_CTRL_SET_SIGALGS_LIST > Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) > or die("Failed to set sigalgs"); I would rather refrain from SSL connections as in test_tls13() when creating an SSL context, hence the change. But it looks like I was wrong assuming OpenSSL handles sigalgs similarly to ciphers, and ignores unknown ones. Looking through the code suggests it instead returns an error if it sees an unknown signature algorithm, so trying to set 'RSA+SHA256:PSS+SHA256' fails if OpenSSL does not support TLSv1.3. Something like this should be enough to address this without introducing additional TLSv1.3 tests: diff -r a797d7428fa5 ssl_certificates.t --- a/ssl_certificates.t Thu May 18 18:07:19 2023 +0300 +++ b/ssl_certificates.t Tue May 23 01:03:42 2023 +0000 @@ -120,10 +120,11 @@ return unless defined $type; my $ssleay = Net::SSLeay::SSLeay(); return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; - $sigalgs = $type . '+SHA256' unless $type eq 'RSA'; + my @sigalgs = ('RSA+SHA256:PSS+SHA256', 'RSA+SHA256'); + @sigalgs = ($type . '+SHA256') unless $type eq 'RSA'; # SSL_CTRL_SET_SIGALGS_LIST - Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) + Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[0]) + or Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[1]) or die("Failed to set sigalgs"); }; diff -r a797d7428fa5 ssl_stapling.t --- a/ssl_stapling.t Thu May 18 18:07:19 2023 +0300 +++ b/ssl_stapling.t Tue May 23 01:03:42 2023 +0000 @@ -319,10 +319,11 @@ return unless defined $ciphers; my $ssleay = Net::SSLeay::SSLeay(); return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; - $sigalgs = $ciphers . '+SHA256' unless $ciphers eq 'RSA'; + my @sigalgs = ('RSA+SHA256:PSS+SHA256', 'RSA+SHA256'); + @sigalgs = ($ciphers . '+SHA256') unless $ciphers eq 'RSA'; # SSL_CTRL_SET_SIGALGS_LIST - Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) + Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[0]) + or Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[1]) or die("Failed to set sigalgs"); }; (The code basically retries with 'RSA+SHA256' if setting sigalgs to 'RSA+SHA256:PSS+SHA256'. If an error happens with ECDSA, it also retries with undefined, and then reports the error.) -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Tue May 23 01:32:47 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 23 May 2023 01:32:47 +0000 Subject: [njs] Tests: imported nginx modules tests from nginx-tests. Message-ID: details: https://hg.nginx.org/njs/rev/08a912ab9520 branches: changeset: 2129:08a912ab9520 user: Dmitry Volyntsev date: Mon May 22 17:59:47 2023 -0700 description: Tests: imported nginx modules tests from nginx-tests. diffstat: nginx/t/README | 12 + nginx/t/js.t | 391 ++++++++++++++++++++ nginx/t/js_args.t | 167 ++++++++ nginx/t/js_async.t | 225 +++++++++++ nginx/t/js_body_filter.t | 168 ++++++++ nginx/t/js_body_filter_if.t | 127 ++++++ nginx/t/js_buffer.t | 184 +++++++++ nginx/t/js_dump.t | 110 +++++ nginx/t/js_fetch.t | 710 +++++++++++++++++++++++++++++++++++++ nginx/t/js_fetch_https.t | 283 ++++++++++++++ nginx/t/js_fetch_objects.t | 500 ++++++++++++++++++++++++++ nginx/t/js_fetch_resolver.t | 231 ++++++++++++ nginx/t/js_fetch_timeout.t | 119 ++++++ nginx/t/js_fetch_verify.t | 192 ++++++++++ nginx/t/js_header_filter.t | 93 ++++ nginx/t/js_header_filter_if.t | 95 ++++ nginx/t/js_headers.t | 568 +++++++++++++++++++++++++++++ nginx/t/js_import.t | 108 +++++ nginx/t/js_import2.t | 127 ++++++ nginx/t/js_internal_redirect.t | 107 +++++ nginx/t/js_modules.t | 84 ++++ nginx/t/js_ngx.t | 94 ++++ nginx/t/js_object.t | 137 +++++++ nginx/t/js_paths.t | 110 +++++ nginx/t/js_preload_object.t | 181 +++++++++ nginx/t/js_promise.t | 201 ++++++++++ nginx/t/js_request_body.t | 110 +++++ nginx/t/js_return.t | 73 +++ nginx/t/js_subrequests.t | 636 +++++++++++++++++++++++++++++++++ nginx/t/js_var.t | 90 ++++ nginx/t/js_var2.t | 90 ++++ nginx/t/js_variables.t | 95 ++++ nginx/t/stream_js.t | 478 ++++++++++++++++++++++++ nginx/t/stream_js_buffer.t | 177 +++++++++ nginx/t/stream_js_exit.t | 152 +++++++ nginx/t/stream_js_fetch.t | 277 ++++++++++++++ nginx/t/stream_js_fetch_https.t | 404 +++++++++++++++++++++ nginx/t/stream_js_fetch_init.t | 149 +++++++ nginx/t/stream_js_import.t | 117 ++++++ nginx/t/stream_js_import2.t | 117 ++++++ nginx/t/stream_js_ngx.t | 94 ++++ nginx/t/stream_js_object.t | 98 +++++ nginx/t/stream_js_preload_object.t | 122 ++++++ nginx/t/stream_js_send.t | 186 +++++++++ nginx/t/stream_js_var.t | 75 +++ nginx/t/stream_js_var2.t | 75 +++ nginx/t/stream_js_variables.t | 84 ++++ 47 files changed, 9023 insertions(+), 0 deletions(-) diffs (truncated from 9211 to 1000 lines): diff -r e7aedbc18246 -r 08a912ab9520 nginx/t/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/README Mon May 22 17:59:47 2023 -0700 @@ -0,0 +1,12 @@ +Test suite for nginx JavaScript module. + +This test suite relies on nginx-tests repository for the test library. + +Use prove to run tests as one usually do for perl tests. Individual tests +may be run as well. + +Usage: + + $ TEST_NGINX_BINARY=/path/to/nginx prove -r -I /path/to/nginx-tests/lib/ nginx/t + +Refer to nginx-tests documentation for more details. diff -r e7aedbc18246 -r 08a912ab9520 nginx/t/js.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/js.t Mon May 22 17:59:47 2023 -0700 @@ -0,0 +1,391 @@ +#!/usr/bin/perl + +# (C) Roman Arutyunyan +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $test_method test.method; + js_set $test_version test.version; + js_set $test_addr test.addr; + js_set $test_uri test.uri; + js_set $test_var test.variable; + js_set $test_type test.type; + js_set $test_global test.global_obj; + js_set $test_log test.log; + js_set $test_internal test.sub_internal; + js_set $test_except test.except; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /method { + return 200 $test_method; + } + + location /version { + return 200 $test_version; + } + + location /addr { + return 200 $test_addr; + } + + location /uri { + return 200 $test_uri; + } + + location /var { + return 200 $test_var; + } + + location /global { + return 200 $test_global; + } + + location /body { + js_content test.request_body; + } + + location /in_file { + client_body_in_file_only on; + js_content test.request_body; + } + + location /status { + js_content test.status; + } + + location /request_body { + js_content test.request_body; + } + + location /request_body_cache { + js_content test.request_body_cache; + } + + location /send { + js_content test.send; + } + + location /return_method { + js_content test.return_method; + } + + location /type { + js_content test.type; + } + + location /log { + return 200 $test_log; + } + + location /internal { + js_content test.internal; + } + + location /sub_internal { + internal; + return 200 $test_internal; + } + + location /except { + return 200 $test_except; + } + + location /content_except { + js_content test.content_except; + } + + location /content_empty { + js_content test.content_empty; + } + } +} + +EOF + +$t->write_file('test.js', < a[v], r); + + var typ = Buffer.isBuffer(p) ? 'buffer' : (typeof p); + r.return(200, `type: \${typ}`); + } + + function log(r) { + r.log('SEE-LOG'); + } + + async function internal(r) { + let reply = await r.subrequest('/sub_internal'); + + r.return(200, `parent: \${r.internal} sub: \${reply.responseText}`); + } + + function sub_internal(r) { + return r.internal; + } + + function except(r) { + var fs = require('fs'); + fs.readFileSync(); + } + + + function content_except(r) { + JSON.parse({}.a.a); + } + + function content_empty(r) { + } + + export default {njs:test_njs, method, version, addr, uri, + variable, global_obj, status, request_body, internal, + request_body_cache, send, return_method, sub_internal, + type, log, except, content_except, content_empty}; + +EOF + +$t->try_run('no njs available')->plan(27); + +############################################################################### + +like(http_get('/method'), qr/method=GET/, 'r.method'); +like(http_get('/version'), qr/version=1.0/, 'r.httpVersion'); +like(http_get('/addr'), qr/addr=127.0.0.1/, 'r.remoteAddress'); +like(http_get('/uri'), qr/uri=\/uri/, 'r.uri'); + +like(http_get('/status'), qr/204 No Content/, 'r.status'); + +like(http_post('/body'), qr/REQ-BODY/, 'request body'); +like(http_post('/in_file'), qr/request body is in a file/, + 'request body in file'); +like(http_post_big('/body'), qr/200.*^(1234567890){1024}$/ms, + 'request body big'); + +like(http_get('/send?foo=12345&n=11&foo-2=bar&ndd=&foo-3=z'), + qr/n=foo, v=12 n=foo-2, v=ba n=foo-3, v=z/, 'r.send'); + +like(http_get('/return_method?c=200'), qr/200 OK.*\x0d\x0a?\x0d\x0a?$/s, + 'return code'); +like(http_get('/return_method?c=200&t=SEE-THIS'), qr/200 OK.*^SEE-THIS$/ms, + 'return text'); +like(http_get('/return_method?c=301&t=path'), qr/ 301 .*Location: path/s, + 'return redirect'); +like(http_get('/return_method?c=404'), qr/404 Not.*html/s, 'return error page'); +like(http_get('/return_method?c=inv'), qr/ 500 /, 'return invalid'); + +like(http_get('/type?path=variables.host'), qr/200 OK.*type: string$/s, + 'variables type'); +like(http_get('/type?path=rawVariables.host'), qr/200 OK.*type: buffer$/s, + 'rawVariables type'); + +like(http_post('/type?path=requestText'), qr/200 OK.*type: string$/s, + 'requestText type'); +like(http_post('/type?path=requestBuffer'), qr/200 OK.*type: buffer$/s, + 'requestBuffer type'); +like(http_post('/request_body_cache'), + qr/requestText:string requestBuffer:buffer$/s, 'request body cache'); + +like(http_get('/var'), qr/variable=127.0.0.1/, 'r.variables'); +like(http_get('/global'), qr/global=njs/, 'global code'); +like(http_get('/log'), qr/200 OK/, 'r.log'); + +TODO: { +local $TODO = 'not yet' unless has_version('0.7.7'); + +like(http_get('/internal'), qr/parent: false sub: true/, 'r.internal'); + +} + +http_get('/except'); +http_get('/content_except'); + +like(http_get('/content_empty'), qr/500 Internal Server Error/, + 'empty handler'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 'SEE-LOG') > 0, 'log js'); +ok(index($t->read_file('error.log'), 'at fs.readFileSync') > 0, + 'js_set backtrace'); +ok(index($t->read_file('error.log'), 'at JSON.parse') > 0, + 'js_content backtrace'); + +############################################################################### + +sub has_version { + my $need = shift; + + http_get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + +############################################################################### + +sub http_get_hdr { + my ($url, %extra) = @_; + return http(< "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + js_set $test_iter test.iter; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /iter { + return 200 $test_iter; + } + + location /keys { + js_content test.keys; + } + + location /object { + js_content test.object; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs')->plan(15); + +############################################################################### + +sub recode { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return ""; + } + + JSON::PP->new()->canonical()->encode($json); +} + +sub get_json { + http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms; + recode($1); +} + +############################################################################### + +local $TODO = 'not yet' unless has_version('0.7.6'); + +like(http_get('/iter?foo=12345&foo2=bar&nn=22&foo-3=z'), qr/12345barz/, + 'r.args iteration'); +like(http_get('/iter?foo=123&foo2=&foo3&foo4=456'), qr/123456/, + 'r.args iteration 2'); +like(http_get('/iter?foo=123&foo2=&foo3'), qr/123/, 'r.args iteration 3'); +like(http_get('/iter?foo=123&foo2='), qr/123/, 'r.args iteration 4'); +like(http_get('/iter?foo=1&foo=2'), qr/1,2/m, 'r.args iteration 5'); + +like(http_get('/keys?b=1&c=2&a=5'), qr/a,b,c/m, 'r.args sorted keys'); +like(http_get('/keys?b=1&b=2'), qr/b/m, 'r.args duplicate keys'); +like(http_get('/keys?b=1&a&c='), qr/a,b,c/m, 'r.args empty value'); + +is(get_json('/object'), '{}', 'empty object'); +is(get_json('/object?a=1&b=2&c=3'), '{"a":"1","b":"2","c":"3"}', + 'ordinary object'); +is(get_json('/object?a=1&A=2'), '{"A":"2","a":"1"}', + 'case sensitive object'); +is(get_json('/object?a=1&A=2&a=3'), '{"A":"2","a":["1","3"]}', + 'duplicate keys object'); +is(get_json('/object?%61=1&a=2'), '{"a":["1","2"]}', + 'keys percent-encoded object'); +is(get_json('/object?a=%62%63&b=%63%64'), '{"a":"bc","b":"cd"}', + 'values percent-encoded object'); +is(get_json('/object?a=%6&b=%&c=%%&d=%zz'), + '{"a":"%6","b":"%","c":"%%","d":"%zz"}', + 'values percent-encoded broken object'); + +############################################################################### + +sub has_version { + my $need = shift; + + http_get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + +############################################################################### diff -r e7aedbc18246 -r 08a912ab9520 nginx/t/js_async.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/js_async.t Mon May 22 17:59:47 2023 -0700 @@ -0,0 +1,225 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Async tests for http njs module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $test_async test.set_timeout; + js_set $context_var test.context_var; + js_set $test_set_rv_var test.set_rv_var; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /async_var { + return 200 $test_async; + } + + location /shared_ctx { + add_header H $context_var; + js_content test.shared_ctx; + } + + location /set_timeout { + js_content test.set_timeout; + } + + location /set_timeout_many { + js_content test.set_timeout_many; + } + + location /set_timeout_data { + postpone_output 0; + js_content test.set_timeout_data; + } + + location /limit_rate { + postpone_output 0; + sendfile_max_chunk 5; + js_content test.limit_rate; + } + + location /async_content { + js_content test.async_content; + } + + location /set_rv_var { + return 200 $test_set_rv_var; + } + } +} + +EOF + +$t->write_file('test.js', < {resolve(x)}).then(v => v).then(v => v); + } + + async function async_content(r) { + const a1 = await pr('A'); + const a2 = await pr('B'); + + r.return(200, `retval: \${a1 + a2}`); + } + + async function set_rv_var(r) { + const a1 = await pr(10); + const a2 = await pr(20); + + r.setReturnValue(`retval: \${a1 + a2}`); + } + + export default {njs:test_njs, set_timeout, set_timeout_data, + set_timeout_many, context_var, shared_ctx, limit_rate, + async_content, set_rv_var}; + +EOF + +$t->try_run('no njs available')->plan(9); + +############################################################################### + +like(http_get('/set_timeout'), qr/Content-Type: foo/, 'setTimeout'); +like(http_get('/set_timeout_many'), qr/Content-Type: reply/, 'setTimeout many'); +like(http_get('/set_timeout_data'), qr/123456789/, 'setTimeout data'); +like(http_get('/shared_ctx?a=xxx'), qr/H: xxx/, 'shared context'); +like(http_get('/limit_rate'), qr/A{50}/, 'limit_rate'); + +like(http_get('/async_content'), qr/retval: AB/, 'async content'); +like(http_get('/set_rv_var'), qr/retval: 30/, 'set return value variable'); + +http_get('/async_var'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 'pending events') > 0, + 'pending js events'); +ok(index($t->read_file('error.log'), 'async operation inside') > 0, + 'async op in var handler'); + +############################################################################### diff -r e7aedbc18246 -r 08a912ab9520 nginx/t/js_body_filter.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/js_body_filter.t Mon May 22 17:59:47 2023 -0700 @@ -0,0 +1,168 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, body filter. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /append { + js_body_filter test.append; + proxy_pass http://127.0.0.1:8081/source; + } + + location /buffer_type { + js_body_filter test.buffer_type buffer_type=buffer; + proxy_pass http://127.0.0.1:8081/source; + } + + location /forward { + js_body_filter test.forward buffer_type=string; + proxy_pass http://127.0.0.1:8081/source; + } + + location /filter { + proxy_buffering off; + js_body_filter test.filter; + proxy_pass http://127.0.0.1:8081/source; + } + + location /prepend { + js_body_filter test.prepend; + proxy_pass http://127.0.0.1:8081/source; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /source { + postpone_output 1; + js_content test.source; + } + } +} + +EOF + +$t->write_file('test.js', <= Number(r.args.len)) { + r.sendBuffer(`\${data}|`, flags); + + if (r.args.dup && !flags.last) { + r.sendBuffer(data, flags); + } + } + } + + function forward(r, data, flags) { + r.sendBuffer(data, flags); + } + + function prepend(r, data, flags) { + r.sendBuffer("XXX"); + r.sendBuffer(data, flags); + r.done(); + } + + export default {njs: test_njs, append, buffer_type, filter, forward, + prepend, source}; + +EOF + +$t->try_run('no njs body filter')->plan(6); + +############################################################################### + +like(http_get('/append'), qr/AAABBCDDDDXXX/, 'append'); +like(http_get('/buffer_type'), qr/AAABBCDDDD/, 'buffer type'); +like(http_get('/forward'), qr/AAABBCDDDD/, 'forward'); +like(http_get('/filter?len=3'), qr/AAA|DDDD|/, 'filter 3'); +like(http_get('/filter?len=2&dup=1'), qr/AAA|AAABB|BBDDDD|DDDD/, + 'filter 2 dup'); +like(http_get('/prepend'), qr/XXXAAABBCDDDD/, 'prepend'); + +############################################################################### diff -r e7aedbc18246 -r 08a912ab9520 nginx/t/js_body_filter_if.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/js_body_filter_if.t Mon May 22 17:59:47 2023 -0700 @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, body filter, if context. + +############################################################################### + +use warnings; +use strict; + +use Test::More; From mdounin at mdounin.ru Tue May 23 02:17:50 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 05:17:50 +0300 Subject: [PATCH 5 of 6] Tests: added missing socket_ssl_alpn guard in mail_ssl.t In-Reply-To: <633613b924b957fc18dd.1684785137@enoparse.local> References: <633613b924b957fc18dd.1684785137@enoparse.local> Message-ID: Hello! On Mon, May 22, 2023 at 11:52:17PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1684784660 -14400 > # Mon May 22 23:44:20 2023 +0400 > # Node ID 633613b924b957fc18dde972ff346fe92b9e823e > # Parent 42066e126d2ca0f6d5095d818910559adf5d4bdc > Tests: added missing socket_ssl_alpn guard in mail_ssl.t. > > diff --git a/mail_ssl.t b/mail_ssl.t > --- a/mail_ssl.t > +++ b/mail_ssl.t > @@ -164,6 +164,10 @@ like($s->socket()->dump_peer_certificate > > # alpn > > +TODO: { > +local $TODO = 'no ALPN support in IO::Socket::SSL' > + unless $t->has_feature('socket_ssl_alpn'); > + > $s = Test::Nginx::IMAP->new( > PeerAddr => '127.0.0.1:' . port(8148), > SSL => 1, > @@ -171,6 +175,8 @@ like($s->socket()->dump_peer_certificate > ); > $s->ok('alpn'); > > +} > + This will unexpectedly succeed if IO::Socket:SSL do not have ALPN support at all (and will simply ignore SSL_alpn_protocols). Probably skipping ALPN tests if there is no socket_ssl_alpn is the way to go: diff -r a797d7428fa5 mail_ssl.t --- a/mail_ssl.t Thu May 18 18:07:19 2023 +0300 +++ b/mail_ssl.t Tue May 23 02:17:06 2023 +0000 @@ -164,6 +164,17 @@ # alpn + +SKIP: { +skip 'LibreSSL too old', 2 + if $t->has_module('LibreSSL') + and not $t->has_feature('libressl:3.4.0'); +skip 'OpenSSL too old', 2 + if $t->has_module('OpenSSL') + and not $t->has_feature('openssl:1.1.0'); +skip 'no ALPN support in IO::Socket::SSL', 2 + unless $t->has_feature('socket_ssl_alpn'); + $s = Test::Nginx::IMAP->new( PeerAddr => '127.0.0.1:' . port(8148), SSL => 1, @@ -171,18 +182,8 @@ ); $s->ok('alpn'); -SKIP: { -skip 'LibreSSL too old', 1 - if $t->has_module('LibreSSL') - and not $t->has_feature('libressl:3.4.0'); -skip 'OpenSSL too old', 1 - if $t->has_module('OpenSSL') - and not $t->has_feature('openssl:1.1.0'); - TODO: { local $TODO = 'not yet' unless $t->has_version('1.21.4'); -local $TODO = 'no ALPN support in IO::Socket::SSL' - unless $t->has_feature('socket_ssl_alpn'); $s = Test::Nginx::IMAP->new( PeerAddr => '127.0.0.1:' . port(8148), > SKIP: { > skip 'LibreSSL too old', 1 > if $t->has_module('LibreSSL') > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue May 23 02:38:08 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 05:38:08 +0300 Subject: [PATCH 6 of 6] Tests: added missing socket_ssl_reused prerequisites In-Reply-To: <193b129568708d6689c7.1684785138@enoparse.local> References: <193b129568708d6689c7.1684785138@enoparse.local> Message-ID: Hello! On Mon, May 22, 2023 at 11:52:18PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1684784712 -14400 > # Mon May 22 23:45:12 2023 +0400 > # Node ID 193b129568708d6689c7cb8707f46bca7280a0d0 > # Parent 633613b924b957fc18dde972ff346fe92b9e823e > Tests: added missing socket_ssl_reused prerequisites. > > diff --git a/mail_ssl_session_reuse.t b/mail_ssl_session_reuse.t > --- a/mail_ssl_session_reuse.t > +++ b/mail_ssl_session_reuse.t > @@ -27,7 +27,7 @@ select STDOUT; $| = 1; > local $SIG{PIPE} = 'IGNORE'; > > my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap socket_ssl_sslversion/) > - ->has_daemon('openssl')->plan(7); > + ->has(qw/socket_ssl_reused/)->has_daemon('openssl')->plan(7); > > $t->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/stream_ssl_session_reuse.t b/stream_ssl_session_reuse.t > --- a/stream_ssl_session_reuse.t > +++ b/stream_ssl_session_reuse.t > @@ -27,7 +27,7 @@ select STDERR; $| = 1; > select STDOUT; $| = 1; > > my $t = Test::Nginx->new()->has(qw/stream stream_ssl socket_ssl_sslversion/) > - ->has_daemon('openssl')->plan(7); > + ->has(qw/socket_ssl_reused/)->has_daemon('openssl')->plan(7); > > $t->write_file_expand('nginx.conf', <<'EOF'); > Style nitpicking: diff --git a/mail_ssl_session_reuse.t b/mail_ssl_session_reuse.t --- a/mail_ssl_session_reuse.t +++ b/mail_ssl_session_reuse.t @@ -26,7 +26,8 @@ select STDOUT; $| = 1; local $SIG{PIPE} = 'IGNORE'; -my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap socket_ssl_sslversion/) +my $t = Test::Nginx->new() + ->has(qw/mail mail_ssl imap socket_ssl_sslversion socket_ssl_reused/) ->has_daemon('openssl')->plan(7); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/stream_ssl_session_reuse.t b/stream_ssl_session_reuse.t --- a/stream_ssl_session_reuse.t +++ b/stream_ssl_session_reuse.t @@ -26,7 +26,8 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream stream_ssl socket_ssl_sslversion/) +my $t = Test::Nginx->new() + ->has(qw/stream stream_ssl socket_ssl_sslversion socket_ssl_reused/) ->has_daemon('openssl')->plan(7); $t->write_file_expand('nginx.conf', <<'EOF'); Also, just using socket_ssl_reused should be enough, since get_session_reused() appeared in IO::Socket::SSL 2.057, and this implies that get_sslversion(), which appeared in IO::Socket::SSL 1.964, is also available. I don't object being explicit and listing both though, given that this order is not obvious (in contrast to SNI vs. ALPN one). Otherwise looks good. -- Maxim Dounin http://mdounin.ru/ From kadhar2006 at gmail.com Tue May 23 07:04:31 2023 From: kadhar2006 at gmail.com (abdul kadhar) Date: Tue, 23 May 2023 12:34:31 +0530 Subject: How to create same location to point different proxy server In-Reply-To: References: Message-ID: Hi, I need to know how to configure nginx for below scenario. I have configured the Load Balancer(Nginx) for ignition. I have created two upstream(ignapp & ignProdapp) server. For ignapp upstream it is working fine. I need to configure the below code for upstream server(ignProdapp) in the same configuration file how to do this. Below is the sample configuration for the server ignapp. Can you please help me how to configure the below settings for another upstream server. Below is the config setting for ignapp same way I need to do it for creating location ~ /data/, location ~ /system/ , location ~ /res/ , location ~ /idp/, location ~ /.well-known/ for ignappPro Upstream server . Upstream ignapp{ ip_hash; server 10.10.10.11:8088; } upstream ignappPro { ip_hash; server 10.10.10.12:8088; } server { listen 80; server_name www.abdul.com; location ~ /data/ { proxy_pass ignapp/data/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /system/ { proxy_pass ​ignapp/system/; tp_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /res/ { proxy_pass ignapp/res/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /idp/ { proxy_pass ​ignapp/idp/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } location ~ /.well-known/ { proxy_pass ignapp/.well-known/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } # Here we are setting the protocol type, address, port, and uri (optional) that will be the destination of our proxied server: location / { proxy_pass ​ignapp/data/perspective/client/demo/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_pass_request_headers on; proxy_cache_bypass $http_upgrade; } } -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Tue May 23 10:34:54 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 23 May 2023 14:34:54 +0400 Subject: [PATCH 2 of 6] Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key In-Reply-To: References: <4a3a451716ba26f8fc4b.1684785134@enoparse.local> Message-ID: > On 23 May 2023, at 03:43, Maxim Dounin wrote: > > Hello! > > On Mon, May 22, 2023 at 11:52:14PM +0400, Sergey Kandaurov wrote: > >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1684773874 -14400 >> # Mon May 22 20:44:34 2023 +0400 >> # Node ID 4a3a451716ba26f8fc4be1ccc88dd8596101a6b2 >> # Parent 231b14e2041afed10f5c2e6f62aad298854a6379 >> Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key. >> >> diff --git a/ssl_certificate.t b/ssl_certificate.t >> --- a/ssl_certificate.t >> +++ b/ssl_certificate.t >> @@ -171,6 +171,8 @@ local $TODO = 'no TLSv1.3 sessions, old >> 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(); >> +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' >> + if $IO::Socket::SSL::VERSION < 1.965; >> >> like(get('default', 8080, $s), qr/default:r/, 'session reused'); >> > > Should be: > > diff -r a797d7428fa5 ssl_certificate.t > --- a/ssl_certificate.t Thu May 18 18:07:19 2023 +0300 > +++ b/ssl_certificate.t Mon May 22 22:20:59 2023 +0000 > @@ -177,6 +177,8 @@ > TODO: { > # ticket key name mismatch prevents session resumption > local $TODO = 'not yet' unless $t->has_version('1.23.2'); > +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' > + if $IO::Socket::SSL::VERSION < 1.965; > > like(get('default', 8081, $s), qr/default:r/, 'session id context match'); > > as the "session reused" is passed regardless of SSL_session_key > presence. I agree. The reason why it was done so is to cover an unrelated issue with TLSv1.2 session reuse that happens in ssl_certificate.t with IO::Socket::SSL < 1.963, which results in inability to restore the session. Probably that is because of improper behavior in stop_SSL. Well, it doesn't happen if remove http_end() to stop closing a client socket. Though, given that this bug doesn't appear on real distros that have IO::Socket::SSL [1.84..1.963] (inclusive), the range where it manifests, so that it doesn't annoy in real life, then we can simply ignore it. > >> diff --git a/stream_ssl_certificate.t b/stream_ssl_certificate.t >> --- a/stream_ssl_certificate.t >> +++ b/stream_ssl_certificate.t >> @@ -148,6 +148,8 @@ local $TODO = 'no TLSv1.3 sessions, old >> 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(); >> +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' >> + if $IO::Socket::SSL::VERSION < 1.965; >> >> like(get('default', 8080, $s), qr/default:r/, 'session reused'); >> > > The same here. > > Additionally, ssl_session_ticket_key.t also uses SSL_session_key, > but it currently happens to be skipped at least on CentOS 7 due to > old Net::SSLeay. Given IO::Socket::SSL changes, appropriate > version for it is probably 2.029. > > diff --git a/ssl_session_ticket_key.t b/ssl_session_ticket_key.t > --- a/ssl_session_ticket_key.t > +++ b/ssl_session_ticket_key.t > @@ -24,6 +24,8 @@ select STDOUT; $| = 1; > > eval { require Net::SSLeay; die if $Net::SSLeay::VERSION < 1.86; }; > plan(skip_all => 'Net::SSLeay version => 1.86 required') if $@; > +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 2.029; }; > +plan(skip_all => 'IO::Socket::SSL version => 2.029 required') if $@; > > my $t = Test::Nginx->new()->has(qw/http http_ssl socket_ssl/) > ->has_daemon('openssl')->plan(2) > Indeed, it doesn't happen to work with old IO::Socket::SSL versions, thanks for catching this. By manual testing, it appears that 2.030 is required to work. The relevant git log: remove internal sub session_cache and access cache directly (faster) This also fixes a problem when SSL_session_key was used, which was introduced in 2.029 Additionally, if TLSv1.3 was negotiated, for session reuse to work, 2.061 is required (same as for rest TLSv1.3 session reuse tests). Moved this part to the separate change: # HG changeset patch # User Sergey Kandaurov # Date 1684835827 -14400 # Tue May 23 13:57:07 2023 +0400 # Node ID 5dc8aa4446484456eb1310e63b092f1d08258574 # Parent d570dbcad925499d968006ea509798234df09248 Tests: unbreak ssl_session_ticket_key.t with old IO::Socket::SSL. Once ssl_session_ticket_key.t was rewritten using IO::Socket::SSL, it requires checks for SSL_session_key and TLSv1.3 session reuse support. While SSL_session_key has been introduced in IO::Socket::SSL 1.965, further improvements appeared in 2.030 are needed for tests to work. diff --git a/ssl_session_ticket_key.t b/ssl_session_ticket_key.t --- a/ssl_session_ticket_key.t +++ b/ssl_session_ticket_key.t @@ -24,6 +24,8 @@ select STDOUT; $| = 1; eval { require Net::SSLeay; die if $Net::SSLeay::VERSION < 1.86; }; plan(skip_all => 'Net::SSLeay version => 1.86 required') if $@; +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 2.030; }; +plan(skip_all => 'IO::Socket::SSL version => 2.030 required') if $@; my $t = Test::Nginx->new()->has(qw/http http_ssl socket_ssl/) ->has_daemon('openssl')->plan(2) @@ -99,6 +101,8 @@ select undef, undef, undef, 2.5; local $TODO = 'no TLSv1.3 sessions in LibreSSL' if $t->has_module('LibreSSL') && test_tls13(); +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' + if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); cmp_ok(get_ticket_key_name(), 'ne', $key, 'ticket key next'); -- Sergey Kandaurov From pluknet at nginx.com Tue May 23 12:23:05 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 23 May 2023 16:23:05 +0400 Subject: [PATCH 4 of 6] Tests: avoid specifying PSS in sigalgs unless in TLSv1.3 In-Reply-To: References: <42066e126d2ca0f6d509.1684785136@enoparse.local> Message-ID: <7251BFED-C6F0-4D89-B829-1477F2E22B09@nginx.com> > On 23 May 2023, at 05:07, Maxim Dounin wrote: > > Hello! > > On Mon, May 22, 2023 at 11:52:16PM +0400, Sergey Kandaurov wrote: > >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1684774417 -14400 >> # Mon May 22 20:53:37 2023 +0400 >> # Node ID 42066e126d2ca0f6d5095d818910559adf5d4bdc >> # Parent e60c76cbf2a5b0d9e1d235770d68f260cf1a4e3e >> Tests: avoid specifying PSS in sigalgs unless in TLSv1.3. >> >> It might happen that TLSv1.3 is disabled and PSS isn't supported as seen >> on Amazon Linux (LTS). The change restores old logic before 0e1865aa9b33. >> >> diff --git a/ssl_certificates.t b/ssl_certificates.t >> --- a/ssl_certificates.t >> +++ b/ssl_certificates.t >> @@ -120,8 +120,8 @@ sub get_socket { >> return unless defined $type; >> my $ssleay = Net::SSLeay::SSLeay(); >> return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); >> - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; >> - $sigalgs = $type . '+SHA256' unless $type eq 'RSA'; >> + my $sigalgs = $type eq 'RSA' && test_tls13() >> + ? 'RSA+SHA256:PSS+SHA256' : $type . '+SHA256'; >> # SSL_CTRL_SET_SIGALGS_LIST >> Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) >> or die("Failed to set sigalgs"); >> diff --git a/ssl_stapling.t b/ssl_stapling.t >> --- a/ssl_stapling.t >> +++ b/ssl_stapling.t >> @@ -321,8 +321,8 @@ sub staple { >> return unless defined $ciphers; >> my $ssleay = Net::SSLeay::SSLeay(); >> return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); >> - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; >> - $sigalgs = $ciphers . '+SHA256' unless $ciphers eq 'RSA'; >> + my $sigalgs = $ciphers eq 'RSA' && test_tls13() >> + ? 'RSA+SHA256:PSS+SHA256' : $ciphers . '+SHA256'; >> # SSL_CTRL_SET_SIGALGS_LIST >> Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) >> or die("Failed to set sigalgs"); > > I would rather refrain from SSL connections as in test_tls13() > when creating an SSL context, hence the change. I don't like this as well and prefer to avoid if possible. > > But it looks like I was wrong assuming OpenSSL handles sigalgs > similarly to ciphers, and ignores unknown ones. Looking through > the code suggests it instead returns an error if it sees an > unknown signature algorithm, so trying to set > 'RSA+SHA256:PSS+SHA256' fails if OpenSSL does not support TLSv1.3. > > Something like this should be enough to address this without > introducing additional TLSv1.3 tests: > Applied, tnx. > diff -r a797d7428fa5 ssl_certificates.t > --- a/ssl_certificates.t Thu May 18 18:07:19 2023 +0300 > +++ b/ssl_certificates.t Tue May 23 01:03:42 2023 +0000 > @@ -120,10 +120,11 @@ > return unless defined $type; > my $ssleay = Net::SSLeay::SSLeay(); > return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); > - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; > - $sigalgs = $type . '+SHA256' unless $type eq 'RSA'; > + my @sigalgs = ('RSA+SHA256:PSS+SHA256', 'RSA+SHA256'); > + @sigalgs = ($type . '+SHA256') unless $type eq 'RSA'; > # SSL_CTRL_SET_SIGALGS_LIST > - Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) > + Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[0]) > + or Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[1]) > or die("Failed to set sigalgs"); > }; > > diff -r a797d7428fa5 ssl_stapling.t > --- a/ssl_stapling.t Thu May 18 18:07:19 2023 +0300 > +++ b/ssl_stapling.t Tue May 23 01:03:42 2023 +0000 > @@ -319,10 +319,11 @@ > return unless defined $ciphers; > my $ssleay = Net::SSLeay::SSLeay(); > return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); > - my $sigalgs = 'RSA+SHA256:PSS+SHA256'; > - $sigalgs = $ciphers . '+SHA256' unless $ciphers eq 'RSA'; > + my @sigalgs = ('RSA+SHA256:PSS+SHA256', 'RSA+SHA256'); > + @sigalgs = ($ciphers . '+SHA256') unless $ciphers eq 'RSA'; > # SSL_CTRL_SET_SIGALGS_LIST > - Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs) > + Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[0]) > + or Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[1]) > or die("Failed to set sigalgs"); > }; > > > (The code basically retries with 'RSA+SHA256' if setting sigalgs > to 'RSA+SHA256:PSS+SHA256'. If an error happens with ECDSA, it > also retries with undefined, and then reports the error.) -- Sergey Kandaurov From arut at nginx.com Tue May 23 12:50:04 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Tue, 23 May 2023 12:50:04 +0000 Subject: [nginx] Closed the quic branch. Message-ID: details: https://hg.nginx.org/nginx/rev/0f704ca791ad branches: quic changeset: 9114:0f704ca791ad user: Roman Arutyunyan date: Tue May 23 16:05:33 2023 +0400 description: Closed the quic branch. From pluknet at nginx.com Tue May 23 12:55:44 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 23 May 2023 16:55:44 +0400 Subject: [PATCH 5 of 6] Tests: added missing socket_ssl_alpn guard in mail_ssl.t In-Reply-To: References: <633613b924b957fc18dd.1684785137@enoparse.local> Message-ID: > On 23 May 2023, at 06:17, Maxim Dounin wrote: > > Hello! > > On Mon, May 22, 2023 at 11:52:17PM +0400, Sergey Kandaurov wrote: > >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1684784660 -14400 >> # Mon May 22 23:44:20 2023 +0400 >> # Node ID 633613b924b957fc18dde972ff346fe92b9e823e >> # Parent 42066e126d2ca0f6d5095d818910559adf5d4bdc >> Tests: added missing socket_ssl_alpn guard in mail_ssl.t. >> >> diff --git a/mail_ssl.t b/mail_ssl.t >> --- a/mail_ssl.t >> +++ b/mail_ssl.t >> @@ -164,6 +164,10 @@ like($s->socket()->dump_peer_certificate >> >> # alpn >> >> +TODO: { >> +local $TODO = 'no ALPN support in IO::Socket::SSL' >> + unless $t->has_feature('socket_ssl_alpn'); >> + >> $s = Test::Nginx::IMAP->new( >> PeerAddr => '127.0.0.1:' . port(8148), >> SSL => 1, >> @@ -171,6 +175,8 @@ like($s->socket()->dump_peer_certificate >> ); >> $s->ok('alpn'); >> >> +} >> + > > This will unexpectedly succeed if IO::Socket:SSL do not have ALPN > support at all (and will simply ignore SSL_alpn_protocols). > > Probably skipping ALPN tests if there is no socket_ssl_alpn is the > way to go: Indeed. Applied, thanks. > > diff -r a797d7428fa5 mail_ssl.t > --- a/mail_ssl.t Thu May 18 18:07:19 2023 +0300 > +++ b/mail_ssl.t Tue May 23 02:17:06 2023 +0000 > @@ -164,6 +164,17 @@ > > # alpn > > + > +SKIP: { > +skip 'LibreSSL too old', 2 > + if $t->has_module('LibreSSL') > + and not $t->has_feature('libressl:3.4.0'); > +skip 'OpenSSL too old', 2 > + if $t->has_module('OpenSSL') > + and not $t->has_feature('openssl:1.1.0'); > +skip 'no ALPN support in IO::Socket::SSL', 2 > + unless $t->has_feature('socket_ssl_alpn'); > + > $s = Test::Nginx::IMAP->new( > PeerAddr => '127.0.0.1:' . port(8148), > SSL => 1, > @@ -171,18 +182,8 @@ > ); > $s->ok('alpn'); > > -SKIP: { > -skip 'LibreSSL too old', 1 > - if $t->has_module('LibreSSL') > - and not $t->has_feature('libressl:3.4.0'); > -skip 'OpenSSL too old', 1 > - if $t->has_module('OpenSSL') > - and not $t->has_feature('openssl:1.1.0'); > - > TODO: { > local $TODO = 'not yet' unless $t->has_version('1.21.4'); > -local $TODO = 'no ALPN support in IO::Socket::SSL' > - unless $t->has_feature('socket_ssl_alpn'); > > $s = Test::Nginx::IMAP->new( > PeerAddr => '127.0.0.1:' . port(8148), > -- Sergey Kandaurov From mdounin at mdounin.ru Tue May 23 13:31:46 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 16:31:46 +0300 Subject: [PATCH 2 of 6] Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key In-Reply-To: References: <4a3a451716ba26f8fc4b.1684785134@enoparse.local> Message-ID: Hello! On Tue, May 23, 2023 at 02:34:54PM +0400, Sergey Kandaurov wrote: > > On 23 May 2023, at 03:43, Maxim Dounin wrote: > > > > Hello! > > > > On Mon, May 22, 2023 at 11:52:14PM +0400, Sergey Kandaurov wrote: > > > >> # HG changeset patch > >> # User Sergey Kandaurov > >> # Date 1684773874 -14400 > >> # Mon May 22 20:44:34 2023 +0400 > >> # Node ID 4a3a451716ba26f8fc4be1ccc88dd8596101a6b2 > >> # Parent 231b14e2041afed10f5c2e6f62aad298854a6379 > >> Tests: unbreak tests with IO::Socket:SSL lacking SSL_session_key. > >> > >> diff --git a/ssl_certificate.t b/ssl_certificate.t > >> --- a/ssl_certificate.t > >> +++ b/ssl_certificate.t > >> @@ -171,6 +171,8 @@ local $TODO = 'no TLSv1.3 sessions, old > >> 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(); > >> +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' > >> + if $IO::Socket::SSL::VERSION < 1.965; > >> > >> like(get('default', 8080, $s), qr/default:r/, 'session reused'); > >> > > > > Should be: > > > > diff -r a797d7428fa5 ssl_certificate.t > > --- a/ssl_certificate.t Thu May 18 18:07:19 2023 +0300 > > +++ b/ssl_certificate.t Mon May 22 22:20:59 2023 +0000 > > @@ -177,6 +177,8 @@ > > TODO: { > > # ticket key name mismatch prevents session resumption > > local $TODO = 'not yet' unless $t->has_version('1.23.2'); > > +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' > > + if $IO::Socket::SSL::VERSION < 1.965; > > > > like(get('default', 8081, $s), qr/default:r/, 'session id context match'); > > > > as the "session reused" is passed regardless of SSL_session_key > > presence. > > I agree. The reason why it was done so is to cover an unrelated > issue with TLSv1.2 session reuse that happens in ssl_certificate.t > with IO::Socket::SSL < 1.963, which results in inability to restore > the session. Probably that is because of improper behavior in stop_SSL. > Well, it doesn't happen if remove http_end() to stop closing a client socket. > Though, given that this bug doesn't appear on real distros that have > IO::Socket::SSL [1.84..1.963] (inclusive), the range where it manifests, > so that it doesn't annoy in real life, then we can simply ignore it. We can use SKIP instead if there are unexpected issues with older IO::Socket::SSL versions, or even introduce socket_ssl_session_key feature and skip the entire test. But if this does not happen on real distros, this probably don't worth the effort. For sure there are IO::Socket::SSL (as well as Net::SSLeay and OpenSSL) versions with bugs, but trying to work around all possible bugs is certainly not the goal. > > > >> diff --git a/stream_ssl_certificate.t b/stream_ssl_certificate.t > >> --- a/stream_ssl_certificate.t > >> +++ b/stream_ssl_certificate.t > >> @@ -148,6 +148,8 @@ local $TODO = 'no TLSv1.3 sessions, old > >> 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(); > >> +local $TODO = 'no SSL_session_key, old IO::Socket::SSL' > >> + if $IO::Socket::SSL::VERSION < 1.965; > >> > >> like(get('default', 8080, $s), qr/default:r/, 'session reused'); > >> > > > > The same here. > > > > Additionally, ssl_session_ticket_key.t also uses SSL_session_key, > > but it currently happens to be skipped at least on CentOS 7 due to > > old Net::SSLeay. Given IO::Socket::SSL changes, appropriate > > version for it is probably 2.029. > > > > diff --git a/ssl_session_ticket_key.t b/ssl_session_ticket_key.t > > --- a/ssl_session_ticket_key.t > > +++ b/ssl_session_ticket_key.t > > @@ -24,6 +24,8 @@ select STDOUT; $| = 1; > > > > eval { require Net::SSLeay; die if $Net::SSLeay::VERSION < 1.86; }; > > plan(skip_all => 'Net::SSLeay version => 1.86 required') if $@; > > +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 2.029; }; > > +plan(skip_all => 'IO::Socket::SSL version => 2.029 required') if $@; > > > > my $t = Test::Nginx->new()->has(qw/http http_ssl socket_ssl/) > > ->has_daemon('openssl')->plan(2) > > > > Indeed, it doesn't happen to work with old IO::Socket::SSL versions, > thanks for catching this. > By manual testing, it appears that 2.030 is required to work. > The relevant git log: > remove internal sub session_cache and access cache directly (faster) > This also fixes a problem when SSL_session_key was used, which was > introduced in 2.029 > > Additionally, if TLSv1.3 was negotiated, for session reuse to work, > 2.061 is required (same as for rest TLSv1.3 session reuse tests). > > Moved this part to the separate change: > > # HG changeset patch > # User Sergey Kandaurov > # Date 1684835827 -14400 > # Tue May 23 13:57:07 2023 +0400 > # Node ID 5dc8aa4446484456eb1310e63b092f1d08258574 > # Parent d570dbcad925499d968006ea509798234df09248 > Tests: unbreak ssl_session_ticket_key.t with old IO::Socket::SSL. > > Once ssl_session_ticket_key.t was rewritten using IO::Socket::SSL, > it requires checks for SSL_session_key and TLSv1.3 session reuse support. > While SSL_session_key has been introduced in IO::Socket::SSL 1.965, > further improvements appeared in 2.030 are needed for tests to work. > > diff --git a/ssl_session_ticket_key.t b/ssl_session_ticket_key.t > --- a/ssl_session_ticket_key.t > +++ b/ssl_session_ticket_key.t > @@ -24,6 +24,8 @@ select STDOUT; $| = 1; > > eval { require Net::SSLeay; die if $Net::SSLeay::VERSION < 1.86; }; > plan(skip_all => 'Net::SSLeay version => 1.86 required') if $@; > +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 2.030; }; > +plan(skip_all => 'IO::Socket::SSL version => 2.030 required') if $@; > > my $t = Test::Nginx->new()->has(qw/http http_ssl socket_ssl/) > ->has_daemon('openssl')->plan(2) > @@ -99,6 +101,8 @@ select undef, undef, undef, 2.5; > > local $TODO = 'no TLSv1.3 sessions in LibreSSL' > if $t->has_module('LibreSSL') && test_tls13(); > +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' > + if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); > > cmp_ok(get_ticket_key_name(), 'ne', $key, 'ticket key next'); > This needs Net::SSLeay 1.88 also then? The usual pattern is: 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(); local $TODO = 'no TLSv1.3 sessions in LibreSSL' if $t->has_module('LibreSSL') && test_tls13(); -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue May 23 14:21:57 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 17:21:57 +0300 Subject: nginx 1.25.0 changes draft Message-ID: Hello! Changes with nginx 1.25.0 23 May 2023 *) Feature: experimental HTTP/3 support. Изменения в nginx 1.25.0 23.05.2023 *) Добавление: экспериментальная поддержка HTTP/3. -- Maxim Dounin http://mdounin.ru/ From yar at nginx.com Tue May 23 14:37:43 2023 From: yar at nginx.com (=?utf-8?q?Yaroslav_Zhuravlev?=) Date: Tue, 23 May 2023 15:37:43 +0100 Subject: [PATCH] Updated QUIC documentation after QUIC code merge Message-ID: <9015546a429e45497c7d.1684852663@ORK-ML-00007151> xml/en/docs/configure.xml | 4 +- xml/en/docs/http/ngx_http_core_module.xml | 4 +- xml/en/docs/http/ngx_http_v3_module.xml | 97 +----------- xml/en/docs/quic.xml | 248 +++++------------------------ xml/ru/GNUmakefile | 1 + xml/ru/docs/configure.xml | 4 +- xml/ru/docs/http/ngx_http_core_module.xml | 4 +- xml/ru/docs/http/ngx_http_v3_module.xml | 96 +----------- xml/ru/docs/index.xml | 2 +- xml/ru/docs/quic.xml | 252 ++++++++++++++++++++++++++++++ 10 files changed, 310 insertions(+), 402 deletions(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: nginx.org.patch Type: text/x-patch Size: 30571 bytes Desc: not available URL: From pluknet at nginx.com Tue May 23 14:40:53 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 23 May 2023 18:40:53 +0400 Subject: nginx 1.25.0 changes draft In-Reply-To: References: Message-ID: <919FC9DB-A7E1-4D5A-9B86-BB248F35EF42@nginx.com> > On 23 May 2023, at 18:21, Maxim Dounin wrote: > > Hello! > > > Changes with nginx 1.25.0 23 May 2023 > > *) Feature: experimental HTTP/3 support. > > > Изменения в nginx 1.25.0 23.05.2023 > > *) Добавление: экспериментальная поддержка HTTP/3. > > Looks good. -- Sergey Kandaurov From mdounin at mdounin.ru Tue May 23 15:24:57 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 23 May 2023 18:24:57 +0300 Subject: nginx 1.25.0 changes draft In-Reply-To: <919FC9DB-A7E1-4D5A-9B86-BB248F35EF42@nginx.com> References: <919FC9DB-A7E1-4D5A-9B86-BB248F35EF42@nginx.com> Message-ID: Hello! On Tue, May 23, 2023 at 06:40:53PM +0400, Sergey Kandaurov wrote: > > > On 23 May 2023, at 18:21, Maxim Dounin wrote: > > > > Hello! > > > > > > Changes with nginx 1.25.0 23 May 2023 > > > > *) Feature: experimental HTTP/3 support. > > > > > > Изменения в nginx 1.25.0 23.05.2023 > > > > *) Добавление: экспериментальная поддержка HTTP/3. > > > > > > Looks good. Pushed to: http://mdounin.ru/hg/nginx http://mdounin.ru/hg/nginx.org Release files: http://mdounin.ru/temp/nginx-1.25.0.tar.gz http://mdounin.ru/temp/nginx-1.25.0.tar.gz.asc http://mdounin.ru/temp/nginx-1.25.0.zip http://mdounin.ru/temp/nginx-1.25.0.zip.asc -- Maxim Dounin http://mdounin.ru/ From arut at nginx.com Tue May 23 15:48:16 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Tue, 23 May 2023 19:48:16 +0400 Subject: [PATCH] Configure: dump unsupported repository error and exit Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1684856745 -14400 # Tue May 23 19:45:45 2023 +0400 # Branch quic # Node ID bd25277d0ed77a90766bc2f4153538c86621cdfc # Parent a83071314a22652d4a552e72e13d18aa4c01d79e Configure: dump unsupported repository error and exit. diff --git a/auto/configure b/auto/configure --- a/auto/configure +++ b/auto/configure @@ -4,6 +4,13 @@ # Copyright (C) Nginx, Inc. +cat << END + +$0: this repository is no longer supported, see README + +END +exit 1 + LC_ALL=C export LC_ALL From thresh at nginx.com Tue May 23 15:54:43 2023 From: thresh at nginx.com (Konstantin Pavlov) Date: Tue, 23 May 2023 15:54:43 +0000 Subject: [nginx] nginx-1.25.0-RELEASE Message-ID: details: https://hg.nginx.org/nginx/rev/12dcf92b0c2c branches: changeset: 9115:12dcf92b0c2c user: Maxim Dounin date: Tue May 23 18:08:19 2023 +0300 description: nginx-1.25.0-RELEASE diffstat: docs/xml/nginx/changes.xml | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-) diffs (24 lines): diff -r bddd3f76e3e5 -r 12dcf92b0c2c docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml Tue May 23 00:45:18 2023 +0400 +++ b/docs/xml/nginx/changes.xml Tue May 23 18:08:19 2023 +0300 @@ -5,6 +5,20 @@ + + + + +экспериментальная поддержка HTTP/3. + + +experimental HTTP/3 support. + + + + + + From thresh at nginx.com Tue May 23 15:54:46 2023 From: thresh at nginx.com (Konstantin Pavlov) Date: Tue, 23 May 2023 15:54:46 +0000 Subject: [nginx] release-1.25.0 tag Message-ID: details: https://hg.nginx.org/nginx/rev/8eae1b4f1c55 branches: changeset: 9116:8eae1b4f1c55 user: Maxim Dounin date: Tue May 23 18:08:20 2023 +0300 description: release-1.25.0 tag diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff -r 12dcf92b0c2c -r 8eae1b4f1c55 .hgtags --- a/.hgtags Tue May 23 18:08:19 2023 +0300 +++ b/.hgtags Tue May 23 18:08:20 2023 +0300 @@ -472,3 +472,4 @@ a63d0a70afea96813ba6667997bc7d68b5863f0d aa901551a7ebad1e8b0f8c11cb44e3424ba29707 release-1.23.2 ff3afd1ce6a6b65057741df442adfaa71a0e2588 release-1.23.3 ac779115ed6ee4f3039e9aea414a54e560450ee2 release-1.23.4 +12dcf92b0c2c68552398f19644ce3104459807d7 release-1.25.0 From pluknet at nginx.com Tue May 23 16:08:15 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 23 May 2023 20:08:15 +0400 Subject: [PATCH] Updated QUIC documentation after QUIC code merge In-Reply-To: <9015546a429e45497c7d.1684852663@ORK-ML-00007151> References: <9015546a429e45497c7d.1684852663@ORK-ML-00007151> Message-ID: <32DD1999-ED48-49A5-A901-223BBCBE681D@nginx.com> > On 23 May 2023, at 18:37, Yaroslav Zhuravlev wrote: > > xml/en/docs/configure.xml | 4 +- > xml/en/docs/http/ngx_http_core_module.xml | 4 +- > xml/en/docs/http/ngx_http_v3_module.xml | 97 +----------- > xml/en/docs/quic.xml | 248 +++++------------------------ > xml/ru/GNUmakefile | 1 + > xml/ru/docs/configure.xml | 4 +- > xml/ru/docs/http/ngx_http_core_module.xml | 4 +- > xml/ru/docs/http/ngx_http_v3_module.xml | 96 +----------- > xml/ru/docs/index.xml | 2 +- > xml/ru/docs/quic.xml | 252 ++++++++++++++++++++++++++++++ > 10 files changed, 310 insertions(+), 402 deletions(-) > > > # HG changeset patch > # User Yaroslav Zhuravlev > # Date 1684852637 -3600 > # Tue May 23 15:37:17 2023 +0100 > # Node ID 9015546a429e45497c7d189e0f3a77423254e019 > # Parent c7bf5c9a1174695b7505b8d1b7a94fe6abff318f > Updated QUIC documentation after QUIC code merge. Looks good. [..] -- Sergey Kandaurov From pluknet at nginx.com Tue May 23 16:29:03 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 23 May 2023 20:29:03 +0400 Subject: [PATCH] Configure: dump unsupported repository error and exit In-Reply-To: References: Message-ID: > On 23 May 2023, at 19:48, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1684856745 -14400 > # Tue May 23 19:45:45 2023 +0400 > # Branch quic > # Node ID bd25277d0ed77a90766bc2f4153538c86621cdfc > # Parent a83071314a22652d4a552e72e13d18aa4c01d79e > Configure: dump unsupported repository error and exit. > > diff --git a/auto/configure b/auto/configure > --- a/auto/configure > +++ b/auto/configure > @@ -4,6 +4,13 @@ > # Copyright (C) Nginx, Inc. > > > +cat << END > + > +$0: this repository is no longer supported, see README > + > +END > +exit 1 > + > LC_ALL=C > export LC_ALL > Something similar to what we use for invalid options might be enough. Also, using "error" might help emphasize this is an error: diff --git a/auto/configure b/auto/configure --- a/auto/configure +++ b/auto/configure @@ -4,6 +4,9 @@ # Copyright (C) Nginx, Inc. +echo "$0: error: this repository is no longer supported, see README" +exit 1 + LC_ALL=C export LC_ALL Personally, I think this makes sense, in order to help break pipelines. -- Sergey Kandaurov From amdeich at gmail.com Tue May 23 22:07:00 2023 From: amdeich at gmail.com (Andrey Kulikov) Date: Wed, 24 May 2023 01:07:00 +0300 Subject: Q: http2 and http1 virtual hosts both works via HTTP/2 - bug of feature? Message-ID: Hello, Observed nginx's version 1.22.1 questionable behaviour with two virtual hosts, one with H2 - enabled, second without http2 support. Both on the same IP and port, with different domain names/server names. When browsers make requests to a second domain, h2 being ALPN-negotiated, and data transferred via HTTP/2, in spite of http2 was not configured on that virtual host. Sample config snippet: http { ... server { listen 1985 http2 ssl; server_name 'mavr.cp.eu'; ssl_certificate domain.cer; ssl_certificate_key domain.key; location / { return 302 https://zavr.cp.eu:1985$request_uri; } } server { listen 1985 ssl; # NO h2! server_name 'zavr.cp.eu'; ssl_certificate domain.cer; ssl_certificate_key domain.key; location / { # Doesn't really matter what's here, for simplicity I've used ngx_lua_module. echo "Server protocol: $server_protocol H2 connection: $http2 ."; } } ... } When I type https://mavr.cp.eu:1985 in browser I see: 1. Browser negotiates h2 ALPN with the server with SNI mavr.cp.eu ; 2. Server replied with 302 redirect to https://zavr.cp.eu:1985 (expected) 3. Browser makes NEW TCP-connection with the server with SNI zavr.cp.eu; (expected) 4. Server replied with h2 ALPN (unexpected) 5. Browser shows "Server protocol: HTTP/2.0 H2 connection: h2 ." When I browsed source code, I spotted following line: http://hg.nginx.org/nginx/file/tip/src/http/modules/ngx_http_ssl_module.c#l460 #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 My assumption (could be wrong) that it means, when http2 is enabled on the address, related to (possibly) many virtual hosts, we always add h2 ALPN. Regardless of negotiated SNI. At least I see that ngx_http_find_virtual_server() being called here: http://hg.nginx.org/nginx/file/tip/src/http/ngx_http_request.c#l2236 on already established http(s) connection, not during TLS handshake. This behaviour caused some pain in my neck today, so could please someone be so kind to enlighten me, is it like it supposed to be, or is it possible to change this in the way, so http1 and http2 virtual hosts could be served on the same IP:port? Is it a bug, feature, or just not needed by anyone? Quick Googling did not reveal that anyone had complained about it too much. If one will manage to implement a patch, correcting this behaviour, will it be even considered to review? -- Andrey P.S. Under "Browser" here I meant Chrome, Chromium, and Firefox on Windows. But, according to unconfirmed information, "some", yet unidentified, browsers do NOT make a second TCP-connection to another domain after redirection in the provided example. And put a second request to the same h2 connection, despite the fact that it was negotiated for different SNI. Shame on them. -------------- next part -------------- An HTML attachment was scrubbed... URL: From thresh at nginx.com Tue May 23 22:16:22 2023 From: thresh at nginx.com (=?iso-8859-1?q?Konstantin_Pavlov?=) Date: Tue, 23 May 2023 15:16:22 -0700 Subject: [PATCH] Linux packages: documented lack of HTTP/3 support for older distros Message-ID: <3ba229c95e50b7a422f8.1684880182@QGCD7XG9R9> # HG changeset patch # User Konstantin Pavlov # Date 1684878839 25200 # Tue May 23 14:53:59 2023 -0700 # Node ID 3ba229c95e50b7a422f8a61db7560d40bc6524db # Parent 46b1da35ceeb697431de877cf43681b186617335 Linux packages: documented lack of HTTP/3 support for older distros. diff -r 46b1da35ceeb -r 3ba229c95e50 xml/en/linux_packages.xml --- a/xml/en/linux_packages.xml Tue May 23 19:36:24 2023 +0300 +++ b/xml/en/linux_packages.xml Tue May 23 14:53:59 2023 -0700 @@ -7,7 +7,7 @@
+ rev="86">
@@ -179,6 +179,12 @@ versions: + +Packages for RHEL 7 and SLES 12 are built without +HTTP/3 support +because OpenSSL used by those doesn't support TLSv1.3. + +
diff -r 46b1da35ceeb -r 3ba229c95e50 xml/ru/linux_packages.xml --- a/xml/ru/linux_packages.xml Tue May 23 19:36:24 2023 +0300 +++ b/xml/ru/linux_packages.xml Tue May 23 14:53:59 2023 -0700 @@ -7,7 +7,7 @@
+ rev="86">
@@ -179,6 +179,12 @@ + +Пакеты для RHEL 7 и SLES 12 собраны без +поддержки HTTP/3, +так как OpenSSL, используемая в этих дистрибутивах, не поддерживает TLSv1.3. + +
From maxim at nginx.com Tue May 23 23:55:30 2023 From: maxim at nginx.com (Maxim Konovalov) Date: Tue, 23 May 2023 16:55:30 -0700 Subject: [PATCH] Linux packages: documented lack of HTTP/3 support for older distros In-Reply-To: <3ba229c95e50b7a422f8.1684880182@QGCD7XG9R9> References: <3ba229c95e50b7a422f8.1684880182@QGCD7XG9R9> Message-ID: <1940dc7a-06d3-fe89-1d1d-e8d781ba69cd@nginx.com> On 23.05.2023 15:16, Konstantin Pavlov wrote: > # HG changeset patch > # User Konstantin Pavlov > # Date 1684878839 25200 > # Tue May 23 14:53:59 2023 -0700 > # Node ID 3ba229c95e50b7a422f8a61db7560d40bc6524db > # Parent 46b1da35ceeb697431de877cf43681b186617335 > Linux packages: documented lack of HTTP/3 support for older distros. > > diff -r 46b1da35ceeb -r 3ba229c95e50 xml/en/linux_packages.xml > --- a/xml/en/linux_packages.xml Tue May 23 19:36:24 2023 +0300 > +++ b/xml/en/linux_packages.xml Tue May 23 14:53:59 2023 -0700 > @@ -7,7 +7,7 @@ >
link="/en/linux_packages.html" > lang="en" > - rev="85"> > + rev="86"> > >
> > @@ -179,6 +179,12 @@ versions: > > > > + > +Packages for RHEL 7 and SLES 12 are built without > +HTTP/3 support > +because OpenSSL used by those doesn't support TLSv1.3. > + > + >
> > > diff -r 46b1da35ceeb -r 3ba229c95e50 xml/ru/linux_packages.xml > --- a/xml/ru/linux_packages.xml Tue May 23 19:36:24 2023 +0300 > +++ b/xml/ru/linux_packages.xml Tue May 23 14:53:59 2023 -0700 > @@ -7,7 +7,7 @@ >
link="/ru/linux_packages.html" > lang="ru" > - rev="85"> > + rev="86"> > >
> > @@ -179,6 +179,12 @@ > > > > + > +Пакеты для RHEL 7 и SLES 12 собраны без > +поддержки HTTP/3, > +так как OpenSSL, используемая в этих дистрибутивах, не поддерживает TLSv1.3. > + > + >
> Looks good. -- Maxim Konovalov From xeioex at nginx.com Wed May 24 04:21:25 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 24 May 2023 04:21:25 +0000 Subject: [njs] WebCrypto: sorted njs_webcrypto_alg accoding to njs_webcrypto_alg_t. Message-ID: details: https://hg.nginx.org/njs/rev/510d8ebedfba branches: changeset: 2130:510d8ebedfba user: Dmitry Volyntsev date: Mon May 22 22:48:58 2023 -0700 description: WebCrypto: sorted njs_webcrypto_alg accoding to njs_webcrypto_alg_t. So njs_webcrypto_alg[alg->type] can be used to get the algorithm name. diffstat: external/njs_webcrypto_module.c | 68 +++++++++++++++++++++------------------- 1 files changed, 35 insertions(+), 33 deletions(-) diffs (120 lines): diff -r 08a912ab9520 -r 510d8ebedfba external/njs_webcrypto_module.c --- a/external/njs_webcrypto_module.c Mon May 22 17:59:47 2023 -0700 +++ b/external/njs_webcrypto_module.c Mon May 22 22:48:58 2023 -0700 @@ -45,6 +45,7 @@ typedef enum { NJS_ALGORITHM_ECDH, NJS_ALGORITHM_PBKDF2, NJS_ALGORITHM_HKDF, + NJS_ALGORITHM_MAX, } njs_webcrypto_alg_t; @@ -54,6 +55,7 @@ typedef enum { NJS_HASH_SHA256, NJS_HASH_SHA384, NJS_HASH_SHA512, + NJS_HASH_MAX, } njs_webcrypto_hash_t; @@ -153,6 +155,28 @@ static njs_webcrypto_entry_t njs_webcryp (uintptr_t) & (njs_webcrypto_algorithm_t) { type, usage_mask, fmt_mask } { + njs_str("RSASSA-PKCS1-v1_5"), + njs_webcrypto_algorithm(NJS_ALGORITHM_RSASSA_PKCS1_v1_5, + NJS_KEY_USAGE_SIGN | + NJS_KEY_USAGE_VERIFY | + NJS_KEY_USAGE_GENERATE_KEY, + NJS_KEY_FORMAT_PKCS8 | + NJS_KEY_FORMAT_SPKI | + NJS_KEY_FORMAT_JWK) + }, + + { + njs_str("RSA-PSS"), + njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_PSS, + NJS_KEY_USAGE_SIGN | + NJS_KEY_USAGE_VERIFY | + NJS_KEY_USAGE_GENERATE_KEY, + NJS_KEY_FORMAT_PKCS8 | + NJS_KEY_FORMAT_SPKI | + NJS_KEY_FORMAT_JWK) + }, + + { njs_str("RSA-OAEP"), njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_OAEP, NJS_KEY_USAGE_ENCRYPT | @@ -166,6 +190,16 @@ static njs_webcrypto_entry_t njs_webcryp }, { + njs_str("HMAC"), + njs_webcrypto_algorithm(NJS_ALGORITHM_HMAC, + NJS_KEY_USAGE_GENERATE_KEY | + NJS_KEY_USAGE_SIGN | + NJS_KEY_USAGE_VERIFY, + NJS_KEY_FORMAT_RAW | + NJS_KEY_FORMAT_JWK) + }, + + { njs_str("AES-GCM"), njs_webcrypto_algorithm(NJS_ALGORITHM_AES_GCM, NJS_KEY_USAGE_ENCRYPT | @@ -202,28 +236,6 @@ static njs_webcrypto_entry_t njs_webcryp }, { - njs_str("RSASSA-PKCS1-v1_5"), - njs_webcrypto_algorithm(NJS_ALGORITHM_RSASSA_PKCS1_v1_5, - NJS_KEY_USAGE_SIGN | - NJS_KEY_USAGE_VERIFY | - NJS_KEY_USAGE_GENERATE_KEY, - NJS_KEY_FORMAT_PKCS8 | - NJS_KEY_FORMAT_SPKI | - NJS_KEY_FORMAT_JWK) - }, - - { - njs_str("RSA-PSS"), - njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_PSS, - NJS_KEY_USAGE_SIGN | - NJS_KEY_USAGE_VERIFY | - NJS_KEY_USAGE_GENERATE_KEY, - NJS_KEY_FORMAT_PKCS8 | - NJS_KEY_FORMAT_SPKI | - NJS_KEY_FORMAT_JWK) - }, - - { njs_str("ECDSA"), njs_webcrypto_algorithm(NJS_ALGORITHM_ECDSA, NJS_KEY_USAGE_SIGN | @@ -262,16 +274,6 @@ static njs_webcrypto_entry_t njs_webcryp }, { - njs_str("HMAC"), - njs_webcrypto_algorithm(NJS_ALGORITHM_HMAC, - NJS_KEY_USAGE_GENERATE_KEY | - NJS_KEY_USAGE_SIGN | - NJS_KEY_USAGE_VERIFY, - NJS_KEY_FORMAT_RAW | - NJS_KEY_FORMAT_JWK) - }, - - { njs_null_str, 0 } @@ -335,7 +337,7 @@ static njs_webcrypto_entry_t njs_webcryp static njs_str_t - njs_webcrypto_alg_name[NJS_ALGORITHM_HMAC + 1][NJS_HASH_SHA512 + 1] = { + njs_webcrypto_alg_name[NJS_ALGORITHM_HMAC + 1][NJS_HASH_MAX] = { { njs_null_str, njs_str("RS1"), From xeioex at nginx.com Wed May 24 04:21:27 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 24 May 2023 04:21:27 +0000 Subject: [njs] WebCrypto: making njs_webcrypto_key_t more compact by using union. Message-ID: details: https://hg.nginx.org/njs/rev/646374a97d8c branches: changeset: 2131:646374a97d8c user: Dmitry Volyntsev date: Mon May 22 22:48:59 2023 -0700 description: WebCrypto: making njs_webcrypto_key_t more compact by using union. diffstat: external/njs_webcrypto_module.c | 260 +++++++++++++++++++++------------------ 1 files changed, 142 insertions(+), 118 deletions(-) diffs (814 lines): diff -r 510d8ebedfba -r 646374a97d8c external/njs_webcrypto_module.c --- a/external/njs_webcrypto_module.c Mon May 22 22:48:58 2023 -0700 +++ b/external/njs_webcrypto_module.c Mon May 22 22:48:59 2023 -0700 @@ -69,20 +69,28 @@ typedef struct { njs_webcrypto_alg_t type; unsigned usage; unsigned fmt; + unsigned raw; } njs_webcrypto_algorithm_t; typedef struct { njs_webcrypto_algorithm_t *alg; - njs_webcrypto_hash_t hash; - int curve; - - EVP_PKEY *pkey; - njs_str_t raw; - unsigned usage; njs_bool_t extractable; - njs_bool_t privat; + + njs_webcrypto_hash_t hash; + + union { + struct { + EVP_PKEY *pkey; + njs_bool_t privat; + int curve; + } a; + struct { + njs_str_t raw; + } s; + } u; + } njs_webcrypto_key_t; @@ -151,8 +159,8 @@ static njs_int_t njs_webcrypto_init(njs_ static njs_webcrypto_entry_t njs_webcrypto_alg[] = { -#define njs_webcrypto_algorithm(type, usage_mask, fmt_mask) \ - (uintptr_t) & (njs_webcrypto_algorithm_t) { type, usage_mask, fmt_mask } +#define njs_webcrypto_algorithm(type, usage, fmt, raw) \ + (uintptr_t) & (njs_webcrypto_algorithm_t) { type, usage, fmt, raw } { njs_str("RSASSA-PKCS1-v1_5"), @@ -162,7 +170,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_GENERATE_KEY, NJS_KEY_FORMAT_PKCS8 | NJS_KEY_FORMAT_SPKI | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 0) }, { @@ -173,7 +182,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_GENERATE_KEY, NJS_KEY_FORMAT_PKCS8 | NJS_KEY_FORMAT_SPKI | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 0) }, { @@ -186,7 +196,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_GENERATE_KEY, NJS_KEY_FORMAT_PKCS8 | NJS_KEY_FORMAT_SPKI | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 0) }, { @@ -196,7 +207,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_SIGN | NJS_KEY_USAGE_VERIFY, NJS_KEY_FORMAT_RAW | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 1) }, { @@ -208,7 +220,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_UNWRAP_KEY | NJS_KEY_USAGE_GENERATE_KEY, NJS_KEY_FORMAT_RAW | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 1) }, { @@ -220,7 +233,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_UNWRAP_KEY | NJS_KEY_USAGE_GENERATE_KEY, NJS_KEY_FORMAT_RAW | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 1) }, { @@ -232,7 +246,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_UNWRAP_KEY | NJS_KEY_USAGE_GENERATE_KEY, NJS_KEY_FORMAT_RAW | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 1) }, { @@ -244,7 +259,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_FORMAT_PKCS8 | NJS_KEY_FORMAT_SPKI | NJS_KEY_FORMAT_RAW | - NJS_KEY_FORMAT_JWK) + NJS_KEY_FORMAT_JWK, + 0) }, { @@ -254,7 +270,8 @@ static njs_webcrypto_entry_t njs_webcryp NJS_KEY_USAGE_DERIVE_BITS | NJS_KEY_USAGE_GENERATE_KEY | NJS_KEY_USAGE_UNSUPPORTED, - NJS_KEY_FORMAT_UNKNOWN) + NJS_KEY_FORMAT_UNKNOWN, + 0) }, { @@ -262,7 +279,8 @@ static njs_webcrypto_entry_t njs_webcryp njs_webcrypto_algorithm(NJS_ALGORITHM_PBKDF2, NJS_KEY_USAGE_DERIVE_KEY | NJS_KEY_USAGE_DERIVE_BITS, - NJS_KEY_FORMAT_RAW) + NJS_KEY_FORMAT_RAW, + 1) }, { @@ -270,7 +288,8 @@ static njs_webcrypto_entry_t njs_webcryp njs_webcrypto_algorithm(NJS_ALGORITHM_HKDF, NJS_KEY_USAGE_DERIVE_KEY | NJS_KEY_USAGE_DERIVE_BITS, - NJS_KEY_FORMAT_RAW) + NJS_KEY_FORMAT_RAW, + 1) }, { @@ -706,7 +725,7 @@ njs_cipher_pkey(njs_vm_t *vm, njs_str_t EVP_PKEY_cipher_t cipher; EVP_PKEY_cipher_init_t init; - ctx = EVP_PKEY_CTX_new(key->pkey, NULL); + ctx = EVP_PKEY_CTX_new(key->u.a.pkey, NULL); if (njs_slow_path(ctx == NULL)) { njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed"); return NJS_ERROR; @@ -786,7 +805,7 @@ njs_cipher_aes_gcm(njs_vm_t *vm, njs_str static const njs_str_t string_ad = njs_str("additionalData"); static const njs_str_t string_tl = njs_str("tagLength"); - switch (key->raw.length) { + switch (key->u.s.raw.length) { case 16: cipher = EVP_aes_128_gcm(); break; @@ -865,7 +884,7 @@ njs_cipher_aes_gcm(njs_vm_t *vm, njs_str goto fail; } - ret = EVP_CipherInit_ex(ctx, NULL, NULL, key->raw.start, iv.start, + ret = EVP_CipherInit_ex(ctx, NULL, NULL, key->u.s.raw.start, iv.start, encrypt); if (njs_slow_path(ret <= 0)) { njs_webcrypto_error(vm, "EVP_%sInit_ex() failed", @@ -1077,7 +1096,7 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str static const njs_str_t string_counter = njs_str("counter"); - switch (key->raw.length) { + switch (key->u.s.raw.length) { case 16: cipher = EVP_aes_128_ctr(); break; @@ -1199,7 +1218,7 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str * during the ciphering. * */ - ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, + ret = njs_cipher_aes_ctr128(vm, cipher, key->u.s.raw.start, data->start, data->length, iv.start, dst, &len, encrypt); if (njs_slow_path(ret != NJS_OK)) { @@ -1217,17 +1236,17 @@ njs_cipher_aes_ctr(njs_vm_t *vm, njs_str size1 = BN_get_word(left) * AES_BLOCK_SIZE; - ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, data->start, size1, - iv.start, dst, &len, encrypt); + ret = njs_cipher_aes_ctr128(vm, cipher, key->u.s.raw.start, data->start, + size1, iv.start, dst, &len, encrypt); if (njs_slow_path(ret != NJS_OK)) { goto fail; } njs_counter128_reset(iv.start, (u_char *) iv2, length); - ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, &data->start[size1], - data->length - size1, iv2, &dst[size1], &len2, - encrypt); + ret = njs_cipher_aes_ctr128(vm, cipher, key->u.s.raw.start, + &data->start[size1], data->length - size1, + iv2, &dst[size1], &len2, encrypt); if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -1274,7 +1293,7 @@ njs_cipher_aes_cbc(njs_vm_t *vm, njs_str static const njs_str_t string_iv = njs_str("iv"); - switch (key->raw.length) { + switch (key->u.s.raw.length) { case 16: cipher = EVP_aes_128_cbc(); break; @@ -1321,7 +1340,7 @@ njs_cipher_aes_cbc(njs_vm_t *vm, njs_str return NJS_ERROR; } - ret = EVP_CipherInit_ex(ctx, cipher, NULL, key->raw.start, iv.start, + ret = EVP_CipherInit_ex(ctx, cipher, NULL, key->u.s.raw.start, iv.start, encrypt); if (njs_slow_path(ret <= 0)) { njs_webcrypto_error(vm, "EVP_%SInit_ex() failed", @@ -1527,7 +1546,7 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t md = njs_algorithm_hash_digest(hash); - ret = PKCS5_PBKDF2_HMAC((char *) key->raw.start, key->raw.length, + ret = PKCS5_PBKDF2_HMAC((char *) key->u.s.raw.start, key->u.s.raw.length, salt.start, salt.length, iterations, md, length, k); if (njs_slow_path(ret <= 0)) { @@ -1591,7 +1610,8 @@ njs_ext_derive(njs_vm_t *vm, njs_value_t goto free; } - ret = EVP_PKEY_CTX_set1_hkdf_key(pctx, key->raw.start, key->raw.length); + ret = EVP_PKEY_CTX_set1_hkdf_key(pctx, key->u.s.raw.start, + key->u.s.raw.length); if (njs_slow_path(ret <= 0)) { njs_webcrypto_error(vm, "EVP_PKEY_CTX_set1_hkdf_key() failed"); goto free; @@ -1641,8 +1661,8 @@ free: } } - dkey->raw.start = k; - dkey->raw.length = length; + dkey->u.s.raw.start = k; + dkey->u.s.raw.length = length; ret = njs_vm_external_create(vm, njs_value_arg(&lvalue), njs_webcrypto_crypto_key_proto_id, @@ -1762,7 +1782,7 @@ njs_export_jwk_rsa(njs_vm_t *vm, njs_web *qi_bn; njs_opaque_value_t nvalue, evalue, alg, rsa_s; - rsa = njs_pkey_get_rsa_key(key->pkey); + rsa = njs_pkey_get_rsa_key(key->u.a.pkey); njs_rsa_get0_key(rsa, &n_bn, &e_bn, &d_bn); @@ -1798,7 +1818,7 @@ njs_export_jwk_rsa(njs_vm_t *vm, njs_web return NJS_ERROR; } - if (key->privat) { + if (key->u.a.privat) { njs_rsa_get0_factors(rsa, &p_bn, &q_bn); njs_rsa_get0_ctr_params(rsa, &dp_bn, &dq_bn, &qi_bn); @@ -1859,7 +1879,7 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc y_bn = NULL; d_bn = NULL; - ec = njs_pkey_get_ec_key(key->pkey); + ec = njs_pkey_get_ec_key(key->u.a.pkey); pub = EC_KEY_get0_public_key(ec); group = EC_KEY_get0_group(ec); @@ -1940,7 +1960,7 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc return NJS_ERROR; } - if (key->privat) { + if (key->u.a.privat) { d_bn = EC_KEY_get0_private_key(ec); ret = njs_export_base64url_bignum(vm, &dvalue, d_bn, group_bytes); @@ -1980,15 +2000,15 @@ njs_export_raw_ec(njs_vm_t *vm, njs_webc const EC_POINT *point; point_conversion_form_t form; - njs_assert(key->pkey != NULL); - - if (key->privat) { + njs_assert(key->u.a.pkey != NULL); + + if (key->u.a.privat) { njs_vm_error(vm, "private key of \"%V\" cannot be exported " "in \"raw\" format", njs_algorithm_string(key->alg)); return NJS_ERROR; } - ec = njs_pkey_get_ec_key(key->pkey); + ec = njs_pkey_get_ec_key(key->u.a.pkey); group = EC_KEY_get0_group(ec); point = EC_KEY_get0_public_key(ec); @@ -2022,9 +2042,9 @@ njs_export_jwk_asymmetric(njs_vm_t *vm, njs_int_t ret; njs_opaque_value_t ops, extractable; - njs_assert(key->pkey != NULL); - - switch (EVP_PKEY_id(key->pkey)) { + njs_assert(key->u.a.pkey != NULL); + + switch (EVP_PKEY_id(key->u.a.pkey)) { case EVP_PKEY_RSA: #if (OPENSSL_VERSION_NUMBER >= 0x10101001L) case EVP_PKEY_RSA_PSS: @@ -2073,9 +2093,9 @@ njs_export_jwk_oct(njs_vm_t *vm, njs_web njs_opaque_value_t k, alg, ops, extractable, oct_s; njs_webcrypto_alg_t type; - njs_assert(key->raw.start != NULL); - - ret = njs_string_base64url(vm, njs_value_arg(&k), &key->raw); + njs_assert(key->u.s.raw.start != NULL); + + ret = njs_string_base64url(vm, njs_value_arg(&k), &key->u.s.raw); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -2088,12 +2108,12 @@ njs_export_jwk_oct(njs_vm_t *vm, njs_web nm->length); } else { - switch (key->raw.length) { + switch (key->u.s.raw.length) { case 16: case 24: case 32: nm = &njs_webcrypto_alg_aes_name - [type - NJS_ALGORITHM_AES_GCM][(key->raw.length - 16) / 8]; + [type - NJS_ALGORITHM_AES_GCM][(key->u.s.raw.length - 16) / 8]; (void) njs_vm_value_string_set(vm, njs_value_arg(&alg), nm->start, nm->length); break; @@ -2217,7 +2237,7 @@ njs_ext_export_key(njs_vm_t *vm, njs_val break; case NJS_KEY_FORMAT_PKCS8: - if (!key->privat) { + if (!key->u.a.privat) { njs_vm_error(vm, "public key of \"%V\" cannot be exported " "as PKCS8", njs_algorithm_string(key->alg)); goto fail; @@ -2229,9 +2249,9 @@ njs_ext_export_key(njs_vm_t *vm, njs_val goto fail; } - njs_assert(key->pkey != NULL); - - pkcs8 = EVP_PKEY2PKCS8(key->pkey); + njs_assert(key->u.a.pkey != NULL); + + pkcs8 = EVP_PKEY2PKCS8(key->u.a.pkey); if (njs_slow_path(pkcs8 == NULL)) { BIO_free(bio); njs_webcrypto_error(vm, "EVP_PKEY2PKCS8() failed"); @@ -2260,7 +2280,7 @@ njs_ext_export_key(njs_vm_t *vm, njs_val break; case NJS_KEY_FORMAT_SPKI: - if (key->privat) { + if (key->u.a.privat) { njs_vm_error(vm, "private key of \"%V\" cannot be exported " "as SPKI", njs_algorithm_string(key->alg)); goto fail; @@ -2272,9 +2292,9 @@ njs_ext_export_key(njs_vm_t *vm, njs_val goto fail; } - njs_assert(key->pkey != NULL); - - if (!i2d_PUBKEY_bio(bio, key->pkey)) { + njs_assert(key->u.a.pkey != NULL); + + if (!i2d_PUBKEY_bio(bio, key->u.a.pkey)) { BIO_free(bio); njs_webcrypto_error(vm, "i2d_PUBKEY_bio() failed"); goto fail; @@ -2305,7 +2325,8 @@ njs_ext_export_key(njs_vm_t *vm, njs_val } ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&value), - key->raw.start, key->raw.length); + key->u.s.raw.start, + key->u.s.raw.length); if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -2401,7 +2422,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v goto fail; } - if (EVP_PKEY_keygen(ctx, &key->pkey) <= 0) { + if (EVP_PKEY_keygen(ctx, &key->u.a.pkey) <= 0) { njs_webcrypto_error(vm, "EVP_PKEY_keygen() failed"); goto fail; } @@ -2409,7 +2430,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v EVP_PKEY_CTX_free(ctx); ctx = NULL; - key->privat = 1; + key->u.a.privat = 1; key->usage = (alg->type == NJS_ALGORITHM_RSA_OAEP) ? NJS_KEY_USAGE_DECRYPT : NJS_KEY_USAGE_SIGN; @@ -2419,12 +2440,12 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v goto fail; } - if (njs_pkey_up_ref(key->pkey) <= 0) { + if (njs_pkey_up_ref(key->u.a.pkey) <= 0) { njs_webcrypto_error(vm, "njs_pkey_up_ref() failed"); goto fail; } - keypub->pkey = key->pkey; + keypub->u.a.pkey = key->u.a.pkey; keypub->hash = key->hash; keypub->usage = (alg->type == NJS_ALGORITHM_RSA_OAEP) ? NJS_KEY_USAGE_ENCRYPT @@ -2485,7 +2506,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v goto fail; } - if (EVP_PKEY_keygen(ctx, &key->pkey) <= 0) { + if (EVP_PKEY_keygen(ctx, &key->u.a.pkey) <= 0) { njs_webcrypto_error(vm, "EVP_PKEY_keygen() failed"); goto fail; } @@ -2493,7 +2514,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v EVP_PKEY_CTX_free(ctx); ctx = NULL; - key->privat = 1; + key->u.a.privat = 1; key->usage = NJS_KEY_USAGE_SIGN; keypub = njs_webcrypto_key_alloc(vm, alg, usage, extractable); @@ -2501,13 +2522,13 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v goto fail; } - if (njs_pkey_up_ref(key->pkey) <= 0) { + if (njs_pkey_up_ref(key->u.a.pkey) <= 0) { njs_webcrypto_error(vm, "njs_pkey_up_ref() failed"); goto fail; } - keypub->pkey = key->pkey; - keypub->curve = key->curve; + keypub->u.a.pkey = key->u.a.pkey; + keypub->u.a.curve = key->u.a.curve; keypub->usage = NJS_KEY_USAGE_VERIFY; ret = njs_vm_external_create(vm, njs_value_arg(&priv), @@ -2552,16 +2573,17 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v goto fail; } - key->raw.length = EVP_MD_size(njs_algorithm_hash_digest(key->hash)); + key->u.s.raw.length = + EVP_MD_size(njs_algorithm_hash_digest(key->hash)); } else { val = njs_vm_object_prop(vm, aobject, &string_length, &value); if (val != NULL) { - key->raw.length = njs_value_number(val) / 8; - - if (key->raw.length != 16 - && key->raw.length != 24 - && key->raw.length != 32) + key->u.s.raw.length = njs_value_number(val) / 8; + + if (key->u.s.raw.length != 16 + && key->u.s.raw.length != 24 + && key->u.s.raw.length != 32) { njs_vm_error(vm, "length for \"%V\" key should be one of " "128, 192, 256", njs_algorithm_string(alg)); @@ -2570,13 +2592,14 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v } } - key->raw.start = njs_mp_alloc(njs_vm_memory_pool(vm), key->raw.length); - if (njs_slow_path(key->raw.start == NULL)) { + key->u.s.raw.start = njs_mp_alloc(njs_vm_memory_pool(vm), + key->u.s.raw.length); + if (njs_slow_path(key->u.s.raw.start == NULL)) { njs_vm_memory_error(vm); goto fail; } - if (RAND_bytes(key->raw.start, key->raw.length) <= 0) { + if (RAND_bytes(key->u.s.raw.start, key->u.s.raw.length) <= 0) { njs_webcrypto_error(vm, "RAND_bytes() failed"); goto fail; } @@ -2672,7 +2695,7 @@ fail0: return NULL; } - key->privat = njs_value_is_string(njs_value_arg(&d)); + key->u.a.privat = njs_value_is_string(njs_value_arg(&d)); val = njs_vm_object_prop(vm, jwk, &key_ops, &value); if (val != NULL && !njs_value_is_undefined(val)){ @@ -2739,7 +2762,7 @@ fail0: goto fail; } - if (!key->privat) { + if (!key->u.a.privat) { goto done; } @@ -2869,7 +2892,7 @@ njs_import_raw_ec(njs_vm_t *vm, njs_str_ EC_POINT *pub; const EC_GROUP *group; - ec = EC_KEY_new_by_curve_name(key->curve); + ec = EC_KEY_new_by_curve_name(key->u.a.curve); if (njs_slow_path(ec == NULL)) { njs_webcrypto_error(vm, "EC_KEY_new_by_curve_name() failed"); return NULL; @@ -2965,7 +2988,7 @@ fail0: return NULL; } - key->privat = njs_value_is_string(njs_value_arg(&d)); + key->u.a.privat = njs_value_is_string(njs_value_arg(&d)); val = njs_vm_object_prop(vm, jwk, &key_ops, &value); if (val != NULL && !njs_value_is_undefined(val)) { @@ -3005,12 +3028,12 @@ fail0: } } - if (curve != key->curve) { + if (curve != key->u.a.curve) { njs_vm_error(vm, "JWK EC curve mismatch"); return NULL; } - ec = EC_KEY_new_by_curve_name(key->curve); + ec = EC_KEY_new_by_curve_name(key->u.a.curve); if (njs_slow_path(ec == NULL)) { njs_webcrypto_error(vm, "EC_KEY_new_by_curve_name() failed"); return NULL; @@ -3026,7 +3049,7 @@ fail0: goto fail; } - if (key->privat) { + if (key->u.a.privat) { d_bn = njs_import_base64url_bignum(vm, &d); if (njs_slow_path(d_bn == NULL)) { goto fail; @@ -3055,7 +3078,7 @@ fail0: goto fail_pkey; } - if (key->privat) { + if (key->u.a.privat) { if (!EC_KEY_set_private_key(ec, d_bn)) { njs_webcrypto_error(vm, "EC_KEY_set_private_key() failed"); goto fail_pkey; @@ -3123,15 +3146,16 @@ njs_import_jwk_oct(njs_vm_t *vm, njs_val njs_value_string_get(val, &b64); - (void) njs_decode_base64url_length(&b64, &key->raw.length); - - key->raw.start = njs_mp_alloc(njs_vm_memory_pool(vm), key->raw.length); - if (njs_slow_path(key->raw.start == NULL)) { + (void) njs_decode_base64url_length(&b64, &key->u.s.raw.length); + + key->u.s.raw.start = njs_mp_alloc(njs_vm_memory_pool(vm), + key->u.s.raw.length); + if (njs_slow_path(key->u.s.raw.start == NULL)) { njs_vm_memory_error(vm); return NJS_ERROR; } - njs_decode_base64url(&key->raw, &b64); + njs_decode_base64url(&key->u.s.raw, &b64); size = 16; @@ -3166,7 +3190,7 @@ njs_import_jwk_oct(njs_vm_t *vm, njs_val done: if (key->alg->type != NJS_ALGORITHM_HMAC) { - if (key->raw.length != size) { + if (key->u.s.raw.length != size) { njs_vm_error(vm, "key size and \"alg\" value \"%V\" mismatch", &alg); return NJS_ERROR; @@ -3278,11 +3302,11 @@ njs_ext_import_key(njs_vm_t *vm, njs_val /* * set by njs_webcrypto_key_alloc(): * - * key->pkey = NULL; - * key->raw.length = 0; - * key->raw.start = NULL; - * key->curve = 0; - * key->privat = 0; + * key->u.a.pkey = NULL; + * key->u.s.raw.length = 0; + * key->u.s.raw.start = NULL; + * key->u.a.curve = 0; + * key->u.a.privat = 0; * key->hash = NJS_HASH_UNSET; */ @@ -3312,7 +3336,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val PKCS8_PRIV_KEY_INFO_free(pkcs8); BIO_free(bio); - key->privat = 1; + key->u.a.privat = 1; break; @@ -3350,7 +3374,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val } } else if (njs_strstr_eq(&kty, &njs_str_value("EC"))) { - ret = njs_algorithm_curve(vm, options, &key->curve); + ret = njs_algorithm_curve(vm, options, &key->u.a.curve); if (njs_slow_path(ret == NJS_ERROR)) { goto fail; } @@ -3410,7 +3434,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val goto fail; } - if (key->privat) { + if (key->u.a.privat) { mask = (alg->type == NJS_ALGORITHM_RSA_OAEP) ? ~(NJS_KEY_USAGE_DECRYPT | NJS_KEY_USAGE_UNWRAP_KEY) : ~(NJS_KEY_USAGE_SIGN); @@ -3427,13 +3451,13 @@ njs_ext_import_key(njs_vm_t *vm, njs_val } key->hash = hash; - key->pkey = pkey; + key->u.a.pkey = pkey; break; case NJS_ALGORITHM_ECDSA: case NJS_ALGORITHM_ECDH: - ret = njs_algorithm_curve(vm, options, &key->curve); + ret = njs_algorithm_curve(vm, options, &key->u.a.curve); if (njs_slow_path(ret == NJS_ERROR)) { goto fail; } @@ -3473,12 +3497,12 @@ njs_ext_import_key(njs_vm_t *vm, njs_val #endif - if (njs_slow_path(key->curve != nid)) { + if (njs_slow_path(key->u.a.curve != nid)) { njs_webcrypto_error(vm, "name curve mismatch"); goto fail; } - mask = key->privat ? ~NJS_KEY_USAGE_SIGN : ~NJS_KEY_USAGE_VERIFY; + mask = key->u.a.privat ? ~NJS_KEY_USAGE_SIGN : ~NJS_KEY_USAGE_VERIFY; if (key->usage & mask) { njs_vm_error(vm, "key usage mismatch for \"%V\" key", @@ -3486,7 +3510,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val goto fail; } - key->pkey = pkey; + key->u.a.pkey = pkey; break; @@ -3497,7 +3521,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val goto fail; } - key->raw = key_data; + key->u.s.raw = key_data; } else { /* NJS_KEY_FORMAT_JWK. */ @@ -3530,7 +3554,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val goto fail; } - key->raw = key_data; + key->u.s.raw = key_data; } break; @@ -3538,7 +3562,7 @@ njs_ext_import_key(njs_vm_t *vm, njs_val case NJS_ALGORITHM_PBKDF2: case NJS_ALGORITHM_HKDF: default: - key->raw = key_data; + key->u.s.raw = key_data; break; } @@ -3885,8 +3909,8 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t * outlen = m_len; - p = HMAC(md, key->raw.start, key->raw.length, data.start, data.length, - dst, &m_len); + p = HMAC(md, key->u.s.raw.start, key->u.s.raw.length, data.start, + data.length, dst, &m_len); if (njs_slow_path(p == NULL || m_len != outlen)) { njs_webcrypto_error(vm, "HMAC() failed"); @@ -3927,14 +3951,14 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t * goto fail; } - olen = EVP_PKEY_size(key->pkey); + olen = EVP_PKEY_size(key->u.a.pkey); dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen); if (njs_slow_path(dst == NULL)) { njs_vm_memory_error(vm); goto fail; } - pctx = EVP_PKEY_CTX_new(key->pkey, NULL); + pctx = EVP_PKEY_CTX_new(key->u.a.pkey, NULL); if (njs_slow_path(pctx == NULL)) { njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed"); goto fail; @@ -3955,7 +3979,7 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t * } } - ret = njs_set_rsa_padding(vm, options, key->pkey, pctx, alg->type); + ret = njs_set_rsa_padding(vm, options, key->u.a.pkey, pctx, alg->type); if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -3975,7 +3999,7 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t * } if (alg->type == NJS_ALGORITHM_ECDSA) { - ret = njs_convert_der_to_p1363(vm, key->pkey, dst, outlen, + ret = njs_convert_der_to_p1363(vm, key->u.a.pkey, dst, outlen, &dst, &outlen); if (njs_slow_path(ret != NJS_OK)) { goto fail; @@ -3984,7 +4008,7 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t * } else { if (alg->type == NJS_ALGORITHM_ECDSA) { - ret = njs_convert_p1363_to_der(vm, key->pkey, sig.start, + ret = njs_convert_p1363_to_der(vm, key->u.a.pkey, sig.start, sig.length, &sig.start, &sig.length); if (njs_slow_path(ret != NJS_OK)) { @@ -4087,8 +4111,8 @@ njs_webcrypto_cleanup_pkey(void *data) { njs_webcrypto_key_t *key = data; - if (key->pkey != NULL) { - EVP_PKEY_free(key->pkey); + if (!key->alg->raw) { + EVP_PKEY_free(key->u.a.pkey); } } From xeioex at nginx.com Wed May 24 04:21:30 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 24 May 2023 04:21:30 +0000 Subject: [njs] WebCrypto: introduced CryptoKey properties. Message-ID: details: https://hg.nginx.org/njs/rev/873830a0a78f branches: changeset: 2132:873830a0a78f user: Dmitry Volyntsev date: Tue May 23 20:58:40 2023 -0700 description: WebCrypto: introduced CryptoKey properties. The following properties for CryptoKey were added: algorithm, extractable, type, usages. diffstat: external/njs_webcrypto_module.c | 376 +++++++++++++++++++++++++++++++++++---- test/ts/test.ts | 6 + test/webcrypto/export.t.js | 117 ++++++++++++ ts/njs_webcrypto.d.ts | 51 +++++- 4 files changed, 502 insertions(+), 48 deletions(-) diffs (751 lines): diff -r 646374a97d8c -r 873830a0a78f external/njs_webcrypto_module.c --- a/external/njs_webcrypto_module.c Mon May 22 22:48:59 2023 -0700 +++ b/external/njs_webcrypto_module.c Tue May 23 20:58:40 2023 -0700 @@ -128,6 +128,14 @@ static njs_int_t njs_ext_unwrap_key(njs_ njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_ext_wrap_key(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_key_ext_algorithm(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_key_ext_extractable(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_key_ext_type(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_key_ext_usages(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_ext_get_random_values(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); @@ -138,16 +146,17 @@ 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_opaque_value_t *retval, - unsigned mask); +static njs_int_t njs_key_ops(njs_vm_t *vm, njs_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); static njs_int_t njs_algorithm_hash(njs_vm_t *vm, njs_value_t *value, njs_webcrypto_hash_t *hash); +static njs_str_t *njs_algorithm_hash_name(njs_webcrypto_hash_t hash); static const EVP_MD *njs_algorithm_hash_digest(njs_webcrypto_hash_t hash); static njs_int_t njs_algorithm_curve(njs_vm_t *vm, njs_value_t *value, int *curve); +static njs_str_t *njs_algorithm_curve_name(int curve); static njs_int_t njs_webcrypto_result(njs_vm_t *vm, njs_opaque_value_t *result, njs_int_t rc, njs_value_t *retval); @@ -414,18 +423,6 @@ static njs_str_t njs_webcrypto_alg_aes_n }; -static njs_external_t njs_ext_webcrypto_crypto_key[] = { - - { - .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, - .name.symbol = NJS_SYMBOL_TO_STRING_TAG, - .u.property = { - .value = "CryptoKey", - } - }, -}; - - static njs_external_t njs_ext_subtle_webcrypto[] = { { @@ -575,6 +572,55 @@ static njs_external_t njs_ext_subtle_we }; + +static njs_external_t njs_ext_webcrypto_crypto_key[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "CryptoKey", + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("algorithm"), + .enumerable = 1, + .u.property = { + .handler = njs_key_ext_algorithm, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("extractable"), + .enumerable = 1, + .u.property = { + .handler = njs_key_ext_extractable, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("type"), + .enumerable = 1, + .u.property = { + .handler = njs_key_ext_type, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("usages"), + .enumerable = 1, + .u.property = { + .handler = njs_key_ext_usages, + } + }, +}; + + static njs_external_t njs_ext_webcrypto[] = { { @@ -633,7 +679,11 @@ static const njs_str_t string_ext = njs 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_hash = njs_str("hash"); +static const njs_str_t string_name = njs_str("name"); static const njs_str_t string_length = njs_str("length"); +static const njs_str_t string_ml = njs_str("modulusLength"); +static const njs_str_t string_curve = njs_str("namedCurve"); static njs_int_t njs_webcrypto_crypto_key_proto_id; @@ -1865,15 +1915,15 @@ njs_export_jwk_rsa(njs_vm_t *vm, njs_web static njs_int_t njs_export_jwk_ec(njs_vm_t *vm, njs_webcrypto_key_t *key, njs_value_t *retval) { - int nid, group_bits, group_bytes; - BIGNUM *x_bn, *y_bn; - njs_int_t ret; - 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; + int nid, group_bits, group_bytes; + BIGNUM *x_bn, *y_bn; + njs_int_t ret; + njs_str_t *cname; + 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; x_bn = NULL; y_bn = NULL; @@ -1920,15 +1970,11 @@ njs_export_jwk_ec(njs_vm_t *vm, njs_webc nid = EC_GROUP_get_curve_name(group); - for (e = &njs_webcrypto_curve[0]; e->name.length != 0; e++) { - if ((uintptr_t) nid == e->value) { - (void) njs_vm_value_string_set(vm, njs_value_arg(&name), - e->name.start, e->name.length); - break; - } - } - - if (e->name.length == 0) { + cname = njs_algorithm_curve_name(nid); + (void) njs_vm_value_string_set(vm, njs_value_arg(&name), + cname->start, cname->length); + + if (cname->length == 0) { njs_vm_error(vm, "Unsupported JWK EC curve: %s", OBJ_nid2sn(nid)); goto fail; } @@ -2069,7 +2115,7 @@ njs_export_jwk_asymmetric(njs_vm_t *vm, return NJS_ERROR; } - ret = njs_key_ops(vm, &ops, key->usage); + ret = njs_key_ops(vm, njs_value_arg(&ops), key->usage); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -2124,7 +2170,7 @@ njs_export_jwk_oct(njs_vm_t *vm, njs_web } } - ret = njs_key_ops(vm, &ops, key->usage); + ret = njs_key_ops(vm, njs_value_arg(&ops), key->usage); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -2356,7 +2402,6 @@ njs_ext_generate_key(njs_vm_t *vm, njs_v njs_opaque_value_t value, pub, priv; njs_webcrypto_algorithm_t *alg; - static const njs_str_t string_ml = njs_str("modulusLength"); static const njs_str_t string_priv = njs_str("privateKey"); static const njs_str_t string_pub = njs_str("publicKey"); @@ -4076,6 +4121,225 @@ njs_ext_wrap_key(njs_vm_t *vm, njs_value static njs_int_t +njs_key_ext_algorithm(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + u_char *start; + njs_int_t ret; + njs_str_t *name; + const BIGNUM *n_bn, *e_bn; + const EC_GROUP *group; + njs_opaque_value_t alg, name_s, val, hash; + njs_webcrypto_key_t *key; + + static const njs_str_t string_pexponent = njs_str("publicExponent"); + + key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, value); + if (njs_slow_path(key == NULL)) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + name = &njs_webcrypto_alg[key->alg->type].name; + ret = njs_vm_value_string_set(vm, njs_value_arg(&alg), name->start, + name->length); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + (void) njs_vm_value_string_set(vm, njs_value_arg(&name_s), + (u_char *) "name", njs_length("name")); + + ret = njs_vm_object_alloc(vm, retval, &name_s, &alg, NULL); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + switch (key->alg->type) { + case NJS_ALGORITHM_RSASSA_PKCS1_v1_5: + case NJS_ALGORITHM_RSA_PSS: + case NJS_ALGORITHM_RSA_OAEP: + /* RsaHashedKeyGenParams */ + + njs_assert(key->u.a.pkey != NULL); + njs_assert(EVP_PKEY_id(key->u.a.pkey) == EVP_PKEY_RSA); + + njs_rsa_get0_key(njs_pkey_get_rsa_key(key->u.a.pkey), &n_bn, &e_bn, + NULL); + + njs_value_number_set(njs_value_arg(&val), BN_num_bits(n_bn)); + + ret = njs_vm_object_prop_set(vm, retval, &string_ml, &val); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + start = njs_mp_alloc(njs_vm_memory_pool(vm), BN_num_bytes(e_bn)); + if (njs_slow_path(start == NULL)) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + BN_bn2bin(e_bn, start); + + ret = njs_vm_value_buffer_set(vm, njs_value_arg(&val), start, + BN_num_bytes(e_bn)); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_pexponent, &val); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + name = njs_algorithm_hash_name(key->hash); + ret = njs_vm_value_string_set(vm, njs_value_arg(&hash), name->start, + name->length); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_object_alloc(vm, njs_value_arg(&val), NULL); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, njs_value_arg(&val), &string_name, + &hash); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_hash, &val); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + break; + + case NJS_ALGORITHM_AES_GCM: + case NJS_ALGORITHM_AES_CTR: + case NJS_ALGORITHM_AES_CBC: + /* AesKeyGenParams */ + + njs_value_number_set(njs_value_arg(&val), key->u.s.raw.length * 8); + + ret = njs_vm_object_prop_set(vm, retval, &string_length, &val); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + break; + + case NJS_ALGORITHM_ECDSA: + case NJS_ALGORITHM_ECDH: + /* EcKeyGenParams */ + + njs_assert(key->u.a.pkey != NULL); + njs_assert(EVP_PKEY_id(key->u.a.pkey) == EVP_PKEY_EC); + + group = EC_KEY_get0_group(njs_pkey_get_ec_key(key->u.a.pkey)); + + name = njs_algorithm_curve_name(EC_GROUP_get_curve_name(group)); + + ret = njs_vm_value_string_set(vm, njs_value_arg(&val), name->start, + name->length); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_curve, &val); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + break; + + case NJS_ALGORITHM_HMAC: + default: + /* HmacKeyGenParams */ + + name = njs_algorithm_hash_name(key->hash); + ret = njs_vm_value_string_set(vm, njs_value_arg(&val), name->start, + name->length); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_object_prop_set(vm, retval, &string_hash, &val); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + break; + } + + return NJS_OK; +} + + +static njs_int_t +njs_key_ext_extractable(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + njs_webcrypto_key_t *key; + + key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, value); + if (njs_slow_path(key == NULL)) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + njs_value_boolean_set(retval, key->extractable); + + return NJS_OK; +} + + +static njs_int_t +njs_key_ext_type(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + const char *type; + njs_webcrypto_key_t *key; + + key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, value); + if (njs_slow_path(key == NULL)) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + if (key->alg->raw) { + (void) njs_vm_value_string_set(vm, retval, (u_char *) "secret", + njs_length("secret")); + } else { + type = key->u.a.privat ? "private": "public"; + (void) njs_vm_value_string_set(vm, retval, (u_char *) type, + key->u.a.privat ? 7 : 6); + } + + return NJS_OK; +} + + +static njs_int_t +njs_key_ext_usages(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + njs_webcrypto_key_t *key; + + key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id, value); + if (njs_slow_path(key == NULL)) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + return njs_key_ops(vm, retval, key->usage); +} + + +static njs_int_t njs_ext_get_random_values(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { @@ -4253,20 +4517,20 @@ njs_key_usage(njs_vm_t *vm, njs_value_t static njs_int_t -njs_key_ops(njs_vm_t *vm, njs_opaque_value_t *retval, unsigned mask) +njs_key_ops(njs_vm_t *vm, njs_value_t *retval, unsigned mask) { njs_int_t ret; njs_value_t *value; njs_webcrypto_entry_t *e; - ret = njs_vm_array_alloc(vm, njs_value_arg(retval), 4); + ret = njs_vm_array_alloc(vm, retval, 4); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } for (e = &njs_webcrypto_usage[0]; e->name.length != 0; e++) { if (mask & e->value) { - value = njs_vm_array_push(vm, njs_value_arg(retval)); + value = njs_vm_array_push(vm, retval); if (value == NULL) { return NJS_ERROR; } @@ -4293,8 +4557,6 @@ njs_key_algorithm(njs_vm_t *vm, njs_valu njs_webcrypto_entry_t *e; njs_webcrypto_algorithm_t *alg; - static const njs_str_t string_name = njs_str("name"); - if (njs_value_is_object(options)) { val = njs_vm_object_prop(vm, options, &string_name, &name); if (njs_slow_path(val == NULL)) { @@ -4358,8 +4620,6 @@ njs_algorithm_hash(njs_vm_t *vm, njs_val njs_opaque_value_t value; njs_webcrypto_entry_t *e; - static const njs_str_t string_hash = njs_str("hash"); - if (njs_value_is_object(options)) { val = njs_vm_object_prop(vm, options, &string_hash, &value); if (njs_slow_path(val == NULL)) { @@ -4390,6 +4650,21 @@ njs_algorithm_hash(njs_vm_t *vm, njs_val } +static njs_str_t * +njs_algorithm_hash_name(njs_webcrypto_hash_t hash) +{ + njs_webcrypto_entry_t *e; + + for (e = &njs_webcrypto_hash[0]; e->name.length != 0; e++) { + if (e->value == hash) { + return &e->name; + } + } + + return &e->name; +} + + static const EVP_MD * njs_algorithm_hash_digest(njs_webcrypto_hash_t hash) { @@ -4421,8 +4696,6 @@ njs_algorithm_curve(njs_vm_t *vm, njs_va njs_opaque_value_t value; njs_webcrypto_entry_t *e; - static const njs_str_t string_curve = njs_str("namedCurve"); - if (*curve != 0) { return NJS_OK; } @@ -4452,6 +4725,21 @@ njs_algorithm_curve(njs_vm_t *vm, njs_va } +static njs_str_t * +njs_algorithm_curve_name(int curve) +{ + njs_webcrypto_entry_t *e; + + for (e = &njs_webcrypto_curve[0]; e->name.length != 0; e++) { + if (e->value == (uintptr_t) curve) { + return &e->name; + } + } + + return &e->name; +} + + static njs_int_t njs_promise_trampoline(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) diff -r 646374a97d8c -r 873830a0a78f test/ts/test.ts --- a/test/ts/test.ts Mon May 22 22:48:59 2023 -0700 +++ b/test/ts/test.ts Tue May 23 20:58:40 2023 -0700 @@ -246,13 +246,19 @@ async function crypto_object(keyData: Ar publicExponent: new Uint8Array([1, 0, 1])}, true, ['sign', 'verify']); + pair.privateKey.extractable; + pair.publicKey.algorithm.name; + let hkey = await crypto.subtle.generateKey({name: "HMAC", hash: "SHA-384"}, true, ['sign', 'verify']); + hkey.algorithm.name; let akey = await crypto.subtle.generateKey({name: "AES-GCM", length: 256}, true, ['encrypt', 'decrypt']); + + akey.type; } function buffer(b: Buffer) { diff -r 646374a97d8c -r 873830a0a78f test/webcrypto/export.t.js --- a/test/webcrypto/export.t.js Mon May 22 22:48:59 2023 -0700 +++ b/test/webcrypto/export.t.js Tue May 23 20:58:40 2023 -0700 @@ -35,6 +35,8 @@ async function test(params) { let key = await load_key(params); let exp = await crypto.subtle.exportKey(params.export.fmt, key); + validate_key(key, params); + if (params.check && !params.check(exp, params)) { throw Error(`failed check`); } @@ -92,6 +94,121 @@ function p(args, default_opts) { return params; } +function validate_key(key, params) { + let opts; + + if (params.generate_keys) { + opts = params.generate_keys; + + } else if (params.generate_key) { + opts = params.generate_key; + + } else { + opts = params.key; + } + + if (opts.extractable != key.extractable) { + throw Error(`unexpected generated key.extractable: ${key.extractable}`); + } + + switch (key.type) { + case "secret": + /* AesKeyGenParams */ + + switch (key.algorithm.name) { + case "AES-CBC": + case "AES-CTR": + case "AES-GCM": + let length = key.algorithm.length; + + switch (length) { + case 128: + case 192: + case 256: + break; + default: + throw Error(`unexpected symmetric generated key.algorithm.length: ${length}`); + } + + /* FALLTHROUGH */ + + case "HMAC": + if (!compareObjects(opts.usage, key.usages)) { + throw Error(`unexpected symmetric generated key.usages: ${key.usages}`); + } + + break; + + default: + throw Error(`unexpected symmetric generated key.algorithm.name: ${key.algorithm.name}`); + } + + break; + case "private": + case "public": + if (opts.expected && !compareObjects(opts.expected.key_ops, key.usages)) { + throw Error(`unexpected asymmetric generated key.usages: ${key.usages}`); + } + + switch (key.algorithm.name) { + case "RSA-OAEP": + case "RSA-PSS": + case "RSASSA-PKCS1-v1_5": + /* RsaHashedKeyGenParams */ + + let mlength = key.algorithm.modulusLength; + let pexp = key.algorithm.publicExponent; + let hash = key.algorithm.hash.name; + + switch (mlength) { + case 1024: + case 2048: + case 4096: + break; + default: + throw Error(`unexpected asymmetric generated key.algorithm.modulusLength: ${mlength}`); + } + + if (!compareObjects(new Uint8Array([1, 0, 1]), pexp)) { + throw Error(`unexpected asymmetric generated key.algorithm.publicExponent: ${pexp}`); + } + + switch (hash) { + case "SHA-1": + case "SHA-256": + case "SHA-384": + case "SHA-512": + break; + default: + throw Error(`unexpected asymmetric generated key.algorithm.hash.name: ${hash}`); + } + + break; + case "ECDSA": + case "ECDH": + /* EcKeyGenParams */ + + let crv = key.algorithm.namedCurve; + switch (crv) { + case "P-256": + case "P-384": + case "P-521": + break; + default: + throw Error(`unexpected asymmetric generated key.algorithm.namedCurve: ${crv}`); + } + + break; + default: + throw Error(`unexpected asymmetric generated key.algorithm.name: ${key.algorithm.name}`); + } + + break; + default: + throw Error(`unexpected generated key.type: ${key.type}`); + } +} + function validate_property(exp, p, exp_len) { if (!exp[p]) { throw Error(`"${p}" is not found in ${JSON.stringify(exp)}`); diff -r 646374a97d8c -r 873830a0a78f ts/njs_webcrypto.d.ts --- a/ts/njs_webcrypto.d.ts Mon May 22 22:48:59 2023 -0700 +++ b/ts/njs_webcrypto.d.ts Tue May 23 20:58:40 2023 -0700 @@ -136,6 +136,35 @@ type SignOrVerifyAlgorithm = | "RSASSA-PKCS1-v1_5"; interface CryptoKey { + /* + * An object describing the algorithm for which this key can be used + * and any associated extra parameters. + * @since 0.8.0 + */ + readonly algorithm: GenerateAlgorithm; + /* + * A boolean value that is true if the key can be exported and false if not. + * @since 0.8.0 + */ + readonly extractable: boolean; + /* + * A string value indicates which kind of key is represented by the object. + * + * It can have the following values: + * "secret": This key is a secret key for use with a symmetric algorithm. + * "private": This key is the private half of an asymmetric algorithm's CryptoKeyPair. + * "public": This key is the public half of an asymmetric algorithm's CryptoKeyPair. + * @since 0.8.0 + */ + readonly type: string; + + /* + * An array of strings indicating what this key can be used for. + * Possible array values: "encrypt", "decrypt", "sign", "verify", + * "deriveKey", "deriveBits", "wrapKey", "unwrapKey". + * @since 0.8.0 + */ + readonly usages: Array; } type CryptoKeyPair = { privateKey: CryptoKey, publicKey: CryptoKey }; @@ -233,8 +262,7 @@ interface SubtleCrypto { key: CryptoKey): Promise; /** - * Generates a key for symmetric algorithms or a keypair - * for asymmetric algorithms. + * Generates a key for symmetric algorithms. * * @since 0.7.10 * @param algorithm Dictionary object defining the type of key to generate @@ -244,9 +272,24 @@ interface SubtleCrypto { * Possible array values: "encrypt", "decrypt", "sign", "verify", * "deriveKey", "deriveBits", "wrapKey", "unwrapKey". */ - generateKey(algorithm: GenerateAlgorithm, + generateKey(algorithm: HmacKeyGenParams | AesKeyGenParams, extractable: boolean, - usage: Array): Promise; + usage: Array): Promise; + + /** + * Generates a key for asymmetric algorithms. + * + * @since 0.7.10 + * @param algorithm Dictionary object defining the type of key to generate + * and providing extra algorithm-specific parameters. + * @param extractable Boolean indicating whether a key can be exported. + * @param usage Array indicating what can be done with the key. + * Possible array values: "encrypt", "decrypt", "sign", "verify", + * "deriveKey", "deriveBits", "wrapKey", "unwrapKey". + */ + generateKey(algorithm: RsaHashedKeyGenParams | EcKeyGenParams, + extractable: boolean, + usage: Array): Promise; /** * Generates a digital signature. From mdounin at mdounin.ru Wed May 24 14:39:14 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Wed, 24 May 2023 17:39:14 +0300 Subject: Q: http2 and http1 virtual hosts both works via HTTP/2 - bug of feature? In-Reply-To: References: Message-ID: Hello! On Wed, May 24, 2023 at 01:07:00AM +0300, Andrey Kulikov wrote: > Observed nginx's version 1.22.1 questionable behaviour with two virtual > hosts, one with H2 - enabled, second without http2 support. > Both on the same IP and port, with different domain names/server names. > When browsers make requests to a second domain, h2 being ALPN-negotiated, > and data transferred via HTTP/2, in spite of http2 was not configured on > that virtual host. This is a mailing list about nginx development. For user-level questions, please use the nginx@ mailing list instead. Thank you. [...] -- Maxim Dounin http://mdounin.ru/ From pluknet at nginx.com Thu May 25 15:30:27 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 25 May 2023 19:30:27 +0400 Subject: [PATCH] Tests: added has_feature() test for CryptX Message-ID: <4dad7cf8ebe807f9230a.1685028627@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1685028552 -14400 # Thu May 25 19:29:12 2023 +0400 # Node ID 4dad7cf8ebe807f9230a82dde5fc02b5e12e390b # Parent acd68670b0649da2fbcfd3e8231c9805d50abcb5 Tests: added has_feature() test for CryptX. diff --git a/h3_absolute_redirect.t b/h3_absolute_redirect.t --- a/h3_absolute_redirect.t +++ b/h3_absolute_redirect.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite/) +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite cryptx/) ->has_daemon('openssl')->plan(23); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_headers.t b/h3_headers.t --- a/h3_headers.t +++ b/h3_headers.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite/) +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite cryptx/) ->has_daemon('openssl')->plan(68) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_keepalive.t b/h3_keepalive.t --- a/h3_keepalive.t +++ b/h3_keepalive.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3/) +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) ->has_daemon('openssl')->plan(15) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_limit_conn.t b/h3_limit_conn.t --- a/h3_limit_conn.t +++ b/h3_limit_conn.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 limit_conn proxy/) +my $t = Test::Nginx->new()->has(qw/http http_v3 limit_conn proxy cryptx/) ->has_daemon('openssl')->plan(2); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_limit_req.t b/h3_limit_req.t --- a/h3_limit_req.t +++ b/h3_limit_req.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy limit_req/) +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy limit_req cryptx/) ->has_daemon('openssl')->plan(6); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_proxy.t b/h3_proxy.t --- a/h3_proxy.t +++ b/h3_proxy.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy/) +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy cryptx/) ->has_daemon('openssl')->plan(3) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_proxy_max_temp_file_size.t b/h3_proxy_max_temp_file_size.t --- a/h3_proxy_max_temp_file_size.t +++ b/h3_proxy_max_temp_file_size.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy/) +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy cryptx/) ->has_daemon('openssl')->plan(4); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_reusable.t b/h3_reusable.t --- a/h3_reusable.t +++ b/h3_reusable.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3/) +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) ->has_daemon('openssl')->plan(1) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_server_name.t b/h3_server_name.t --- a/h3_server_name.t +++ b/h3_server_name.t @@ -24,11 +24,8 @@ 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 $@; - my $t = Test::Nginx->new() - ->has(qw/http http_ssl http_v2 http_v3 rewrite socket_ssl_alpn/) + ->has(qw/http http_ssl http_v2 http_v3 rewrite socket_ssl_alpn cryptx/) ->has_daemon('openssl')->plan(6); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_server_tokens.t b/h3_server_tokens.t --- a/h3_server_tokens.t +++ b/h3_server_tokens.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3 rewrite/) +my $t = Test::Nginx->new()->has(qw/http http_v3 rewrite cryptx/) ->has_daemon('openssl')->plan(12); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_ssl_reject_handshake.t b/h3_ssl_reject_handshake.t --- a/h3_ssl_reject_handshake.t +++ b/h3_ssl_reject_handshake.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3/) +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) ->has_daemon('openssl')->plan(7) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_ssl_session_reuse.t b/h3_ssl_session_reuse.t --- a/h3_ssl_session_reuse.t +++ b/h3_ssl_session_reuse.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3/) +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) ->has_daemon('openssl')->plan(8) ->write_file_expand('nginx.conf', <<'EOF'); diff --git a/h3_trailers.t b/h3_trailers.t --- a/h3_trailers.t +++ b/h3_trailers.t @@ -23,10 +23,7 @@ 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 $@; - -my $t = Test::Nginx->new()->has(qw/http http_v3/) +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) ->has_daemon('openssl')->plan(8); $t->write_file_expand('nginx.conf', <<'EOF'); diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm --- a/lib/Test/Nginx.pm +++ b/lib/Test/Nginx.pm @@ -288,6 +288,13 @@ sub has_feature($) { return 1; } + if ($feature eq 'cryptx') { + eval { require Crypt::Misc; }; + return 0 if $@; + eval { die if $Crypt::Misc::VERSION < 0.067; }; + return !$@; + } + return 0; } diff --git a/quic_migration.t b/quic_migration.t --- a/quic_migration.t +++ b/quic_migration.t @@ -23,13 +23,10 @@ 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/) +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) ->has_daemon('openssl')->plan(2); $t->write_file_expand('nginx.conf', <<'EOF'); From xeioex at nginx.com Fri May 26 03:45:40 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 26 May 2023 03:45:40 +0000 Subject: [njs] Types: added TS types for ngx properties added in 25b55a064e42. Message-ID: details: https://hg.nginx.org/njs/rev/a0807bc0ec72 branches: changeset: 2133:a0807bc0ec72 user: Dmitry Volyntsev date: Tue May 23 23:47:43 2023 -0700 description: Types: added TS types for ngx properties added in 25b55a064e42. diffstat: test/ts/test.ts | 6 ++-- ts/ngx_core.d.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diffs (98 lines): diff -r 873830a0a78f -r a0807bc0ec72 test/ts/test.ts --- a/test/ts/test.ts Tue May 23 20:58:40 2023 -0700 +++ b/test/ts/test.ts Tue May 23 23:47:43 2023 -0700 @@ -297,7 +297,7 @@ function njs_object() { } function ngx_object() { - ngx.log(ngx.INFO, 'asdf'); - ngx.log(ngx.WARN, Buffer.from('asdf')); - ngx.log(ngx.ERR, 'asdf'); + ngx.log(ngx.INFO, ngx.conf_prefix); + ngx.log(ngx.WARN, Buffer.from(ngx.error_log_path)); + ngx.log(ngx.ERR, ngx.version); } diff -r 873830a0a78f -r a0807bc0ec72 ts/ngx_core.d.ts --- a/ts/ngx_core.d.ts Tue May 23 20:58:40 2023 -0700 +++ b/ts/ngx_core.d.ts Tue May 23 23:47:43 2023 -0700 @@ -243,16 +243,38 @@ interface NgxFetchOptions { } interface NgxObject { - readonly INFO: number; - readonly WARN: number; + /** + * A string containing an optional nginx build name, corresponds to the + * --build=name argument of the configure script, by default is "" + * @since 0.8.0 + */ + readonly build: string; + /** + * A string containing the file path to current nginx configuration file + * @since 0.8.0 + */ + readonly conf_file_path: string; + /** + * A string containing the file path to directory where nginx is currently + * looking for configuration + * @since 0.7.8 + */ + readonly conf_prefix: string; + /** + * The error level constant for ngx.log() function. + * @since 0.5.1 + */ readonly ERR: number; /** - * Writes a string to the error log with the specified level - * of logging. - * @param level Log level (ngx.INFO, ngx.WARN, ngx.ERR). - * @param message Message to log. + * A string containing the file path to the current error log file + * @since 0.8.0 */ - log(level: number, message: NjsStringOrBuffer): void; + readonly error_log_path: string; + /** + * The info level constant for ngx.log() function. + * @since 0.5.1 + */ + readonly INFO: number; /** * Makes a request to fetch an URL. * Returns a Promise that resolves with the Response object. @@ -262,6 +284,34 @@ interface NgxObject { * @since 0.5.1 */ fetch(init: NjsStringOrBuffer | Request, options?: NgxFetchOptions): Promise; + /** + * Writes a string to the error log with the specified level + * of logging. + * @param level Log level (ngx.INFO, ngx.WARN, ngx.ERR). + * @param message Message to log. + */ + log(level: number, message: NjsStringOrBuffer): void; + /** + * A string containing the file path to a directory that keeps server files + * @since 0.8.0 + */ + readonly prefix: string; + /** + * A string containing nginx version, for example: "1.25.0" + * @since 0.8.0 + */ + readonly version: string; + /** + * A number containing nginx version, for example: 1025000 + * @since 0.8.0 + */ + readonly version_number: number; + /** + * The warn level constant for ngx.log() function. + * @since 0.5.1 + */ + readonly WARN: number; + } declare const ngx: NgxObject; From xeioex at nginx.com Fri May 26 03:45:42 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 26 May 2023 03:45:42 +0000 Subject: [njs] Implemented Array.prototype.toSorted(). Message-ID: details: https://hg.nginx.org/njs/rev/398f4de34fe7 branches: changeset: 2134:398f4de34fe7 user: Dmitry Volyntsev date: Wed May 24 22:04:38 2023 -0700 description: Implemented Array.prototype.toSorted(). diffstat: src/njs_array.c | 457 +++++++++++++++++++++++++++++++--------------- src/test/njs_unit_test.c | 30 +++ 2 files changed, 332 insertions(+), 155 deletions(-) diffs (547 lines): diff -r a0807bc0ec72 -r 398f4de34fe7 src/njs_array.c --- a/src/njs_array.c Tue May 23 23:47:43 2023 -0700 +++ b/src/njs_array.c Wed May 24 22:04:38 2023 -0700 @@ -2586,16 +2586,207 @@ exception: } +static njs_array_sort_slot_t * +njs_sort_indexed_properties(njs_vm_t *vm, njs_value_t *obj, int64_t length, + njs_function_t *compare, njs_bool_t skip_holes, int64_t *nslots, + int64_t *nunds) +{ + int64_t i, ilength, nlen; + njs_int_t ret; + njs_array_t *array, *keys; + njs_value_t *start, *strings, key; + njs_array_sort_ctx_t ctx; + njs_array_sort_slot_t *p, *end, *slots, *newslots; + + slots = NULL; + keys = NULL; + ctx.vm = vm; + ctx.function = compare; + ctx.strings.separate = 0; + ctx.strings.pointer = 0; + ctx.exception = 0; + + if (njs_fast_path(njs_is_fast_array(obj))) { + array = njs_array(obj); + start = array->start; + + slots = njs_mp_alloc(vm->mem_pool, + sizeof(njs_array_sort_slot_t) * length); + if (njs_slow_path(slots == NULL)) { + njs_memory_error(vm); + return NULL; + } + + *nunds = 0; + p = slots; + + for (i = 0; i < length; i++) { + if (njs_fast_path(njs_is_valid(&start[i]))) { + /* not an empty value at index i. */ + njs_value_assign(&p->value, &start[i]); + + } else { + njs_uint32_to_string(&key, i); + ret = njs_value_property(vm, obj, &key, &p->value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + if (ret == NJS_DECLINED && skip_holes) { + continue; + } + } + + if (njs_slow_path(njs_is_undefined(&p->value))) { + (*nunds)++; + continue; + } + + p->pos = i; + p->str = NULL; + p++; + } + + *nslots = p - slots; + + } else { + if (skip_holes) { + keys = njs_array_indices(vm, obj); + if (njs_slow_path(keys == NULL)) { + return NULL; + } + + slots = njs_mp_alloc(vm->mem_pool, + sizeof(njs_array_sort_slot_t) * keys->length); + if (njs_slow_path(slots == NULL)) { + njs_memory_error(vm); + ret = NJS_ERROR; + goto exception; + } + + *nunds = 0; + p = slots; + + ilength = njs_min(keys->length, length); + + for (i = 0; i < ilength; i++) { + ret = njs_value_property(vm, obj, &keys->start[i], &p->value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + if (ret == NJS_DECLINED) { + continue; + } + + if (njs_is_undefined(&p->value)) { + (*nunds)++; + continue; + } + + p->pos = i; + p->str = NULL; + p++; + } + + *nslots = p - slots; + + } else { + /* !skip_holes */ + + nlen = njs_min(length, 8); + slots = njs_mp_alloc(vm->mem_pool, + sizeof(njs_array_sort_slot_t) * nlen); + if (njs_slow_path(slots == NULL)) { + njs_memory_error(vm); + ret = NJS_ERROR; + goto exception; + } + + p = slots; + end = slots + nlen; + + for (i = 0; i < length; i++) { + if (p >= end) { + nlen = njs_min(njs_max((p - slots) * 2, 8), length); + newslots = njs_mp_alloc(vm->mem_pool, + sizeof(njs_array_sort_slot_t) * nlen); + if (njs_slow_path(newslots == NULL)) { + njs_memory_error(vm); + ret = NJS_ERROR; + goto exception; + } + + if (slots != NULL) { + p = (void *) njs_cpymem(newslots, slots, + sizeof(njs_array_sort_slot_t) * (p - slots)); + njs_mp_free(vm->mem_pool, slots); + + } else { + p = newslots; + } + + slots = newslots; + end = slots + nlen; + } + + ret = njs_value_property_i64(vm, obj, i, &p->value); + if (njs_slow_path(ret == NJS_ERROR)) { + ret = NJS_ERROR; + goto exception; + } + + if (njs_is_undefined(&p->value)) { + continue; + } + + p->pos = i; + p->str = NULL; + p++; + } + + *nslots = p - slots; + *nunds = length - *nslots; + } + } + + strings = njs_arr_init(vm->mem_pool, &ctx.strings, NULL, *nslots + 1, + sizeof(njs_value_t)); + if (njs_slow_path(strings == NULL)) { + njs_mp_free(vm->mem_pool, slots); + return NULL; + } + + njs_qsort(slots, *nslots, sizeof(njs_array_sort_slot_t), njs_array_compare, + &ctx); + + ret = NJS_OK; + njs_arr_destroy(&ctx.strings); + +exception: + + if (keys != NULL) { + njs_array_destroy(vm, keys); + } + + if ((ctx.exception || ret == NJS_ERROR) && slots != NULL) { + njs_mp_free(vm->mem_pool, slots); + return NULL; + } + + return slots; +} + + static njs_int_t njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - int64_t i, und, len, nlen, length; - njs_int_t ret, fast_path; - njs_array_t *array; - njs_value_t *this, *comparefn, *start, *strings; - njs_array_sort_ctx_t ctx; - njs_array_sort_slot_t *p, *end, *slots, *nslots; + int64_t i, nslots, nunds, length; + njs_int_t ret; + njs_value_t *this, *comparefn; + njs_function_t *compare; + njs_array_sort_slot_t *slots; comparefn = njs_arg(args, nargs, 1); @@ -2605,10 +2796,10 @@ njs_array_prototype_sort(njs_vm_t *vm, n return NJS_ERROR; } - ctx.function = njs_function(comparefn); + compare = njs_function(comparefn); } else { - ctx.function = NULL; + compare = NULL; } this = njs_argument(args, 0); @@ -2623,158 +2814,34 @@ njs_array_prototype_sort(njs_vm_t *vm, n return ret; } - if (njs_slow_path(length < 2)) { - njs_value_assign(retval, this); - return NJS_OK; - } - - slots = NULL; - ctx.vm = vm; - ctx.strings.separate = 0; - ctx.strings.pointer = 0; - ctx.exception = 0; - - fast_path = njs_is_fast_array(this); - - if (njs_fast_path(fast_path)) { - array = njs_array(this); - start = array->start; - - slots = njs_mp_alloc(vm->mem_pool, - sizeof(njs_array_sort_slot_t) * length); - if (njs_slow_path(slots == NULL)) { - return NJS_ERROR; - } - - und = 0; - p = slots; - - for (i = 0; i < length; i++) { - if (njs_slow_path(!njs_is_valid(&start[i]))) { - fast_path = 0; - njs_mp_free(vm->mem_pool, slots); - slots = NULL; - goto slow_path; - } - - if (njs_slow_path(njs_is_undefined(&start[i]))) { - und++; - continue; - } - - p->value = start[i]; - p->pos = i; - p->str = NULL; - p++; - } - - len = p - slots; - - } else { - -slow_path: - - und = 0; - p = NULL; - end = NULL; - - for (i = 0; i < length; i++) { - if (p >= end) { - nlen = njs_min(njs_max((p - slots) * 2, 8), length); - nslots = njs_mp_alloc(vm->mem_pool, - sizeof(njs_array_sort_slot_t) * nlen); - if (njs_slow_path(nslots == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } - - if (slots != NULL) { - p = (void *) njs_cpymem(nslots, slots, - sizeof(njs_array_sort_slot_t) * (p - slots)); - njs_mp_free(vm->mem_pool, slots); - - } else { - p = nslots; - } - - slots = nslots; - end = slots + nlen; - } - - ret = njs_value_property_i64(vm, this, i, &p->value); - if (njs_slow_path(ret == NJS_ERROR)) { - ret = NJS_ERROR; - goto exception; - } - - if (ret == NJS_DECLINED) { - continue; - } - - if (njs_is_undefined(&p->value)) { - und++; - continue; - } - - p->pos = i; - p->str = NULL; - p++; - } - - len = p - slots; - } - - strings = njs_arr_init(vm->mem_pool, &ctx.strings, NULL, len + 1, - sizeof(njs_value_t)); - if (njs_slow_path(strings == NULL)) { + slots = njs_sort_indexed_properties(vm, this, length, compare, 1, &nslots, + &nunds); + if (njs_slow_path(slots == NULL)) { ret = NJS_ERROR; goto exception; } - njs_qsort(slots, len, sizeof(njs_array_sort_slot_t), njs_array_compare, - &ctx); - - if (ctx.exception) { - ret = NJS_ERROR; - goto exception; - } - - if (njs_fast_path(fast_path - && njs_is_fast_array(this) - && (njs_array(this)->length == length))) - { - array = njs_array(this); - start = array->start; - - for (i = 0; i < len; i++) { - start[i] = slots[i].value; - } - - for (i = len; und-- > 0; i++) { - start[i] = njs_value_undefined; + njs_assert(length >= (nslots + nunds)); + + for (i = 0; i < nslots; i++) { + ret = njs_value_property_i64_set(vm, this, i, &slots[i].value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; } - - } else { - for (i = 0; i < len; i++) { - ret = njs_value_property_i64_set(vm, this, i, &slots[i].value); - if (njs_slow_path(ret == NJS_ERROR)) { - goto exception; - } + } + + for (i = nslots; nunds-- > 0; i++) { + ret = njs_value_property_i64_set(vm, this, i, + njs_value_arg(&njs_value_undefined)); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; } - - for (i = len; und-- > 0; i++) { - ret = njs_value_property_i64_set(vm, this, i, - njs_value_arg(&njs_value_undefined)); - if (njs_slow_path(ret == NJS_ERROR)) { - goto exception; - } - } - - for (; i < length; i++) { - ret = njs_value_property_i64_delete(vm, this, i, NULL); - if (njs_slow_path(ret == NJS_ERROR)) { - goto exception; - } + } + + for (; i < length; i++) { + ret = njs_value_property_i64_delete(vm, this, i, NULL); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; } } @@ -2788,7 +2855,85 @@ exception: njs_mp_free(vm->mem_pool, slots); } - njs_arr_destroy(&ctx.strings); + return ret; +} + + +static njs_int_t +njs_array_prototype_to_sorted(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + int64_t i, nslots, nunds, length; + njs_int_t ret; + njs_array_t *array; + njs_value_t *this, *comparefn; + njs_function_t *compare; + njs_array_sort_slot_t *slots; + + comparefn = njs_arg(args, nargs, 1); + + if (njs_is_defined(comparefn)) { + if (njs_slow_path(!njs_is_function(comparefn))) { + njs_type_error(vm, "comparefn must be callable or undefined"); + return NJS_ERROR; + } + + compare = njs_function(comparefn); + + } else { + compare = NULL; + } + + this = njs_argument(args, 0); + + ret = njs_value_to_object(vm, this); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_value_length(vm, this, &length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + array = njs_array_alloc(vm, 0, length, 0); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + slots = njs_sort_indexed_properties(vm, this, length, compare, 0, &nslots, + &nunds); + if (njs_slow_path(slots == NULL)) { + ret = NJS_ERROR; + goto exception; + } + + njs_assert(length == (nslots + nunds)); + + njs_set_array(retval, array); + + for (i = 0; i < nslots; i++) { + ret = njs_value_property_i64_set(vm, retval, i, &slots[i].value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + } + + for (; i < length; i++) { + ret = njs_value_property_i64_set(vm, retval, i, + njs_value_arg(&njs_value_undefined)); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + } + + ret = NJS_OK; + +exception: + + if (slots != NULL) { + njs_mp_free(vm->mem_pool, slots); + } return ret; } @@ -2945,6 +3090,8 @@ static const njs_object_prop_t njs_arra NJS_DECLARE_PROP_NATIVE("splice", njs_array_prototype_splice, 2, 0), + NJS_DECLARE_PROP_NATIVE("toSorted", njs_array_prototype_to_sorted, 1, 0), + NJS_DECLARE_PROP_NATIVE("toString", njs_array_prototype_to_string, 0, 0), NJS_DECLARE_PROP_NATIVE("unshift", njs_array_prototype_unshift, 1, 0), diff -r a0807bc0ec72 -r 398f4de34fe7 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Tue May 23 23:47:43 2023 -0700 +++ b/src/test/njs_unit_test.c Wed May 24 22:04:38 2023 -0700 @@ -7332,6 +7332,36 @@ static njs_unit_test_t njs_test[] = "[a.length, a[0].toString(), a[63].toString()]"), njs_str("64,00,63") }, + { njs_str("Object.prototype[2] = 4;" + "njs.dump([undefined, 3, /*hole*/, 2, undefined, /*hole*/, 1].sort())"), + njs_str("[1,2,3,4,undefined,undefined,]") }, + + { njs_str("var a = [3,2,1]; [a.toSorted(), a]"), + njs_str("1,2,3,3,2,1") }, + + { njs_str("var a = [3,,1]; njs.dump([a.toSorted(), a.sort()])"), + njs_str("[[1,3,undefined],[1,3,]]") }, + + { njs_str("var a = {length:3, 0:'Z', 2:'A'};" + "njs.dump([Array.prototype.toSorted.call(a), Array.prototype.sort.call(a)])"), + njs_str("[['A','Z',undefined],{length:3,0:'A',1:'Z'}]") }, + + { njs_str("var a = {length: 1}; a.__proto__ = {0:'A'};" + "njs.dump([Array.prototype.toSorted.call(a), Array.prototype.sort.call(a)])"), + njs_str("[['A'],{length:1}]") }, + + { njs_str("Array.prototype.toSorted.call(true)"), + njs_str("") }, + + { njs_str("Array.prototype.toSorted.call({length: -2})"), + njs_str("") }, + + { njs_str("Array.prototype.toSorted.call({length: NaN})"), + njs_str("") }, + + { njs_str("Array.prototype.toSorted.call({length: 2**32})"), + njs_str("RangeError: Invalid array length") }, + /* Array.prototype.keys() Array.prototype.values() From pluknet at nginx.com Fri May 26 11:24:11 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Fri, 26 May 2023 15:24:11 +0400 Subject: [PATCH 0 of 1] Tests: unbreak reading new stderr data after eof ... Message-ID: Simple test to reproduce. #!/usr/bin/perl use warnings; use strict; open STDERR, '>', 'stderr' or die "Can't reopen STDERR: $!"; open my $stderr, '<', 'stderr' or die "Can't open stderr file: $!"; if (fork() == 0) { print STDERR "SEE-THIS"; sleep 1; print STDERR "AND-THAT"; exit 0; } use Data::Dumper; select undef, undef, undef, 0.1 until -s 'stderr' > 0; my $n = -s 'stderr'; print Dumper <$stderr>; print "size = $n pos = ", tell $stderr, "\n"; select undef, undef, undef, 0.1 until -s 'stderr' > $n; $n = -s 'stderr'; print Dumper <$stderr>; print "size = $n pos = ", tell $stderr, "\n"; ############################################################################### vanilla perl$ perl test_readline.t $VAR1 = 'SEE-THIS'; size = 8 pos = 8 $VAR1 = 'AND-THAT'; size = 16 pos = 16 80c1f1e45e8e$ perl test_readline.t $VAR1 = 'SEE-THIS'; size = 8 pos = 8 size = 16 pos = 8 From pluknet at nginx.com Fri May 26 11:24:12 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Fri, 26 May 2023 15:24:12 +0400 Subject: [PATCH 1 of 1] Tests: unbreak reading new stderr data after eof In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1685100180 -14400 # Fri May 26 15:23:00 2023 +0400 # Node ID dc3539be2a14a0cd0b8be9e1e9aae8c95b806828 # Parent 4dad7cf8ebe807f9230a82dde5fc02b5e12e390b Tests: unbreak reading new stderr data after eof. Tests don't expect to stop reading redirected stderr when end of file is reached, but rather to read new data being appended, similar to "tail -f". The behaviour is first changed in Ubuntu 23.04 Perl 5.36, by applying the upstream patch [1], and is expected for inclusion in the next Perl 5.38. The fix is to set filehandle's position to the end of file to clear the error state and allow to continue readline() further from that position. [1] https://github.com/Perl/perl5/commit/80c1f1e45e8e Updated mail_error_log.t and stream_error_log.t for consistency. diff --git a/error_log.t b/error_log.t --- a/error_log.t +++ b/error_log.t @@ -183,7 +183,9 @@ sub lines { my ($t, $file, $pattern) = @_; if ($file eq 'stderr') { - return map { $_ =~ /\Q$pattern\E/ } (<$stderr>); + my $value = map { $_ =~ /\Q$pattern\E/ } (<$stderr>); + seek $stderr, 0, Fcntl::SEEK_END; + return $value; } my $path = $t->testdir() . '/' . $file; diff --git a/mail_error_log.t b/mail_error_log.t --- a/mail_error_log.t +++ b/mail_error_log.t @@ -144,7 +144,9 @@ sub lines { my ($t, $file, $pattern) = @_; if ($file eq 'stderr') { - return map { $_ =~ /\Q$pattern\E/ } (<$stderr>); + my $value = map { $_ =~ /\Q$pattern\E/ } (<$stderr>); + seek $stderr, 0, Fcntl::SEEK_END; + return $value; } my $path = $t->testdir() . '/' . $file; diff --git a/stream_error_log.t b/stream_error_log.t --- a/stream_error_log.t +++ b/stream_error_log.t @@ -150,7 +150,9 @@ sub lines { my ($t, $file, $pattern) = @_; if ($file eq 'stderr') { - return map { $_ =~ /\Q$pattern\E/ } (<$stderr>); + my $value = map { $_ =~ /\Q$pattern\E/ } (<$stderr>); + seek $stderr, 0, Fcntl::SEEK_END; + return $value; } my $path = $t->testdir() . '/' . $file; From vl at inspert.ru Fri May 26 11:43:22 2023 From: vl at inspert.ru (Vladimir Homutov) Date: Fri, 26 May 2023 14:43:22 +0300 Subject: Enable QUIC with Tongsuo SSL library Message-ID: -------------- next part -------------- A non-text attachment was scrubbed... Name: quic-tongsuo-support.diff Type: text/x-diff Size: 1025 bytes Desc: not available URL: From mdounin at mdounin.ru Fri May 26 16:09:57 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 26 May 2023 19:09:57 +0300 Subject: Enable QUIC with Tongsuo SSL library In-Reply-To: References: Message-ID: Hello! On Fri, May 26, 2023 at 02:43:22PM +0300, Vladimir Homutov via nginx-devel wrote: > # HG changeset patch > # User Vladimir Khomutov > # Date 1677761453 -10800 > # Thu Mar 02 15:50:53 2023 +0300 > # Node ID 348772f63be2b77a893b8d101c6b6905382a5735 > # Parent 8eae1b4f1c5528b063351804168a6085f5f50b42 > QUIC: added support for the Tongsuo SSL library. > > For the needs of QUIC, this is basically openssl-1.1.1h with > BoringSSL-compatible QUIC support. > > The library was developed by AliBaba and was previously called BabaSSL, > thus macro names [1]. > > [1] https://github.com/Tongsuo-Project/Tongsuo > > 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 > @@ -12,6 +12,7 @@ > > #if defined OPENSSL_IS_BORINGSSL \ > || defined LIBRESSL_VERSION_NUMBER \ > + || defined BABASSL_VERSION_NUMBER \ > || NGX_QUIC_OPENSSL_COMPAT > #define NGX_QUIC_BORINGSSL_API 1 > #endif I don't like the idea of introducing explicit support for additional OpenSSL/LibreSSL/BoringSSL forks. There should be a way to detect this without checking individual libraries. Also, using something like --with-cc-opt="-DOPENSSL_IS_BORINGSSL" might be a good enough workaround in this particular case (untested though). -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Fri May 26 16:12:42 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 26 May 2023 19:12:42 +0300 Subject: [PATCH] Tests: added has_feature() test for CryptX In-Reply-To: <4dad7cf8ebe807f9230a.1685028627@enoparse.local> References: <4dad7cf8ebe807f9230a.1685028627@enoparse.local> Message-ID: Hello! On Thu, May 25, 2023 at 07:30:27PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1685028552 -14400 > # Thu May 25 19:29:12 2023 +0400 > # Node ID 4dad7cf8ebe807f9230a82dde5fc02b5e12e390b > # Parent acd68670b0649da2fbcfd3e8231c9805d50abcb5 > Tests: added has_feature() test for CryptX. > > diff --git a/h3_absolute_redirect.t b/h3_absolute_redirect.t > --- a/h3_absolute_redirect.t > +++ b/h3_absolute_redirect.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite cryptx/) > ->has_daemon('openssl')->plan(23); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/h3_headers.t b/h3_headers.t > --- a/h3_headers.t > +++ b/h3_headers.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy rewrite cryptx/) > ->has_daemon('openssl')->plan(68) > ->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/h3_keepalive.t b/h3_keepalive.t > --- a/h3_keepalive.t > +++ b/h3_keepalive.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) > ->has_daemon('openssl')->plan(15) > ->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/h3_limit_conn.t b/h3_limit_conn.t > --- a/h3_limit_conn.t > +++ b/h3_limit_conn.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 limit_conn proxy/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 limit_conn proxy cryptx/) > ->has_daemon('openssl')->plan(2); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/h3_limit_req.t b/h3_limit_req.t > --- a/h3_limit_req.t > +++ b/h3_limit_req.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy limit_req/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy limit_req cryptx/) > ->has_daemon('openssl')->plan(6); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/h3_proxy.t b/h3_proxy.t > --- a/h3_proxy.t > +++ b/h3_proxy.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy cryptx/) > ->has_daemon('openssl')->plan(3) > ->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/h3_proxy_max_temp_file_size.t b/h3_proxy_max_temp_file_size.t > --- a/h3_proxy_max_temp_file_size.t > +++ b/h3_proxy_max_temp_file_size.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 proxy/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 proxy cryptx/) > ->has_daemon('openssl')->plan(4); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/h3_reusable.t b/h3_reusable.t > --- a/h3_reusable.t > +++ b/h3_reusable.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) > ->has_daemon('openssl')->plan(1) > ->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/h3_server_name.t b/h3_server_name.t > --- a/h3_server_name.t > +++ b/h3_server_name.t > @@ -24,11 +24,8 @@ 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 $@; > - > my $t = Test::Nginx->new() > - ->has(qw/http http_ssl http_v2 http_v3 rewrite socket_ssl_alpn/) > + ->has(qw/http http_ssl http_v2 http_v3 rewrite socket_ssl_alpn cryptx/) > ->has_daemon('openssl')->plan(6); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/h3_server_tokens.t b/h3_server_tokens.t > --- a/h3_server_tokens.t > +++ b/h3_server_tokens.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3 rewrite/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 rewrite cryptx/) > ->has_daemon('openssl')->plan(12); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/h3_ssl_reject_handshake.t b/h3_ssl_reject_handshake.t > --- a/h3_ssl_reject_handshake.t > +++ b/h3_ssl_reject_handshake.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) > ->has_daemon('openssl')->plan(7) > ->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/h3_ssl_session_reuse.t b/h3_ssl_session_reuse.t > --- a/h3_ssl_session_reuse.t > +++ b/h3_ssl_session_reuse.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) > ->has_daemon('openssl')->plan(8) > ->write_file_expand('nginx.conf', <<'EOF'); > > diff --git a/h3_trailers.t b/h3_trailers.t > --- a/h3_trailers.t > +++ b/h3_trailers.t > @@ -23,10 +23,7 @@ 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 $@; > - > -my $t = Test::Nginx->new()->has(qw/http http_v3/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) > ->has_daemon('openssl')->plan(8); > > $t->write_file_expand('nginx.conf', <<'EOF'); > diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm > --- a/lib/Test/Nginx.pm > +++ b/lib/Test/Nginx.pm > @@ -288,6 +288,13 @@ sub has_feature($) { > return 1; > } > > + if ($feature eq 'cryptx') { > + eval { require Crypt::Misc; }; > + return 0 if $@; > + eval { die if $Crypt::Misc::VERSION < 0.067; }; > + return !$@; > + } > + > return 0; > } > > diff --git a/quic_migration.t b/quic_migration.t > --- a/quic_migration.t > +++ b/quic_migration.t > @@ -23,13 +23,10 @@ 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/) > +my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/) > ->has_daemon('openssl')->plan(2); > > $t->write_file_expand('nginx.conf', <<'EOF'); Looks good. -- Maxim Dounin http://mdounin.ru/ From pgnet.dev at gmail.com Fri May 26 16:19:20 2023 From: pgnet.dev at gmail.com (PGNet Dev) Date: Fri, 26 May 2023 12:19:20 -0400 Subject: QUIC docs with php-fpm & ssl cache Message-ID: <03deea54-560b-295e-42fb-0fbfe0f311d6@gmail.com> I've installed v1.25 nginx -V nginx version: nginx/1.25.0 (Local Build) built with OpenSSL 3.0.8 7 Feb 2023 TLS SNI support enabled configure arguments: --with-debug ... --with-http_v2_module --with-http_v3_module ... First tries on my existing site that frontends php-fpm, uses ssl cache, etc config-checks with NO errors, and execs with NO errors. But, I see no http3 protocol responses -- everything's still http2. Testing with Firefox at cloudflare, client-side QUIC support is fine. So I'm fairly certain it's my config. I'll reduce to simplest config, and track down what the issue is. Are there (yet) any documented examples for release nginx + QUIC in a php-fpm/fastcgi setup? From mdounin at mdounin.ru Fri May 26 16:46:37 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 26 May 2023 19:46:37 +0300 Subject: [PATCH 1 of 1] Tests: unbreak reading new stderr data after eof In-Reply-To: References: Message-ID: Hello! On Fri, May 26, 2023 at 03:24:12PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1685100180 -14400 > # Fri May 26 15:23:00 2023 +0400 > # Node ID dc3539be2a14a0cd0b8be9e1e9aae8c95b806828 > # Parent 4dad7cf8ebe807f9230a82dde5fc02b5e12e390b > Tests: unbreak reading new stderr data after eof. > > Tests don't expect to stop reading redirected stderr when end of file is > reached, but rather to read new data being appended, similar to "tail -f". > The behaviour is first changed in Ubuntu 23.04 Perl 5.36, by applying the > upstream patch [1], and is expected for inclusion in the next Perl 5.38. > The fix is to set filehandle's position to the end of file to clear the > error state and allow to continue readline() further from that position. > > [1] https://github.com/Perl/perl5/commit/80c1f1e45e8e > > Updated mail_error_log.t and stream_error_log.t for consistency. > > diff --git a/error_log.t b/error_log.t > --- a/error_log.t > +++ b/error_log.t > @@ -183,7 +183,9 @@ sub lines { > my ($t, $file, $pattern) = @_; > > if ($file eq 'stderr') { > - return map { $_ =~ /\Q$pattern\E/ } (<$stderr>); > + my $value = map { $_ =~ /\Q$pattern\E/ } (<$stderr>); > + seek $stderr, 0, Fcntl::SEEK_END; > + return $value; > } > > my $path = $t->testdir() . '/' . $file; The SEEK_END here would introduce a race: if some data are added after reading, SEEK_END will silently skip it. While probably not important here, it might be a good idea to avoid introducing unneeded races when possible. In particular, SEEK_SET might be a better option (untested). Upcoming perldelta recommends $handle->clearerr() instead, which indeed seems to be a more explicit option (untested too). https://github.com/Perl/perl5/blob/8db19a86dfa8408a91845da20ea7063f7646a913/pod/perldelta.pod#readline-no-longer-clears-the-stream-error-and-eof-flags -- Maxim Dounin http://mdounin.ru/ From osa at freebsd.org.ru Fri May 26 19:06:11 2023 From: osa at freebsd.org.ru (Sergey A. Osokin) Date: Fri, 26 May 2023 22:06:11 +0300 Subject: QUIC docs with php-fpm & ssl cache In-Reply-To: <03deea54-560b-295e-42fb-0fbfe0f311d6@gmail.com> References: <03deea54-560b-295e-42fb-0fbfe0f311d6@gmail.com> Message-ID: Hi there, On Fri, May 26, 2023 at 12:19:20PM -0400, PGNet Dev wrote: > I've installed v1.25 [...] > Are there (yet) any documented examples for release nginx + QUIC in a php-fpm/fastcgi setup? This mailing list is about nginx development. A user questions need to be addressed to the nginx@ mailing list. Thank you. -- Sergey A. Osokin From xeioex at nginx.com Sat May 27 02:15:54 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 02:15:54 +0000 Subject: [njs] HTTP: throwing an exception in r.internalRedirect() while filtering. Message-ID: details: https://hg.nginx.org/njs/rev/05c7f0b31856 branches: changeset: 2135:05c7f0b31856 user: Dmitry Volyntsev date: Fri May 26 19:13:39 2023 -0700 description: HTTP: throwing an exception in r.internalRedirect() while filtering. A user is notified explicitly that r.internalRedirect() is not supported in filters. diffstat: nginx/ngx_http_js_module.c | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diffs (23 lines): diff -r 398f4de34fe7 -r 05c7f0b31856 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Wed May 24 22:04:38 2023 -0700 +++ b/nginx/ngx_http_js_module.c Fri May 26 19:13:39 2023 -0700 @@ -950,6 +950,7 @@ ngx_http_js_header_filter(ngx_http_reque ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + ctx->filter = 1; pending = njs_vm_pending(ctx->vm); rc = ngx_js_call(ctx->vm, &jlcf->header_filter, r->connection->log, @@ -2433,6 +2434,11 @@ ngx_http_js_ext_internal_redirect(njs_vm ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + if (ctx->filter) { + njs_vm_error(vm, "internalRedirect cannot be called while filtering"); + return NJS_ERROR; + } + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &uri) != NGX_OK) { njs_vm_error(vm, "failed to convert uri arg"); return NJS_ERROR; From xeioex at nginx.com Sat May 27 02:15:56 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 02:15:56 +0000 Subject: [njs] Introduced Array.prototype.toSpliced(). Message-ID: details: https://hg.nginx.org/njs/rev/76a8034b15e1 branches: changeset: 2136:76a8034b15e1 user: Dmitry Volyntsev date: Fri May 26 19:13:41 2023 -0700 description: Introduced Array.prototype.toSpliced(). diffstat: src/njs_array.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++ src/test/njs_unit_test.c | 3 + 2 files changed, 104 insertions(+), 0 deletions(-) diffs (131 lines): diff -r 05c7f0b31856 -r 76a8034b15e1 src/njs_array.c --- a/src/njs_array.c Fri May 26 19:13:39 2023 -0700 +++ b/src/njs_array.c Fri May 26 19:13:41 2023 -0700 @@ -1403,6 +1403,105 @@ njs_array_prototype_splice(njs_vm_t *vm, static njs_int_t +njs_array_prototype_to_spliced(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) +{ + int64_t i, n, r, start, length, to_insert, to_skip, new_length; + njs_int_t ret; + njs_value_t *this, value; + njs_array_t *array; + + this = njs_argument(args, 0); + + ret = njs_value_to_object(vm, this); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_object_length(vm, this, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length); + + to_insert = 0; + to_skip = 0; + + if (nargs == 2) { + to_skip = length - start; + + } else if (nargs > 2) { + to_insert = nargs - 3; + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &to_skip); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + to_skip = njs_min(njs_max(to_skip, 0), length - start); + } + + new_length = length + to_insert - to_skip; + + if (njs_slow_path(new_length > NJS_MAX_LENGTH)) { + njs_type_error(vm, "Invalid length"); + return NJS_ERROR; + } + + array = njs_array_alloc(vm, 0, new_length, 0); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + njs_set_array(retval, array); + + for (i = 0; i < start; i++) { + ret = njs_value_property_i64(vm, this, i, &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + ret = njs_value_create_data_prop_i64(vm, retval, i, &value, 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + for (n = 3; to_insert-- > 0; i++, n++) { + ret = njs_value_create_data_prop_i64(vm, retval, i, &args[n], 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + r = start + to_skip; + + while (i < new_length) { + ret = njs_value_property_i64(vm, this, r, &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + ret = njs_value_create_data_prop_i64(vm, retval, i, &value, 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + r++; + i++; + } + + return NJS_OK; +} + + +static njs_int_t njs_array_prototype_reverse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { @@ -3092,6 +3191,8 @@ static const njs_object_prop_t njs_arra NJS_DECLARE_PROP_NATIVE("toSorted", njs_array_prototype_to_sorted, 1, 0), + NJS_DECLARE_PROP_NATIVE("toSpliced", njs_array_prototype_to_spliced, 2, 0), + NJS_DECLARE_PROP_NATIVE("toString", njs_array_prototype_to_string, 0, 0), NJS_DECLARE_PROP_NATIVE("unshift", njs_array_prototype_unshift, 1, 0), diff -r 05c7f0b31856 -r 76a8034b15e1 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 26 19:13:39 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 26 19:13:41 2023 -0700 @@ -5159,6 +5159,9 @@ static njs_unit_test_t njs_test[] = "a.splice(0)"), njs_str(",,") }, + { njs_str("'/A/B/C/D/'.split('/').toSpliced(1,1).join('/')"), + njs_str("/B/C/D/") }, + { njs_str("var a = []; a.reverse()"), njs_str("") }, From xeioex at nginx.com Sat May 27 02:15:58 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 02:15:58 +0000 Subject: [njs] Introduced Array.prototype.toReversed(). Message-ID: details: https://hg.nginx.org/njs/rev/e74e56120102 branches: changeset: 2137:e74e56120102 user: Dmitry Volyntsev date: Fri May 26 19:13:41 2023 -0700 description: Introduced Array.prototype.toReversed(). diffstat: src/njs_array.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/test/njs_unit_test.c | 3 +++ 2 files changed, 49 insertions(+), 0 deletions(-) diffs (76 lines): diff -r 76a8034b15e1 -r e74e56120102 src/njs_array.c --- a/src/njs_array.c Fri May 26 19:13:41 2023 -0700 +++ b/src/njs_array.c Fri May 26 19:13:41 2023 -0700 @@ -1575,6 +1575,50 @@ njs_array_prototype_reverse(njs_vm_t *vm } +static njs_int_t +njs_array_prototype_to_reversed(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, value; + + this = njs_argument(args, 0); + + ret = njs_value_to_object(vm, this); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_value_length(vm, this, &length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + array = njs_array_alloc(vm, 0, length, 0); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + njs_set_array(retval, array); + + for (i = 0; i < length; i++) { + ret = njs_value_property_i64(vm, this, length - i - 1, &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + ret = njs_value_create_data_prop_i64(vm, retval, i, &value, 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + return NJS_OK; +} + + njs_int_t njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) @@ -3189,6 +3233,8 @@ static const njs_object_prop_t njs_arra NJS_DECLARE_PROP_NATIVE("splice", njs_array_prototype_splice, 2, 0), + NJS_DECLARE_PROP_NATIVE("toReversed", njs_array_prototype_to_reversed, 0, 0), + NJS_DECLARE_PROP_NATIVE("toSorted", njs_array_prototype_to_sorted, 1, 0), NJS_DECLARE_PROP_NATIVE("toSpliced", njs_array_prototype_to_spliced, 2, 0), diff -r 76a8034b15e1 -r e74e56120102 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 26 19:13:41 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 26 19:13:41 2023 -0700 @@ -5218,6 +5218,9 @@ static njs_unit_test_t njs_test[] = { njs_str("Array.prototype[0] = 0; var x = [,1]; x.reverse(); x"), njs_str("1,0") }, + { njs_str("var a = [,3,2,1]; njs.dump([a.toReversed(),a])"), + njs_str("[[1,2,3,undefined],[,3,2,1]]") }, + { njs_str("var a = [1,2,3,4]; a.indexOf()"), njs_str("-1") }, From v.zhestikov at f5.com Sat May 27 02:45:30 2023 From: v.zhestikov at f5.com (Vadim Zhestikov) Date: Sat, 27 May 2023 02:45:30 +0000 Subject: [njs] Fixed parsing of for-in loops. Message-ID: details: https://hg.nginx.org/njs/rev/b9d18d4dd34e branches: changeset: 2138:b9d18d4dd34e user: Vadim Zhestikov date: Fri May 26 19:43:24 2023 -0700 description: Fixed parsing of for-in loops. This fixes parsing for input like: for (a(b * in d) ; The issue was introduced in 283ae119d121 (0.7.9). diffstat: src/njs_parser.c | 35 ++++++++++++++++++++++++++--------- src/test/njs_unit_test.c | 6 ++++++ 2 files changed, 32 insertions(+), 9 deletions(-) diffs (92 lines): diff -r e74e56120102 -r b9d18d4dd34e src/njs_parser.c --- a/src/njs_parser.c Fri May 26 19:13:41 2023 -0700 +++ b/src/njs_parser.c Fri May 26 19:43:24 2023 -0700 @@ -294,7 +294,10 @@ static njs_int_t njs_parser_while_after( static njs_int_t njs_parser_iteration_statement_for(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); -static njs_int_t njs_parser_for_left_hand_side_expression_map( +static njs_int_t njs_parser_for_expression_map_continue( + njs_parser_t *parser, njs_lexer_token_t *token, + njs_queue_link_t *current); +static njs_int_t njs_parser_for_expression_map_reparse( njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_expression_continue_op(njs_parser_t *parser, @@ -5488,12 +5491,10 @@ njs_parser_iteration_statement_for(njs_p static njs_int_t -njs_parser_for_left_hand_side_expression_map(njs_parser_t *parser, +njs_parser_for_expression_map_reparse(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_int_t operation; - njs_str_t *text; - njs_parser_node_t *node; + njs_str_t *text; if (parser->node == NULL) { njs_lexer_in_fail_set(parser->lexer, 1); @@ -5514,8 +5515,19 @@ njs_parser_for_left_hand_side_expression return njs_parser_after(parser, current, text, 1, njs_parser_for_var_in_of_expression); - - } + } + + return njs_parser_stack_pop(parser); +} + + +static njs_int_t +njs_parser_for_expression_map_continue(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current) +{ + njs_int_t operation; + njs_str_t *text; + njs_parser_node_t *node; if (token->type != NJS_TOKEN_IN) { njs_lexer_in_fail_set(parser->lexer, 1); @@ -5575,7 +5587,6 @@ njs_parser_for_left_hand_side_expression return njs_parser_after(parser, current, node, 0, njs_parser_for_in_statement_statement); } - } @@ -5766,8 +5777,14 @@ njs_parser_iteration_statement_for_map(n *text = token->text; + ret = njs_parser_after(parser, current, text, 1, + njs_parser_for_expression_map_continue); + if (ret != NJS_OK) { + return NJS_ERROR; + } + return njs_parser_after(parser, current, text, 0, - njs_parser_for_left_hand_side_expression_map); + njs_parser_for_expression_map_reparse); } expression_after: diff -r e74e56120102 -r b9d18d4dd34e src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 26 19:13:41 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 26 19:43:24 2023 -0700 @@ -2963,6 +2963,12 @@ static njs_unit_test_t njs_test[] = { njs_str("for(A?{,"), njs_str("SyntaxError: Unexpected token \",\" in 1") }, + { njs_str("for(Symbol(A=>A+ in 'A') P/$"), + njs_str("SyntaxError: Unexpected token \"in\" in 1") }, + + { njs_str("for (a(b * in d) ;"), + njs_str("SyntaxError: Unexpected token \"in\" in 1") }, + /* switch. */ { njs_str("switch"), From xeioex at nginx.com Sat May 27 17:06:13 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 17:06:13 +0000 Subject: [njs] Removed dead store introduced in 398f4de34fe7. Message-ID: details: https://hg.nginx.org/njs/rev/84eadc155d46 branches: changeset: 2139:84eadc155d46 user: Dmitry Volyntsev date: Fri May 26 20:58:19 2023 -0700 description: Removed dead store introduced in 398f4de34fe7. Found by Coverity (CID 1530432). diffstat: src/njs_array.c | 11 +++-------- 1 files changed, 3 insertions(+), 8 deletions(-) diffs (21 lines): diff -r b9d18d4dd34e -r 84eadc155d46 src/njs_array.c --- a/src/njs_array.c Fri May 26 19:43:24 2023 -0700 +++ b/src/njs_array.c Fri May 26 20:58:19 2023 -0700 @@ -2860,14 +2860,9 @@ njs_sort_indexed_properties(njs_vm_t *vm goto exception; } - if (slots != NULL) { - p = (void *) njs_cpymem(newslots, slots, - sizeof(njs_array_sort_slot_t) * (p - slots)); - njs_mp_free(vm->mem_pool, slots); - - } else { - p = newslots; - } + p = (void *) njs_cpymem(newslots, slots, + sizeof(njs_array_sort_slot_t) * (p - slots)); + njs_mp_free(vm->mem_pool, slots); slots = newslots; end = slots + nlen; From xeioex at nginx.com Sat May 27 17:06:14 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 17:06:14 +0000 Subject: [njs] Added %TypedArray%.prototype.toSorted(). Message-ID: details: https://hg.nginx.org/njs/rev/fa60c896acaa branches: changeset: 2140:fa60c896acaa user: Dmitry Volyntsev date: Fri May 26 20:58:22 2023 -0700 description: Added %TypedArray%.prototype.toSorted(). diffstat: src/njs_typed_array.c | 20 +++++++++++++++++--- src/test/njs_unit_test.c | 11 +++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diffs (69 lines): diff -r 84eadc155d46 -r fa60c896acaa src/njs_typed_array.c --- a/src/njs_typed_array.c Fri May 26 20:58:19 2023 -0700 +++ b/src/njs_typed_array.c Fri May 26 20:58:22 2023 -0700 @@ -1897,13 +1897,13 @@ exception: static njs_int_t njs_typed_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) + njs_uint_t nargs, njs_index_t to_sorted, njs_value_t *retval) { u_char *base, *orig; int64_t length; uint32_t element_size; - njs_value_t *this, *comparefn; - njs_typed_array_t *array; + njs_value_t *this, *comparefn, arguments[1]; + njs_typed_array_t *array, *self; njs_array_buffer_t *buffer; njs_typed_array_cmp_t cmp; njs_typed_array_sort_ctx_t ctx; @@ -1920,6 +1920,18 @@ njs_typed_array_prototype_sort(njs_vm_t return NJS_ERROR; } + if (to_sorted) { + self = array; + njs_set_number(&arguments[0], njs_typed_array_length(self)); + array = njs_typed_array_alloc(vm, arguments, 1, 0, self->type); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + memcpy(&array->buffer->u.u8[0], &self->buffer->u.u8[0], + self->byte_length); + } + ctx.vm = vm; ctx.buffer = array->buffer; ctx.exception = 0; @@ -2284,6 +2296,8 @@ static const njs_object_prop_t njs_type NJS_DECLARE_PROP_NATIVE("subarray", njs_typed_array_prototype_slice, 2, 0), + NJS_DECLARE_PROP_NATIVE("toSorted", njs_typed_array_prototype_sort, 1, 1), + NJS_DECLARE_PROP_NATIVE("toString", njs_array_prototype_to_string, 0, 0), NJS_DECLARE_PROP_NATIVE("values", njs_typed_array_prototype_iterator_obj, diff -r 84eadc155d46 -r fa60c896acaa src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 26 20:58:19 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 26 20:58:22 2023 -0700 @@ -6751,6 +6751,17 @@ static njs_unit_test_t njs_test[] = { njs_str("(new Float64Array([255,255,NaN,3,NaN,Infinity,3,-Infinity,0,-0,2,1,-5])).slice(2).sort()"), njs_str("-Infinity,-5,0,0,1,2,3,3,Infinity,NaN,NaN") }, + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([3,2,1]);" + " return [a.toSorted(),a].toString() === '1,2,3,3,2,1'})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = (new v([3,2,1]));" + " a.constructor = (v == Uint8Array) ? Uint32Array : Uint8Array;" + " return Object.getPrototypeOf(a.toSorted()) === v.prototype})"), + njs_str("true") }, + { njs_str("(new DataView(new ArrayBuffer(3)))"), njs_str("[object DataView]") }, From xeioex at nginx.com Sat May 27 17:06:16 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 17:06:16 +0000 Subject: [njs] Added %TypedArray%.prototype.toReversed(). Message-ID: details: https://hg.nginx.org/njs/rev/9213a609cb17 branches: changeset: 2141:9213a609cb17 user: Dmitry Volyntsev date: Fri May 26 21:05:15 2023 -0700 description: Added %TypedArray%.prototype.toReversed(). diffstat: src/njs_typed_array.c | 27 +++++++++++++++++++++++---- src/test/njs_unit_test.c | 5 +++++ 2 files changed, 28 insertions(+), 4 deletions(-) diffs (73 lines): diff -r fa60c896acaa -r 9213a609cb17 src/njs_typed_array.c --- a/src/njs_typed_array.c Fri May 26 20:58:22 2023 -0700 +++ b/src/njs_typed_array.c Fri May 26 21:05:15 2023 -0700 @@ -1609,15 +1609,15 @@ njs_typed_array_prototype_reduce(njs_vm_ static njs_int_t njs_typed_array_prototype_reverse(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) + njs_uint_t nargs, njs_index_t to_reversed, njs_value_t *retval) { double *f64; uint8_t *u8; int64_t i, length; uint16_t *u16; uint32_t *u32; - njs_value_t *this; - njs_typed_array_t *array; + njs_value_t *this, arguments[1]; + njs_typed_array_t *array, *self; njs_array_buffer_t *buffer; this = njs_argument(args, 0); @@ -1627,8 +1627,24 @@ njs_typed_array_prototype_reverse(njs_vm } array = njs_typed_array(this); + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + + if (to_reversed) { + self = array; + njs_set_number(&arguments[0], njs_typed_array_length(self)); + array = njs_typed_array_alloc(vm, arguments, 1, 0, self->type); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + memcpy(&array->buffer->u.u8[0], &self->buffer->u.u8[0], + self->byte_length); + } + length = njs_typed_array_length(array); - buffer = njs_typed_array_writable(vm, array); if (njs_slow_path(buffer == NULL)) { return NJS_ERROR; @@ -2296,6 +2312,9 @@ static const njs_object_prop_t njs_type NJS_DECLARE_PROP_NATIVE("subarray", njs_typed_array_prototype_slice, 2, 0), + NJS_DECLARE_PROP_NATIVE("toReversed", njs_typed_array_prototype_reverse, 0, + 1), + NJS_DECLARE_PROP_NATIVE("toSorted", njs_typed_array_prototype_sort, 1, 1), NJS_DECLARE_PROP_NATIVE("toString", njs_array_prototype_to_string, 0, 0), diff -r fa60c896acaa -r 9213a609cb17 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 26 20:58:22 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 26 21:05:15 2023 -0700 @@ -6708,6 +6708,11 @@ static njs_unit_test_t njs_test[] = ".every(v=>{ return (new v([1,2,3,4])).reverse().join('|') == '4|3|2|1'})"), njs_str("true") }, + { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{var a = new v([3,2,1]);" + " return [a.toReversed(), a].toString() === '1,2,3,3,2,1'})"), + njs_str("true") }, + { njs_str("Uint8Array.prototype.sort.call(1)"), njs_str("TypeError: this is not a typed array") }, From xeioex at nginx.com Sat May 27 17:06:18 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 27 May 2023 17:06:18 +0000 Subject: [njs] Parser: improved error message for import statement. Message-ID: details: https://hg.nginx.org/njs/rev/2e8563c8143b branches: changeset: 2142:2e8563c8143b user: Dmitry Volyntsev date: Fri May 26 21:54:12 2023 -0700 description: Parser: improved error message for import statement. This closes #642 issue on Github. diffstat: src/njs_parser.c | 9 ++++++++- src/test/njs_unit_test.c | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diffs (46 lines): diff -r 9213a609cb17 -r 2e8563c8143b src/njs_parser.c --- a/src/njs_parser.c Fri May 26 21:05:15 2023 -0700 +++ b/src/njs_parser.c Fri May 26 21:54:12 2023 -0700 @@ -8118,11 +8118,18 @@ njs_parser_import(njs_parser_t *parser, return NJS_DONE; } - if (token->type != NJS_TOKEN_NAME) { + if (token->type == NJS_TOKEN_MULTIPLICATION + || token->type == NJS_TOKEN_OPEN_BRACE + || token->type == NJS_TOKEN_STRING) + { njs_parser_syntax_error(parser, "Non-default import is not supported"); return NJS_DONE; } + if (token->type != NJS_TOKEN_NAME) { + return njs_parser_failed(parser); + } + name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_LET, &var); if (name == NULL) { diff -r 9213a609cb17 -r 2e8563c8143b src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri May 26 21:05:15 2023 -0700 +++ b/src/test/njs_unit_test.c Fri May 26 21:54:12 2023 -0700 @@ -18903,12 +18903,18 @@ static njs_unit_test_t njs_test[] = /* Module. */ - { njs_str("import;"), + { njs_str("import * from y"), + njs_str("SyntaxError: Non-default import is not supported in 1") }, + + { njs_str("import 'x' from y"), njs_str("SyntaxError: Non-default import is not supported in 1") }, { njs_str("import {x} from y"), njs_str("SyntaxError: Non-default import is not supported in 1") }, + { njs_str("import switch from y"), + njs_str("SyntaxError: Unexpected token \"switch\" in 1") }, + { njs_str("import x from y"), njs_str("SyntaxError: Unexpected token \"y\" in 1") }, From jordanc.carter at outlook.com Sun May 28 04:28:37 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 28 May 2023 05:28:37 +0100 Subject: [PATCH] Added $realip_add_x_forwarded_for In-Reply-To: References: Message-ID: Hello, Thanks for the reply. On Mon, 22 May 2023 15:48:23 +0300 Maxim Dounin wrote: > Hello! > > On Sun, May 14, 2023 at 04:51:58AM +0100, J Carter wrote: > > > # 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 > > x; proxy_set_header Remote $remote_addr; > > proxy_pass http://127.0.0.1:8080; > > } > > } > > Thanks for the patch, but I can't say I like the idea of > introducing yet another variable and asking users to change it > manually. Good point, it should be possible to merge the two. > This is essentially equivalent to using > > proxy_set_header X-Forwarded-For "$http_x_forwarded_for, > $realip_remote_addr"; > > as the ticket suggests. > Well yes, but this proxy_set_header example would only be valid if $http_x_forwarded_for always exists as a request header, otherwise you'd have a hanging comma at the start. It'd need a map to handle that if it were sometimes present and sometimes not. I imagine avoiding such a map is the reason the regular proxy_add_x_forwarded_for directive also exists. > Also, it is an open question if $realip_remote_addr should be > used, or X-Forwarded-For should be left unmodified if remote addr > was set from X-Forwarded-For. Leaving it unmodified does seem undesirable, as it means omitting a proxy hop($realip_remote_addr) from the x-forwarded-for chain. > The realip module instructs nginx > to use the address as obtained from the header, and not using it > for some purposes looks at least questionable. I believe this would be the only valid exception to that, given that value of $realip_add_x_forwarded_for is only ever going to be to overwrite x-forwarded-for with proxy_set_header for the next hop proxy or backend app to utilize. It's quite contained. Also it does seem more sensible than the resulting x-forwarded-for value shown in the ticket, which would look like nonsense to any upstream consumer of that value that wishes to analyze the whole chain. The proxy_add_x_forwarded_for's value in the ticket isn't in the spirit of the header's purpose either, which is to preserve addresses of the client & chain of proxies. > > Also, it seems incorrect to use $realip_remote_addr (or keep > X-Forwarded-For unmodified) if remote addr was set from other > sources, such as PROXY protocol headers. > > Overall, current behaviour might actually be optimal. > > [...] > This is a good point, although perhaps adding both $remote_addr and $realip_remote_addr to the x-forwarded-for chain would be better behavior for the other sources (especially proxy_protocol). It'd need some additional checks to ensure no duplications are introduced (if the x-forwarded-for header already exists). From jordanc.carter at outlook.com Sun May 28 04:42:30 2023 From: jordanc.carter at outlook.com (J Carter) Date: Sun, 28 May 2023 05:42:30 +0100 Subject: [PATCH] Added $http2_stream_id In-Reply-To: References: Message-ID: Hello, On Mon, 22 May 2023 00:40:08 +0300 Maxim Dounin wrote: > Hello! > > On Sun, May 14, 2023 at 11:59:35PM +0100, J Carter wrote: > > > 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. > > In HTTP/2, for each stream nginx creates a new connection, and > r->connection->requests as seen by $connection_requests will be > frozen for the request lifetime. That is, it essentially shows > the request sequence number. I see.. I must've messed my tests of this up somehow - this explanation makes sense though. > > > I'd ask that this patch is reconsidered. > > While $connection_requests is certainly not exactly equivalent to > HTTP/2 stream id, the $connection_requests is believed to be > enough for user-level tasks, as well as for most debugging tasks. > Yes agreed considering the above, I can indeed just offset the value. Thanks again. From arut at nginx.com Sun May 28 07:20:56 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Sun, 28 May 2023 11:20:56 +0400 Subject: [PATCH] QUIC: fixed compat with ciphers other than AES128 (ticket #2500) Message-ID: <5b46de0dda837d97afbe.1685258456@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1685258227 -14400 # Sun May 28 11:17:07 2023 +0400 # Node ID 5b46de0dda837d97afbec7484931223c52d53905 # Parent 8eae1b4f1c5528b063351804168a6085f5f50b42 QUIC: fixed compat with ciphers other than AES128 (ticket #2500). Previously, rec.level field was not uninitialized in SSL_provide_quic_data(). As a result, its value was always ssl_encryption_initial. Later in ngx_quic_ciphers() such level resulted in resetting the cipher to TLS1_3_CK_AES_128_GCM_SHA256 and using AES128 to encrypt the packet. Now the level is initialized and the right cipher is used. diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c --- a/src/event/quic/ngx_event_quic_openssl_compat.c +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -463,6 +463,7 @@ SSL_provide_quic_data(SSL *ssl, enum ssl rec.log = c->log; rec.number = com->read_record++; rec.keys = &com->keys; + rec.level = level; if (level == ssl_encryption_initial) { n = ngx_min(len, 65535); From pluknet at nginx.com Mon May 29 09:55:47 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 29 May 2023 13:55:47 +0400 Subject: [PATCH] QUIC: fixed compat with ciphers other than AES128 (ticket #2500) In-Reply-To: <5b46de0dda837d97afbe.1685258456@arut-laptop> References: <5b46de0dda837d97afbe.1685258456@arut-laptop> Message-ID: <50186FCD-4593-4087-BEF8-04C7068734FA@nginx.com> > On 28 May 2023, at 11:20, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1685258227 -14400 > # Sun May 28 11:17:07 2023 +0400 > # Node ID 5b46de0dda837d97afbec7484931223c52d53905 > # Parent 8eae1b4f1c5528b063351804168a6085f5f50b42 > QUIC: fixed compat with ciphers other than AES128 (ticket #2500). > > Previously, rec.level field was not uninitialized in SSL_provide_quic_data(). > As a result, its value was always ssl_encryption_initial. Later in > ngx_quic_ciphers() such level resulted in resetting the cipher to > TLS1_3_CK_AES_128_GCM_SHA256 and using AES128 to encrypt the packet. > > Now the level is initialized and the right cipher is used. > > diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c > --- a/src/event/quic/ngx_event_quic_openssl_compat.c > +++ b/src/event/quic/ngx_event_quic_openssl_compat.c > @@ -463,6 +463,7 @@ SSL_provide_quic_data(SSL *ssl, enum ssl > rec.log = c->log; > rec.number = com->read_record++; > rec.keys = &com->keys; > + rec.level = level; > > if (level == ssl_encryption_initial) { > n = ngx_min(len, 65535); Looks good. -- Sergey Kandaurov From arut at nginx.com Mon May 29 11:13:15 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 29 May 2023 11:13:15 +0000 Subject: [nginx] Version bump. Message-ID: details: https://hg.nginx.org/nginx/rev/6c75d2484267 branches: changeset: 9117:6c75d2484267 user: Roman Arutyunyan date: Mon May 29 15:03:31 2023 +0400 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r 8eae1b4f1c55 -r 6c75d2484267 src/core/nginx.h --- a/src/core/nginx.h Tue May 23 18:08:20 2023 +0300 +++ b/src/core/nginx.h Mon May 29 15:03:31 2023 +0400 @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1025000 -#define NGINX_VERSION "1.25.0" +#define nginx_version 1025001 +#define NGINX_VERSION "1.25.1" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From arut at nginx.com Mon May 29 11:13:18 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 29 May 2023 11:13:18 +0000 Subject: [nginx] QUIC: fixed compat with ciphers other than AES128 (ticket #2500). Message-ID: details: https://hg.nginx.org/nginx/rev/b4a57278bf24 branches: changeset: 9118:b4a57278bf24 user: Roman Arutyunyan date: Sun May 28 11:17:07 2023 +0400 description: QUIC: fixed compat with ciphers other than AES128 (ticket #2500). Previously, rec.level field was not uninitialized in SSL_provide_quic_data(). As a result, its value was always ssl_encryption_initial. Later in ngx_quic_ciphers() such level resulted in resetting the cipher to TLS1_3_CK_AES_128_GCM_SHA256 and using AES128 to encrypt the packet. Now the level is initialized and the right cipher is used. diffstat: src/event/quic/ngx_event_quic_openssl_compat.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (11 lines): diff -r 6c75d2484267 -r b4a57278bf24 src/event/quic/ngx_event_quic_openssl_compat.c --- a/src/event/quic/ngx_event_quic_openssl_compat.c Mon May 29 15:03:31 2023 +0400 +++ b/src/event/quic/ngx_event_quic_openssl_compat.c Sun May 28 11:17:07 2023 +0400 @@ -463,6 +463,7 @@ SSL_provide_quic_data(SSL *ssl, enum ssl rec.log = c->log; rec.number = com->read_record++; rec.keys = &com->keys; + rec.level = level; if (level == ssl_encryption_initial) { n = ngx_min(len, 65535); From pluknet at nginx.com Mon May 29 13:31:54 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 29 May 2023 17:31:54 +0400 Subject: [PATCH 1 of 1] Tests: unbreak reading new stderr data after eof In-Reply-To: References: Message-ID: <1E822585-7D8E-4F11-9140-97B2BC031A1C@nginx.com> > On 26 May 2023, at 20:46, Maxim Dounin wrote: > > Hello! > > On Fri, May 26, 2023 at 03:24:12PM +0400, Sergey Kandaurov wrote: > >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1685100180 -14400 >> # Fri May 26 15:23:00 2023 +0400 >> # Node ID dc3539be2a14a0cd0b8be9e1e9aae8c95b806828 >> # Parent 4dad7cf8ebe807f9230a82dde5fc02b5e12e390b >> Tests: unbreak reading new stderr data after eof. >> >> Tests don't expect to stop reading redirected stderr when end of file is >> reached, but rather to read new data being appended, similar to "tail -f". >> The behaviour is first changed in Ubuntu 23.04 Perl 5.36, by applying the >> upstream patch [1], and is expected for inclusion in the next Perl 5.38. >> The fix is to set filehandle's position to the end of file to clear the >> error state and allow to continue readline() further from that position. >> >> [1] https://github.com/Perl/perl5/commit/80c1f1e45e8e >> >> Updated mail_error_log.t and stream_error_log.t for consistency. >> >> diff --git a/error_log.t b/error_log.t >> --- a/error_log.t >> +++ b/error_log.t >> @@ -183,7 +183,9 @@ sub lines { >> my ($t, $file, $pattern) = @_; >> >> if ($file eq 'stderr') { >> - return map { $_ =~ /\Q$pattern\E/ } (<$stderr>); >> + my $value = map { $_ =~ /\Q$pattern\E/ } (<$stderr>); >> + seek $stderr, 0, Fcntl::SEEK_END; >> + return $value; >> } >> >> my $path = $t->testdir() . '/' . $file; > > The SEEK_END here would introduce a race: if some data are added > after reading, SEEK_END will silently skip it. While probably not > important here, it might be a good idea to avoid introducing > unneeded races when possible. In particular, SEEK_SET might be a > better option (untested). Sure, tnx for the point. For the record, SEEK_SET appears to avoid races. > > Upcoming perldelta recommends $handle->clearerr() instead, which > indeed seems to be a more explicit option (untested too). > > https://github.com/Perl/perl5/blob/8db19a86dfa8408a91845da20ea7063f7646a913/pod/perldelta.pod#readline-no-longer-clears-the-stream-error-and-eof-flags > As we don't need to change the position, clearerr() fits better. Pushed the updated change. -- Sergey Kandaurov From pluknet at nginx.com Tue May 30 13:54:03 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 30 May 2023 17:54:03 +0400 Subject: [PATCH 2 of 3] HTTP/2: "http2" directive In-Reply-To: <20230516123938.rgrmdpern3i5hq5n@N00W24XTQX> References: <735f9e501922e4b0a1b2.1675781431@arut-laptop> <20230209120234.rttyjviwir4sneed@N00W24XTQX> <20230209123359.i6fosl5r6tdhpvhv@N00W24XTQX> <71CADCEB-5BD1-4F67-AE85-581943918D5B@nginx.com> <20230516123938.rgrmdpern3i5hq5n@N00W24XTQX> Message-ID: <20230530135403.zhyvdjunqnxxq64o@Y9MQ9X2QVV> On Tue, May 16, 2023 at 04:39:38PM +0400, Roman Arutyunyan wrote: > Hi, > > # 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. You might also note that HTTP/2 detection by connection preface also affects the now deprecated "listen .. http2" configuration. Either way, looks good. From mat999 at gmail.com Tue May 30 15:26:35 2023 From: mat999 at gmail.com (Mathew Heard) Date: Wed, 31 May 2023 01:26:35 +1000 Subject: Thread Pool memory ownership Message-ID: Hi, I've been going through the threadpool code for native modules in an attempt to fix a third party module with what appears to be a use-after free error looking for inspiration. I thought I would see a strategy to prevent thread pool tasks that are in the queue for processing being freed when the request / connection their memory is allocated from is cleared but I'm not. For example there does not for example appear to be any protection against linux sendfile tasks from reading memory allocated from the ngx_connection_t if the connection is closed while the task is in the task queue. Is this correct? Is this a bug? Regards, Mathew From mdounin at mdounin.ru Wed May 31 02:15:24 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Wed, 31 May 2023 05:15:24 +0300 Subject: Thread Pool memory ownership In-Reply-To: References: Message-ID: Hello! On Wed, May 31, 2023 at 01:26:35AM +1000, Mathew Heard wrote: > I've been going through the threadpool code for native modules in an > attempt to fix a third party module with what appears to be a > use-after free error looking for inspiration. > > I thought I would see a strategy to prevent thread pool tasks that are > in the queue for processing being freed when the request / connection > their memory is allocated from is cleared but I'm not. > > For example there does not for example appear to be any protection > against linux sendfile tasks from reading memory allocated from the > ngx_connection_t if the connection is closed while the task is in the > task queue. > > Is this correct? Is this a bug? As long as there is a thread task or an AIO request scheduled, the request is expected to be blocked with r->blocked, so it won't be freed. For sendfile in threads, this is done by ngx_http_copy_thread_handler() (in src/http/ngx_http_copy_filter_module.c), which is called by ngx_linux_sendfile_thread() as file->file->thread_handler() when a sendfile task is queued. -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Wed May 31 02:50:20 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 31 May 2023 02:50:20 +0000 Subject: [njs] Random: prioritise CCRandomGenerateBytes over getentropy on macOs. Message-ID: details: https://hg.nginx.org/njs/rev/ab54ef24feac branches: changeset: 2143:ab54ef24feac user: David CARLIER date: Sun May 28 15:36:46 2023 +0100 description: Random: prioritise CCRandomGenerateBytes over getentropy on macOs. It is recommended approach by Apple itself. diffstat: auto/getrandom | 24 +++++++++++++++++++++++- src/njs_random.c | 13 +++++++++++++ 2 files changed, 36 insertions(+), 1 deletions(-) diffs (71 lines): diff -r 2e8563c8143b -r ab54ef24feac auto/getrandom --- a/auto/getrandom Fri May 26 21:54:12 2023 -0700 +++ b/auto/getrandom Sun May 28 15:36:46 2023 +0100 @@ -50,6 +50,28 @@ fi if [ $njs_found = no ]; then + # macOS 10.10. + + njs_feature="CCRandomGenerateBytes() in CommonCrypto/CommonRandom.h" + njs_feature_name=NJS_HAVE_CCRANDOMGENERATEBYTES + njs_feature_test="#include + #include + + int main(void) { + char buf[4]; + + if (CCRandomGenerateBytes(buf, 4) != kCCSuccess) { + return 1; + } + + return 0; + }" + . auto/feature +fi + + +if [ $njs_found = no ]; then + # OpenBSD 5.6 lacks . njs_feature="getentropy()" @@ -71,7 +93,7 @@ fi if [ $njs_found = no ]; then - # macOS 10.12. + # Solaris based systems. njs_feature="getentropy() in sys/random.h" njs_feature_name=NJS_HAVE_GETENTROPY_SYS_RANDOM diff -r 2e8563c8143b -r ab54ef24feac src/njs_random.c --- a/src/njs_random.c Fri May 26 21:54:12 2023 -0700 +++ b/src/njs_random.c Sun May 28 15:36:46 2023 +0100 @@ -8,6 +8,9 @@ #include #if (NJS_HAVE_GETRANDOM) #include +#elif (NJS_HAVE_CCRANDOMGENERATEBYTES) +#include +#include #elif (NJS_HAVE_LINUX_SYS_GETRANDOM) #include #include @@ -72,6 +75,16 @@ njs_random_stir(njs_random_t *r, njs_pid n = syscall(SYS_getrandom, &key, NJS_RANDOM_KEY_SIZE, 0); +#elif (NJS_HAVE_CCRANDOMGENERATEBYTES) + + /* Apple discourages the use of getentropy. */ + + n = 0; + + if (CCRandomGenerateBytes(&key, NJS_RANDOM_KEY_SIZE) == kCCSuccess) { + n = NJS_RANDOM_KEY_SIZE; + } + #elif (NJS_HAVE_GETENTROPY || NJS_HAVE_GETENTROPY_SYS_RANDOM) n = 0; From claireecf at gmail.com Wed May 31 20:28:19 2023 From: claireecf at gmail.com (claire liu) Date: Wed, 31 May 2023 15:28:19 -0500 Subject: QUIC connection migration Message-ID: Hi dev team, I am testing the QUIC connection migration feature. Whether I turn on or off 'quic_bpf', it supports connection migration. Are there any other configurations to control QUIC connection migration? Thanks in advance. Regards, Claire -------------- next part -------------- An HTML attachment was scrubbed... URL: From thresh at nginx.com Wed May 31 21:52:27 2023 From: thresh at nginx.com (=?iso-8859-1?q?Konstantin_Pavlov?=) Date: Wed, 31 May 2023 14:52:27 -0700 Subject: [PATCH] Linux packages: removed Ubuntu 18.04 'bionic' due to EOL Message-ID: <203f32f5373458548931.1685569947@QGCD7XG9R9> # HG changeset patch # User Konstantin Pavlov # Date 1685569850 25200 # Wed May 31 14:50:50 2023 -0700 # Node ID 203f32f5373458548931a706e728fdf94daa6a77 # Parent 7e7cd9f0cc41481fa6c1b3bc578af2da1cfbd866 Linux packages: removed Ubuntu 18.04 'bionic' due to EOL. diff -r 7e7cd9f0cc41 -r 203f32f53734 xml/en/linux_packages.xml --- a/xml/en/linux_packages.xml Wed May 10 13:51:55 2023 +0100 +++ b/xml/en/linux_packages.xml Wed May 31 14:50:50 2023 -0700 @@ -7,7 +7,7 @@
+ rev="87">
@@ -73,11 +73,6 @@ versions: -18.04 “bionic” -x86_64, aarch64/arm64 - - - 20.04 “focal” x86_64, aarch64/arm64, s390x diff -r 7e7cd9f0cc41 -r 203f32f53734 xml/ru/linux_packages.xml --- a/xml/ru/linux_packages.xml Wed May 10 13:51:55 2023 +0100 +++ b/xml/ru/linux_packages.xml Wed May 31 14:50:50 2023 -0700 @@ -7,7 +7,7 @@
+ rev="87">
@@ -73,11 +73,6 @@ -18.04 “bionic” -x86_64, aarch64/arm64 - - - 20.04 “focal” x86_64, aarch64/arm64, s390x From pluknet at nginx.com Wed May 31 22:04:54 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 1 Jun 2023 02:04:54 +0400 Subject: QUIC connection migration In-Reply-To: References: Message-ID: <3027FE25-D9BA-464A-8253-42F7FE5D680D@nginx.com> > On 1 Jun 2023, at 00:28, claire liu wrote: > > Hi dev team, > I am testing the QUIC connection migration feature. Whether I turn on or off 'quic_bpf', it supports connection migration. Are there any other configurations to control QUIC connection migration? Thanks in advance. > quic_bpf allows connection migration to work with multiple worker processes, it doesn't affect connection migration itself. In early development, there was a directive to disable migration. It was used to send a corresponding QUIC transport parameter "disable_active_migration" to signal it is not supported by server and also to drop incoming packets from an unknown path. Later, it was reconsidered to always allow connection migration. Specification is a bit vague on this point: although disabled, it still can be allowed to accept packets from new paths, see RFC 9000, section 9: : the endpoint MUST either drop the incoming packets on that path : without generating a Stateless Reset or proceed with path validation : and allow the peer to migrate. Later the directive was removed to simplify configuration, and also because connection migration is supported. Though, it might be used for fine-tuning configurations with multiple worker processes to disable migration where quic_bpf isn't available. -- Sergey Kandaurov From pluknet at nginx.com Wed May 31 22:11:12 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 1 Jun 2023 02:11:12 +0400 Subject: [PATCH] Linux packages: removed Ubuntu 18.04 'bionic' due to EOL In-Reply-To: <203f32f5373458548931.1685569947@QGCD7XG9R9> References: <203f32f5373458548931.1685569947@QGCD7XG9R9> Message-ID: > On 1 Jun 2023, at 01:52, Konstantin Pavlov wrote: > > # HG changeset patch > # User Konstantin Pavlov > # Date 1685569850 25200 > # Wed May 31 14:50:50 2023 -0700 > # Node ID 203f32f5373458548931a706e728fdf94daa6a77 > # Parent 7e7cd9f0cc41481fa6c1b3bc578af2da1cfbd866 > Linux packages: removed Ubuntu 18.04 'bionic' due to EOL. > Looks good. -- Sergey Kandaurov