From arut at nginx.com Fri Sep 1 09:52:25 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 1 Sep 2023 13:52:25 +0400 Subject: [PATCH] QUIC: removed use of SSL_quic_read_level and SSL_quic_write_level In-Reply-To: <015353ca1be7acc176f6.1693475484@enoparse.local> References: <015353ca1be7acc176f6.1693475484@enoparse.local> Message-ID: <20230901095225.v7ypnebp22du4v5r@N00W24XTQX> Hi, On Thu, Aug 31, 2023 at 01:51:24PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1693475261 -14400 > # Thu Aug 31 13:47:41 2023 +0400 > # Node ID 015353ca1be7acc176f6369ed92ec6c49975ee6a > # Parent 8f7e6d8c061e1f4b7656141d2fac85ce6846ac23 > QUIC: removed use of SSL_quic_read_level and SSL_quic_write_level. > > As explained in BoringSSL change[1], levels were introduced in the original > QUIC API to draw a line between when keys are released and when are active. > In the new QUIC API they are released in separate calls when it's needed. > BoringSSL has then a consideration to remove levels API, hence the change. > > In most places such as feeding SSL handshake with CRYPTO payload, we already > have such information from a QUIC packet header. If not, it is taken based > on keys availability. The only real use of levels is to prevent using app > keys before they are active in QuicTLS that provides old BoringSSL QUIC API, > it is replaced with an equivalent check of c->ssl->handshaked. > > This change also removes OpenSSL compat shims since they are no longer used. > The only exception left is keeping write level in the internal field which > is a handy equivalent of checking keys availability. > > [1] https://boringssl.googlesource.com/boringssl/+/1e859054 > > 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 > @@ -509,8 +509,17 @@ ngx_quic_close_connection(ngx_connection > * to terminate the connection immediately. > */ > > - qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) > - : ssl_encryption_initial; > + if (ngx_quic_keys_available(qc->keys, ssl_encryption_application)) { > + qc->error_level = ssl_encryption_application; > + > + } else if (ngx_quic_keys_available(qc->keys, > + ssl_encryption_handshake)) > + { > + qc->error_level = ssl_encryption_handshake; > + > + } else { > + qc->error_level = ssl_encryption_initial; > + } > > if (qc->error == (ngx_uint_t) -1) { > qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; > @@ -964,10 +973,7 @@ ngx_quic_handle_payload(ngx_connection_t > #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) > - { > + if (pkt->level == ssl_encryption_application && !c->ssl->handshaked) { > ngx_log_error(NGX_LOG_INFO, c->log, 0, > "quic no %s keys ready, ignoring packet", > ngx_quic_level_name(pkt->level)); > 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 > @@ -44,7 +44,6 @@ 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; > @@ -213,7 +212,6 @@ ngx_quic_compat_keylog_callback(const SS > > } 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, > @@ -583,32 +581,6 @@ ngx_quic_compat_create_record(ngx_quic_c > } > > > -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) > 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 > @@ -48,8 +48,6 @@ ngx_int_t ngx_quic_compat_init(ngx_conf_ > 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, > 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 > @@ -43,7 +43,8 @@ static int ngx_quic_add_handshake_data(n > 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); > +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, > + enum ssl_encryption_level_t level); > > > #if (NGX_QUIC_BORINGSSL_API) > @@ -354,7 +355,7 @@ ngx_quic_handle_crypto_frame(ngx_connect > } > > if (f->offset == ctx->crypto.offset) { > - if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { > + if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { > return NGX_ERROR; > } > > @@ -372,7 +373,7 @@ ngx_quic_handle_crypto_frame(ngx_connect > cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); > > if (cl) { > - if (ngx_quic_crypto_input(c, cl) != NGX_OK) { > + if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { > return NGX_ERROR; > } > > @@ -384,7 +385,8 @@ ngx_quic_handle_crypto_frame(ngx_connect > > > static ngx_int_t > -ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) > +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, > + enum ssl_encryption_level_t level) > { > int n, sslerr; > ngx_buf_t *b; > @@ -397,15 +399,10 @@ ngx_quic_crypto_input(ngx_connection_t * > > 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), > + if (!SSL_provide_quic_data(ssl_conn, level, > b->pos, b->last - b->pos)) This can fit in one line now. > { > ngx_ssl_error(NGX_LOG_INFO, c->log, 0, > @@ -416,11 +413,6 @@ ngx_quic_crypto_input(ngx_connection_t * > > 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) { > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks ok From arut at nginx.com Fri Sep 1 09:55:56 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 1 Sep 2023 13:55:56 +0400 Subject: [PATCH] QUIC: refined sending CONNECTION_CLOSE in various packet types In-Reply-To: <358c657a4a7afef502a0.1693493986@enoparse.local> References: <358c657a4a7afef502a0.1693493986@enoparse.local> Message-ID: <20230901095556.bfuvz4zgs663zhr5@N00W24XTQX> Hi, On Thu, Aug 31, 2023 at 06:59:46PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1693493736 -14400 > # Thu Aug 31 18:55:36 2023 +0400 > # Node ID 358c657a4a7afef502a00b9a41bddbe08f6859ae > # Parent 015353ca1be7acc176f6369ed92ec6c49975ee6a > QUIC: refined sending CONNECTION_CLOSE in various packet types. > > As per RFC 9000, section 10.2.3, to ensure that peer successfully removed > packet protection, CONNECTION_CLOSE can be sent in multiple packets using > different packet protection levels. > > Specifically, new logic is added to more strictly follow these rules: > > - by default, the highest available level of packet protection is used; > - unless handshake is confirmed, but we have got application keys available, > that means the client may have or may have not application keys to remove may *not* have ? > 1-RTT packet protection; in such case, send both 1-RTT and HS packets; > - additionally, if we still have initial protection keys not yet discarded, > which happens if the path was not yet validated by successfully removing > Handshake packet protection, that means the client may not have handshake > keys; in such case, send an Initial packet too. > > This roughly resembles the following paragraph: > > * Prior to confirming the handshake, a peer might be unable to process 1-RTT > packets, so an endpoint SHOULD send a CONNECTION_CLOSE frame in both Handshake > and 1-RTT packets. A server SHOULD also send a CONNECTION_CLOSE frame in an > Initial packet. > > In practice, this change allows to avoid sending Initial packet when we know > the client has handshake keys, and fixes sending CONNECTION_CLOSE when using > QuicTLS with old QUIC API, where TLS stack releases application read keys > before handshake confirmation, sending it additionally in a Handshake packet. > > 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 > @@ -540,8 +540,20 @@ ngx_quic_close_connection(ngx_connection > > (void) ngx_quic_send_cc(c); > > - if (qc->error_level == ssl_encryption_handshake) { > - /* for clients that might not have handshake keys */ > + if (qc->error_level == ssl_encryption_application > + && ngx_quic_keys_available(qc->keys, > + ssl_encryption_handshake)) > + { > + /* handshake not confirmed, client may not have app keys */ > + qc->error_level = ssl_encryption_handshake; > + (void) ngx_quic_send_cc(c); > + } > + > + if (qc->error_level == ssl_encryption_handshake > + && ngx_quic_keys_available(qc->keys, > + ssl_encryption_initial)) > + { > + /* path not validated, client may not have hs keys */ > qc->error_level = ssl_encryption_initial; > (void) ngx_quic_send_cc(c); > } I have a feeling that we can just send CC for all levels for which keys are available: for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ctx = &qc->send_ctx[i]; if (!ngx_quic_keys_available(qc->keys, ctx->level)) { continue; } qc->error_level = ctx->level; (void) ngx_quic_send_cc(c); if (rc == NGX_OK && !qc->close.timer_set) { ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); } } -- Roman Arutyunyan From ry.davis at f5.com Fri Sep 1 13:00:32 2023 From: ry.davis at f5.com (Ryan Davis) Date: Fri, 1 Sep 2023 13:00:32 +0000 Subject: [PATCH] Minor docs fixes In-Reply-To: References: <806e7402d63563de6e45.1693512078@C02CQ2ZCMD6R> Message-ID: Thanks for the feedback Yaroslav! Updated patch is attached with the suggested commit log, whitespace, and `rev` changes. Thanks, [signature_3506516580] Ryan Davis | Sr Software Engineer From: nginx-devel on behalf of Yaroslav Zhuravlev Date: Thursday, August 31, 2023 at 5:26 PM To: nginx-devel at nginx.org Subject: Re: [PATCH] Minor docs fixes EXTERNAL MAIL: nginx-devel-bounces at nginx.org Hi, > On 31 Aug 2023, at 21:43, Sergey A. Osokin wrote: > > Hi, > > here's the patch from Ryan. > > On Thu, Aug 31, 2023 at 08:31:34PM +0000, Ryan Davis via nginx-devel wrote: >> Apologies, the corporate SMTP server is doing something unexpected. > > # HG changeset patch > # User Ryan Davis > # Date 1693509279 14400 > # Thu Aug 31 15:14:39 2023 -0400 > # Node ID 31eebaba6f8d4973c35c40f3da69c9d9fe598438 > # Parent 4e25281328fa2152cadedc52e05f8a1b1bf531cd > Minor docs fixes Perhaps a more descriptive commit log would be better, e.g. "Fixed lang attribute, added module closing tag." > > - add a missing close tag that tripped up non-streaming XML parsers > - fix a misclassified `lang` attribute > > diff -r 4e25281328fa -r 31eebaba6f8d xml/en/docs/http/ngx_http_api_module_head.xml > --- a/xml/en/docs/http/ngx_http_api_module_head.xml Tue Aug 29 09:11:57 2023 +0100 > +++ b/xml/en/docs/http/ngx_http_api_module_head.xml Thu Aug 31 15:14:39 2023 -0400 > @@ -9,7 +9,7 @@ > link="/en/docs/http/ngx_http_api_module.html" > lang="en" > - rev="3"> > + rev="4"> > >
> > @@ -298,3 +298,4 @@ > > >
Blank line missing between section and module tags. > +
> diff -r 4e25281328fa -r 31eebaba6f8d xml/ru/docs/http/ngx_http_v3_module.xml > --- a/xml/ru/docs/http/ngx_http_v3_module.xml Tue Aug 29 09:11:57 2023 +0100 > +++ b/xml/ru/docs/http/ngx_http_v3_module.xml Thu Aug 31 15:14:39 2023 -0400 > @@ -8,8 +8,8 @@ > > link="/ru/docs/http/ngx_http_v3_module.html" > - lang="en" > - rev="2"> > + lang="ru" > + rev="3"> Version bump should also be applied to the En version as well as they correspond to each other. Or it might be better not to bump the version at all here as the change is small and not content related. > >
> > > -- > Sergey A. Osokin > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmailman.nginx.org%2Fmailman%2Flistinfo%2Fnginx-devel&data=05%7C01%7Cry.davis%40f5.com%7C1b07ff05f87e41444cac08dbaa68e1d2%7Cdd3dfd2f6a3b40d19be0bf8327d81c50%7C0%7C0%7C638291139671112762%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=AriByeciwGYwHqdfP36a5yoaicPkQlule1QoFF9iugk%3D&reserved=0 _______________________________________________ nginx-devel mailing list nginx-devel at nginx.org https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmailman.nginx.org%2Fmailman%2Flistinfo%2Fnginx-devel&data=05%7C01%7Cry.davis%40f5.com%7C1b07ff05f87e41444cac08dbaa68e1d2%7Cdd3dfd2f6a3b40d19be0bf8327d81c50%7C0%7C0%7C638291139671112762%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=AriByeciwGYwHqdfP36a5yoaicPkQlule1QoFF9iugk%3D&reserved=0 -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: image001.png Type: image/png Size: 7040 bytes Desc: image001.png URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: docs.diff Type: application/octet-stream Size: 1205 bytes Desc: docs.diff URL: From pluknet at nginx.com Fri Sep 1 15:39:46 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 1 Sep 2023 19:39:46 +0400 Subject: [PATCH] QUIC: refined sending CONNECTION_CLOSE in various packet types In-Reply-To: <20230901095556.bfuvz4zgs663zhr5@N00W24XTQX> References: <358c657a4a7afef502a0.1693493986@enoparse.local> <20230901095556.bfuvz4zgs663zhr5@N00W24XTQX> Message-ID: <71BDD0F7-3920-4D80-AEFA-2F0D6E59258B@nginx.com> > On 1 Sep 2023, at 13:55, Roman Arutyunyan wrote: > > Hi, > > On Thu, Aug 31, 2023 at 06:59:46PM +0400, Sergey Kandaurov wrote: >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1693493736 -14400 >> # Thu Aug 31 18:55:36 2023 +0400 >> # Node ID 358c657a4a7afef502a00b9a41bddbe08f6859ae >> # Parent 015353ca1be7acc176f6369ed92ec6c49975ee6a >> QUIC: refined sending CONNECTION_CLOSE in various packet types. >> >> As per RFC 9000, section 10.2.3, to ensure that peer successfully removed >> packet protection, CONNECTION_CLOSE can be sent in multiple packets using >> different packet protection levels. >> >> Specifically, new logic is added to more strictly follow these rules: >> >> - by default, the highest available level of packet protection is used; >> - unless handshake is confirmed, but we have got application keys available, >> that means the client may have or may have not application keys to remove > > may *not* have ? Thanks, replaced with "the client may or may not have". > >> 1-RTT packet protection; in such case, send both 1-RTT and HS packets; >> - additionally, if we still have initial protection keys not yet discarded, >> which happens if the path was not yet validated by successfully removing >> Handshake packet protection, that means the client may not have handshake >> keys; in such case, send an Initial packet too. >> >> This roughly resembles the following paragraph: >> >> * Prior to confirming the handshake, a peer might be unable to process 1-RTT >> packets, so an endpoint SHOULD send a CONNECTION_CLOSE frame in both Handshake >> and 1-RTT packets. A server SHOULD also send a CONNECTION_CLOSE frame in an >> Initial packet. >> >> In practice, this change allows to avoid sending Initial packet when we know >> the client has handshake keys, and fixes sending CONNECTION_CLOSE when using >> QuicTLS with old QUIC API, where TLS stack releases application read keys >> before handshake confirmation, sending it additionally in a Handshake packet. >> >> 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 >> @@ -540,8 +540,20 @@ ngx_quic_close_connection(ngx_connection >> >> (void) ngx_quic_send_cc(c); >> >> - if (qc->error_level == ssl_encryption_handshake) { >> - /* for clients that might not have handshake keys */ >> + if (qc->error_level == ssl_encryption_application >> + && ngx_quic_keys_available(qc->keys, >> + ssl_encryption_handshake)) >> + { >> + /* handshake not confirmed, client may not have app keys */ >> + qc->error_level = ssl_encryption_handshake; >> + (void) ngx_quic_send_cc(c); >> + } >> + >> + if (qc->error_level == ssl_encryption_handshake >> + && ngx_quic_keys_available(qc->keys, >> + ssl_encryption_initial)) >> + { >> + /* path not validated, client may not have hs keys */ >> qc->error_level = ssl_encryption_initial; >> (void) ngx_quic_send_cc(c); >> } > > I have a feeling that we can just send CC for all levels for which keys are > available: > > for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > ctx = &qc->send_ctx[i]; > > if (!ngx_quic_keys_available(qc->keys, ctx->level)) { > continue; > } > > qc->error_level = ctx->level; > (void) ngx_quic_send_cc(c); > > if (rc == NGX_OK && !qc->close.timer_set) { > ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); > } > } Agree, this should be equivalent code, and probably it is simpler. It also removes the need to set something to qc->error_level above in the patch that eliminates the use of SSL_quic_read/write_level. So these lines can be also removed: qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) : ssl_encryption_initial; I had a concern to keep setting PTO out of the cycle, to use the highest protection level available, because ngx_quic_pto() computation depends on a passed level, it takes into account max_ack_delay on the app level. But looking at ngx_quic_pto() I see this depends on whether we have handshaked (c->ssl->handshaked). If yes, then lower levels are already discarded and will be skipped in the cycle, so this should be safe: either the timer is set for application level only (with max_ack_delay), or for any first level available (and max_ack_delay isn't applied). So it looks good in the end, applied. Also, refined commit logs to reflect the change. Since this and the "levels" patch now depend, below are both: # HG changeset patch # User Sergey Kandaurov # Date 1693581796 -14400 # Fri Sep 01 19:23:16 2023 +0400 # Node ID 139d7219cecbc99ebf74475cd6446a5a81f9745f # Parent 8f7e6d8c061e1f4b7656141d2fac85ce6846ac23 QUIC: refined sending CONNECTION_CLOSE in various packet types. As per RFC 9000, section 10.2.3, to ensure that peer successfully removed packet protection, CONNECTION_CLOSE can be sent in multiple packets using different packet protection levels. Now it is sent in all protection levels available. This roughly corresponds to the following paragraph: * Prior to confirming the handshake, a peer might be unable to process 1-RTT packets, so an endpoint SHOULD send a CONNECTION_CLOSE frame in both Handshake and 1-RTT packets. A server SHOULD also send a CONNECTION_CLOSE frame in an Initial packet. In practice, this change allows to avoid sending an Initial packet when we know the client has handshake keys, by checking if we have discarded initial keys. Also, this fixes sending CONNECTION_CLOSE when using QuicTLS with old QUIC API, where TLS stack releases application read keys before handshake confirmation; it is fixed by sending CONNECTION_CLOSE additionally in a Handshake packet. 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 @@ -509,9 +509,6 @@ ngx_quic_close_connection(ngx_connection * 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; @@ -524,17 +521,19 @@ ngx_quic_close_connection(ngx_connection 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)); - } + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (!ngx_quic_keys_available(qc->keys, ctx->level)) { + continue; + } - (void) ngx_quic_send_cc(c); + qc->error_level = ctx->level; + (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); + if (rc == NGX_OK && !qc->close.timer_set) { + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); + } } } # HG changeset patch # User Sergey Kandaurov # Date 1693582604 -14400 # Fri Sep 01 19:36:44 2023 +0400 # Node ID 207a930a2c1d7467e7a8e3c98e1d3851fcdd4d2f # Parent 139d7219cecbc99ebf74475cd6446a5a81f9745f QUIC: removed use of SSL_quic_read_level and SSL_quic_write_level. As explained in BoringSSL change[1], levels were introduced in the original QUIC API to draw a line between when keys are released and when are active. In the new QUIC API they are released in separate calls when it's needed. BoringSSL has then a consideration to remove levels API, hence the change. If not available e.g. from a QUIC packet header, levels can be taken based on keys availability. The only real use of levels is to prevent using app keys before they are active in QuicTLS that provides the old BoringSSL QUIC API, it is replaced with an equivalent check of c->ssl->handshaked. This change also removes OpenSSL compat shims since they are no longer used. The only exception left is caching write level from the keylog callback in the internal field which is a handy equivalent of checking keys availability. [1] https://boringssl.googlesource.com/boringssl/+/1e859054 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 @@ -963,10 +963,7 @@ ngx_quic_handle_payload(ngx_connection_t #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) - { + if (pkt->level == ssl_encryption_application && !c->ssl->handshaked) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic no %s keys ready, ignoring packet", ngx_quic_level_name(pkt->level)); 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 @@ -44,7 +44,6 @@ 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; @@ -213,7 +212,6 @@ ngx_quic_compat_keylog_callback(const SS } 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, @@ -583,32 +581,6 @@ ngx_quic_compat_create_record(ngx_quic_c } -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) 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 @@ -48,8 +48,6 @@ ngx_int_t ngx_quic_compat_init(ngx_conf_ 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, 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 @@ -43,7 +43,8 @@ static int ngx_quic_add_handshake_data(n 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); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level); #if (NGX_QUIC_BORINGSSL_API) @@ -354,7 +355,7 @@ ngx_quic_handle_crypto_frame(ngx_connect } if (f->offset == ctx->crypto.offset) { - if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { + if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { return NGX_ERROR; } @@ -372,7 +373,7 @@ ngx_quic_handle_crypto_frame(ngx_connect cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); if (cl) { - if (ngx_quic_crypto_input(c, cl) != NGX_OK) { + if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { return NGX_ERROR; } @@ -384,7 +385,8 @@ ngx_quic_handle_crypto_frame(ngx_connect static ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level) { int n, sslerr; ngx_buf_t *b; @@ -397,17 +399,10 @@ ngx_quic_crypto_input(ngx_connection_t * 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)) - { + if (!SSL_provide_quic_data(ssl_conn, level, b->pos, b->last - b->pos)) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "SSL_provide_quic_data() failed"); return NGX_ERROR; @@ -416,11 +411,6 @@ ngx_quic_crypto_input(ngx_connection_t * 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) { -- Sergey Kandaurov From pluknet at nginx.com Fri Sep 1 15:52:57 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 01 Sep 2023 15:52:57 +0000 Subject: [nginx] Upstream: fixed handling of Status headers without reason-phrase. Message-ID: details: https://hg.nginx.org/nginx/rev/35bb47f65cab branches: changeset: 9155:35bb47f65cab user: Maxim Dounin date: Thu Aug 31 22:59:17 2023 +0300 description: Upstream: fixed handling of Status headers without reason-phrase. Status header with an empty reason-phrase, such as "Status: 404 ", is valid per CGI specification, but looses the trailing space during parsing. Currently, this results in "HTTP/1.1 404" HTTP status line in the response, which violates HTTP specification due to missing trailing space. With this change, only the status code is used from such short Status header lines, so nginx will generate status line itself, with the space and appropriate reason phrase if available. Reported at: https://mailman.nginx.org/pipermail/nginx/2023-August/EX7G4JUUHJWJE5UOAZMO5UD6OJILCYGX.html diffstat: src/http/modules/ngx_http_fastcgi_module.c | 5 ++++- src/http/modules/ngx_http_scgi_module.c | 5 ++++- src/http/modules/ngx_http_uwsgi_module.c | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diffs (45 lines): diff -r f6b6f3dd7ca0 -r 35bb47f65cab src/http/modules/ngx_http_fastcgi_module.c --- a/src/http/modules/ngx_http_fastcgi_module.c Thu Aug 31 10:54:07 2023 +0400 +++ b/src/http/modules/ngx_http_fastcgi_module.c Thu Aug 31 22:59:17 2023 +0300 @@ -2048,7 +2048,10 @@ ngx_http_fastcgi_process_header(ngx_http } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff -r f6b6f3dd7ca0 -r 35bb47f65cab src/http/modules/ngx_http_scgi_module.c --- a/src/http/modules/ngx_http_scgi_module.c Thu Aug 31 10:54:07 2023 +0400 +++ b/src/http/modules/ngx_http_scgi_module.c Thu Aug 31 22:59:17 2023 +0300 @@ -1153,7 +1153,10 @@ ngx_http_scgi_process_header(ngx_http_re } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff -r f6b6f3dd7ca0 -r 35bb47f65cab src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c Thu Aug 31 10:54:07 2023 +0400 +++ b/src/http/modules/ngx_http_uwsgi_module.c Thu Aug 31 22:59:17 2023 +0300 @@ -1381,7 +1381,10 @@ ngx_http_uwsgi_process_header(ngx_http_r } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; From arut at nginx.com Fri Sep 1 15:54:52 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 1 Sep 2023 19:54:52 +0400 Subject: [PATCH] QUIC: refined sending CONNECTION_CLOSE in various packet types In-Reply-To: <71BDD0F7-3920-4D80-AEFA-2F0D6E59258B@nginx.com> References: <358c657a4a7afef502a0.1693493986@enoparse.local> <20230901095556.bfuvz4zgs663zhr5@N00W24XTQX> <71BDD0F7-3920-4D80-AEFA-2F0D6E59258B@nginx.com> Message-ID: <20230901155452.vrbs3yijkiyzpexy@N00W24XTQX> Hi, On Fri, Sep 01, 2023 at 07:39:46PM +0400, Sergey Kandaurov wrote: > > > On 1 Sep 2023, at 13:55, Roman Arutyunyan wrote: > > > > Hi, > > > > On Thu, Aug 31, 2023 at 06:59:46PM +0400, Sergey Kandaurov wrote: > >> # HG changeset patch > >> # User Sergey Kandaurov > >> # Date 1693493736 -14400 > >> # Thu Aug 31 18:55:36 2023 +0400 > >> # Node ID 358c657a4a7afef502a00b9a41bddbe08f6859ae > >> # Parent 015353ca1be7acc176f6369ed92ec6c49975ee6a > >> QUIC: refined sending CONNECTION_CLOSE in various packet types. > >> > >> As per RFC 9000, section 10.2.3, to ensure that peer successfully removed > >> packet protection, CONNECTION_CLOSE can be sent in multiple packets using > >> different packet protection levels. > >> > >> Specifically, new logic is added to more strictly follow these rules: > >> > >> - by default, the highest available level of packet protection is used; > >> - unless handshake is confirmed, but we have got application keys available, > >> that means the client may have or may have not application keys to remove > > > > may *not* have ? > > Thanks, replaced with "the client may or may not have". > > > > >> 1-RTT packet protection; in such case, send both 1-RTT and HS packets; > >> - additionally, if we still have initial protection keys not yet discarded, > >> which happens if the path was not yet validated by successfully removing > >> Handshake packet protection, that means the client may not have handshake > >> keys; in such case, send an Initial packet too. > >> > >> This roughly resembles the following paragraph: > >> > >> * Prior to confirming the handshake, a peer might be unable to process 1-RTT > >> packets, so an endpoint SHOULD send a CONNECTION_CLOSE frame in both Handshake > >> and 1-RTT packets. A server SHOULD also send a CONNECTION_CLOSE frame in an > >> Initial packet. > >> > >> In practice, this change allows to avoid sending Initial packet when we know > >> the client has handshake keys, and fixes sending CONNECTION_CLOSE when using > >> QuicTLS with old QUIC API, where TLS stack releases application read keys > >> before handshake confirmation, sending it additionally in a Handshake packet. > >> > >> 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 > >> @@ -540,8 +540,20 @@ ngx_quic_close_connection(ngx_connection > >> > >> (void) ngx_quic_send_cc(c); > >> > >> - if (qc->error_level == ssl_encryption_handshake) { > >> - /* for clients that might not have handshake keys */ > >> + if (qc->error_level == ssl_encryption_application > >> + && ngx_quic_keys_available(qc->keys, > >> + ssl_encryption_handshake)) > >> + { > >> + /* handshake not confirmed, client may not have app keys */ > >> + qc->error_level = ssl_encryption_handshake; > >> + (void) ngx_quic_send_cc(c); > >> + } > >> + > >> + if (qc->error_level == ssl_encryption_handshake > >> + && ngx_quic_keys_available(qc->keys, > >> + ssl_encryption_initial)) > >> + { > >> + /* path not validated, client may not have hs keys */ > >> qc->error_level = ssl_encryption_initial; > >> (void) ngx_quic_send_cc(c); > >> } > > > > I have a feeling that we can just send CC for all levels for which keys are > > available: > > > > for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > > ctx = &qc->send_ctx[i]; > > > > if (!ngx_quic_keys_available(qc->keys, ctx->level)) { > > continue; > > } > > > > qc->error_level = ctx->level; > > (void) ngx_quic_send_cc(c); > > > > if (rc == NGX_OK && !qc->close.timer_set) { > > ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); > > } > > } > > Agree, this should be equivalent code, and probably it is simpler. > It also removes the need to set something to qc->error_level above > in the patch that eliminates the use of SSL_quic_read/write_level. > So these lines can be also removed: > > qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) > : ssl_encryption_initial; > > I had a concern to keep setting PTO out of the cycle, to use the highest > protection level available, because ngx_quic_pto() computation depends on > a passed level, it takes into account max_ack_delay on the app level. > But looking at ngx_quic_pto() I see this depends on whether we have > handshaked (c->ssl->handshaked). If yes, then lower levels are already > discarded and will be skipped in the cycle, so this should be safe: > either the timer is set for application level only (with max_ack_delay), > or for any first level available (and max_ack_delay isn't applied). Yes, exactly. > So it looks good in the end, applied. > Also, refined commit logs to reflect the change. > Since this and the "levels" patch now depend, below are both: > > > # HG changeset patch > # User Sergey Kandaurov > # Date 1693581796 -14400 > # Fri Sep 01 19:23:16 2023 +0400 > # Node ID 139d7219cecbc99ebf74475cd6446a5a81f9745f > # Parent 8f7e6d8c061e1f4b7656141d2fac85ce6846ac23 > QUIC: refined sending CONNECTION_CLOSE in various packet types. > > As per RFC 9000, section 10.2.3, to ensure that peer successfully removed > packet protection, CONNECTION_CLOSE can be sent in multiple packets using > different packet protection levels. > > Now it is sent in all protection levels available. > This roughly corresponds to the following paragraph: > > * Prior to confirming the handshake, a peer might be unable to process 1-RTT > packets, so an endpoint SHOULD send a CONNECTION_CLOSE frame in both Handshake > and 1-RTT packets. A server SHOULD also send a CONNECTION_CLOSE frame in an > Initial packet. > > In practice, this change allows to avoid sending an Initial packet when we know > the client has handshake keys, by checking if we have discarded initial keys. > Also, this fixes sending CONNECTION_CLOSE when using QuicTLS with old QUIC API, > where TLS stack releases application read keys before handshake confirmation; > it is fixed by sending CONNECTION_CLOSE additionally in a Handshake packet. > > 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 > @@ -509,9 +509,6 @@ ngx_quic_close_connection(ngx_connection > * 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; > @@ -524,17 +521,19 @@ ngx_quic_close_connection(ngx_connection > 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)); > - } > + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > + ctx = &qc->send_ctx[i]; > + > + if (!ngx_quic_keys_available(qc->keys, ctx->level)) { > + continue; > + } > > - (void) ngx_quic_send_cc(c); > + qc->error_level = ctx->level; > + (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); > + if (rc == NGX_OK && !qc->close.timer_set) { > + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); > + } > } > } > > # HG changeset patch > # User Sergey Kandaurov > # Date 1693582604 -14400 > # Fri Sep 01 19:36:44 2023 +0400 > # Node ID 207a930a2c1d7467e7a8e3c98e1d3851fcdd4d2f > # Parent 139d7219cecbc99ebf74475cd6446a5a81f9745f > QUIC: removed use of SSL_quic_read_level and SSL_quic_write_level. > > As explained in BoringSSL change[1], levels were introduced in the original > QUIC API to draw a line between when keys are released and when are active. > In the new QUIC API they are released in separate calls when it's needed. > BoringSSL has then a consideration to remove levels API, hence the change. > > If not available e.g. from a QUIC packet header, levels can be taken based on > keys availability. The only real use of levels is to prevent using app keys > before they are active in QuicTLS that provides the old BoringSSL QUIC API, > it is replaced with an equivalent check of c->ssl->handshaked. > > This change also removes OpenSSL compat shims since they are no longer used. > The only exception left is caching write level from the keylog callback in > the internal field which is a handy equivalent of checking keys availability. > > [1] https://boringssl.googlesource.com/boringssl/+/1e859054 > > 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 > @@ -963,10 +963,7 @@ ngx_quic_handle_payload(ngx_connection_t > #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) > - { > + if (pkt->level == ssl_encryption_application && !c->ssl->handshaked) { > ngx_log_error(NGX_LOG_INFO, c->log, 0, > "quic no %s keys ready, ignoring packet", > ngx_quic_level_name(pkt->level)); > 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 > @@ -44,7 +44,6 @@ 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; > @@ -213,7 +212,6 @@ ngx_quic_compat_keylog_callback(const SS > > } 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, > @@ -583,32 +581,6 @@ ngx_quic_compat_create_record(ngx_quic_c > } > > > -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) > 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 > @@ -48,8 +48,6 @@ ngx_int_t ngx_quic_compat_init(ngx_conf_ > 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, > 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 > @@ -43,7 +43,8 @@ static int ngx_quic_add_handshake_data(n > 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); > +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, > + enum ssl_encryption_level_t level); > > > #if (NGX_QUIC_BORINGSSL_API) > @@ -354,7 +355,7 @@ ngx_quic_handle_crypto_frame(ngx_connect > } > > if (f->offset == ctx->crypto.offset) { > - if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { > + if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { > return NGX_ERROR; > } > > @@ -372,7 +373,7 @@ ngx_quic_handle_crypto_frame(ngx_connect > cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); > > if (cl) { > - if (ngx_quic_crypto_input(c, cl) != NGX_OK) { > + if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { > return NGX_ERROR; > } > > @@ -384,7 +385,8 @@ ngx_quic_handle_crypto_frame(ngx_connect > > > static ngx_int_t > -ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) > +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, > + enum ssl_encryption_level_t level) > { > int n, sslerr; > ngx_buf_t *b; > @@ -397,17 +399,10 @@ ngx_quic_crypto_input(ngx_connection_t * > > 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)) > - { > + if (!SSL_provide_quic_data(ssl_conn, level, b->pos, b->last - b->pos)) { > ngx_ssl_error(NGX_LOG_INFO, c->log, 0, > "SSL_provide_quic_data() failed"); > return NGX_ERROR; > @@ -416,11 +411,6 @@ ngx_quic_crypto_input(ngx_connection_t * > > 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) { > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Both patches look ok. -- Roman Arutyunyan From pluknet at nginx.com Fri Sep 1 21:15:36 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 01 Sep 2023 21:15:36 +0000 Subject: [nginx] QUIC: refined sending CONNECTION_CLOSE in various packet types. Message-ID: details: https://hg.nginx.org/nginx/rev/36b59521a41c branches: changeset: 9156:36b59521a41c user: Sergey Kandaurov date: Fri Sep 01 20:31:46 2023 +0400 description: QUIC: refined sending CONNECTION_CLOSE in various packet types. As per RFC 9000, section 10.2.3, to ensure that peer successfully removed packet protection, CONNECTION_CLOSE can be sent in multiple packets using different packet protection levels. Now it is sent in all protection levels available. This roughly corresponds to the following paragraph: * Prior to confirming the handshake, a peer might be unable to process 1-RTT packets, so an endpoint SHOULD send a CONNECTION_CLOSE frame in both Handshake and 1-RTT packets. A server SHOULD also send a CONNECTION_CLOSE frame in an Initial packet. In practice, this change allows to avoid sending an Initial packet when we know the client has handshake keys, by checking if we have discarded initial keys. Also, this fixes sending CONNECTION_CLOSE when using QuicTLS with old QUIC API, where TLS stack releases application read keys before handshake confirmation; it is fixed by sending CONNECTION_CLOSE additionally in a Handshake packet. diffstat: src/event/quic/ngx_event_quic.c | 23 +++++++++++------------ 1 files changed, 11 insertions(+), 12 deletions(-) diffs (42 lines): diff -r 35bb47f65cab -r 36b59521a41c src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Thu Aug 31 22:59:17 2023 +0300 +++ b/src/event/quic/ngx_event_quic.c Fri Sep 01 20:31:46 2023 +0400 @@ -509,9 +509,6 @@ ngx_quic_close_connection(ngx_connection * 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; @@ -524,17 +521,19 @@ ngx_quic_close_connection(ngx_connection 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)); - } + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (!ngx_quic_keys_available(qc->keys, ctx->level)) { + continue; + } - (void) ngx_quic_send_cc(c); + qc->error_level = ctx->level; + (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); + if (rc == NGX_OK && !qc->close.timer_set) { + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); + } } } From pluknet at nginx.com Fri Sep 1 21:15:39 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 01 Sep 2023 21:15:39 +0000 Subject: [nginx] QUIC: removed use of SSL_quic_read_level and SSL_quic_write_level. Message-ID: details: https://hg.nginx.org/nginx/rev/daf8f5ba23d8 branches: changeset: 9157:daf8f5ba23d8 user: Sergey Kandaurov date: Fri Sep 01 20:31:46 2023 +0400 description: QUIC: removed use of SSL_quic_read_level and SSL_quic_write_level. As explained in BoringSSL change[1], levels were introduced in the original QUIC API to draw a line between when keys are released and when are active. In the new QUIC API they are released in separate calls when it's needed. BoringSSL has then a consideration to remove levels API, hence the change. If not available e.g. from a QUIC packet header, levels can be taken based on keys availability. The only real use of levels is to prevent using app keys before they are active in QuicTLS that provides the old BoringSSL QUIC API, it is replaced with an equivalent check of c->ssl->handshaked. This change also removes OpenSSL compat shims since they are no longer used. The only exception left is caching write level from the keylog callback in the internal field which is a handy equivalent of checking keys availability. [1] https://boringssl.googlesource.com/boringssl/+/1e859054 diffstat: src/event/quic/ngx_event_quic.c | 5 +--- src/event/quic/ngx_event_quic_openssl_compat.c | 28 -------------------------- src/event/quic/ngx_event_quic_openssl_compat.h | 2 - src/event/quic/ngx_event_quic_ssl.c | 24 ++++++--------------- 4 files changed, 8 insertions(+), 51 deletions(-) diffs (151 lines): diff -r 36b59521a41c -r daf8f5ba23d8 src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic.c Fri Sep 01 20:31:46 2023 +0400 @@ -963,10 +963,7 @@ ngx_quic_handle_payload(ngx_connection_t #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) - { + if (pkt->level == ssl_encryption_application && !c->ssl->handshaked) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic no %s keys ready, ignoring packet", ngx_quic_level_name(pkt->level)); diff -r 36b59521a41c -r daf8f5ba23d8 src/event/quic/ngx_event_quic_openssl_compat.c --- a/src/event/quic/ngx_event_quic_openssl_compat.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic_openssl_compat.c Fri Sep 01 20:31:46 2023 +0400 @@ -44,7 +44,6 @@ 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; @@ -213,7 +212,6 @@ ngx_quic_compat_keylog_callback(const SS } 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, @@ -583,32 +581,6 @@ ngx_quic_compat_create_record(ngx_quic_c } -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) diff -r 36b59521a41c -r daf8f5ba23d8 src/event/quic/ngx_event_quic_openssl_compat.h --- a/src/event/quic/ngx_event_quic_openssl_compat.h Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic_openssl_compat.h Fri Sep 01 20:31:46 2023 +0400 @@ -48,8 +48,6 @@ ngx_int_t ngx_quic_compat_init(ngx_conf_ 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, diff -r 36b59521a41c -r daf8f5ba23d8 src/event/quic/ngx_event_quic_ssl.c --- a/src/event/quic/ngx_event_quic_ssl.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic_ssl.c Fri Sep 01 20:31:46 2023 +0400 @@ -43,7 +43,8 @@ static int ngx_quic_add_handshake_data(n 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); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level); #if (NGX_QUIC_BORINGSSL_API) @@ -354,7 +355,7 @@ ngx_quic_handle_crypto_frame(ngx_connect } if (f->offset == ctx->crypto.offset) { - if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { + if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { return NGX_ERROR; } @@ -372,7 +373,7 @@ ngx_quic_handle_crypto_frame(ngx_connect cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); if (cl) { - if (ngx_quic_crypto_input(c, cl) != NGX_OK) { + if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { return NGX_ERROR; } @@ -384,7 +385,8 @@ ngx_quic_handle_crypto_frame(ngx_connect static ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level) { int n, sslerr; ngx_buf_t *b; @@ -397,17 +399,10 @@ ngx_quic_crypto_input(ngx_connection_t * 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)) - { + if (!SSL_provide_quic_data(ssl_conn, level, b->pos, b->last - b->pos)) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "SSL_provide_quic_data() failed"); return NGX_ERROR; @@ -416,11 +411,6 @@ ngx_quic_crypto_input(ngx_connection_t * 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) { From yar at nginx.com Fri Sep 1 23:09:59 2023 From: yar at nginx.com (Yaroslav Zhuravlev) Date: Sat, 2 Sep 2023 00:09:59 +0100 Subject: [PATCH] Minor docs fixes In-Reply-To: References: <806e7402d63563de6e45.1693512078@C02CQ2ZCMD6R> Message-ID: > On 1 Sep 2023, at 14:00, Ryan Davis via nginx-devel wrote: > > Thanks for the feedback Yaroslav! Updated patch is attached with the suggested commit log, whitespace, and `rev` changes. No problem at all, thank you for the patch, committed: http://hg.nginx.org/nginx.org/rev/8b8f94802f41 [...] From mdounin at mdounin.ru Sat Sep 2 00:51:22 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 2 Sep 2023 03:51:22 +0300 Subject: [PATCH] Provided more details about ssl_protocol directive usage by In-Reply-To: References: Message-ID: Hello! On Wed, Aug 30, 2023 at 12:34:16AM +0300, Sergey A. Osokin wrote: > Hi, > > On Tue, Aug 29, 2023 at 09:21:54PM +0300, Maxim Dounin wrote: > > > > > > Enables the specified protocols. > > > + > > > + > > > > $ make > > ... > > xmllint --noout --valid xml/en/docs/http/ngx_http_ssl_module.xml > > xml/en/docs/http/ngx_http_ssl_module.xml:626: element para: > > validity error : Element para is not declared in para list of > > possible children > > > > ^ > > gmake[1]: *** [GNUmakefile:164: > > libxslt/en/docs/http/ngx_http_ssl_module.html] Error 4 > > > > In this particular case, appropriate solution would be to close > > the paragraph, and put notes into their own paragraph. > > Fixed. > > > > +If the directive is specified on the level, > > > +the value from the default will be used. > > > > s/the default/the default server/ > > Fixed. > > > Also, reading it again I tend to think that "will be used" might > > be misleading. It might be good to adjust this somehow to make it > > clear that ssl_protocols works if used in the default server, but > > will use the configuration from the default server if used in a > > name-based virtual server. Not sure how though. > > > > Alternatively, just using "can be used" as in other directives > > might be good enough. > > Fixed. > > > > +Defails are provided in the > > > > s/Defails/Details/ > > Fixed. > > [...] > > > + > > > + > > > > > > Параметры TLSv1.1 и TLSv1.2 > > > (1.1.13, 1.0.12) работают только при использовании OpenSSL 1.0.1 и выше. > > > > Same here. > > Fixed. Here's the update version of the patch, thanks. > > # HG changeset patch > # User Sergey A. Osokin > # Date 1693344638 -10800 > # Wed Aug 30 00:30:38 2023 +0300 > # Node ID 44c3f59a54333249114428eaae2005eabdc57e36 > # Parent 4e25281328fa2152cadedc52e05f8a1b1bf531cd > Updated ngx_http_ssl_module module documentation. > > Provided details about ssl_protocol directive usage by > adding link to the "Server names" document. > > diff -r 4e25281328fa -r 44c3f59a5433 xml/en/docs/http/ngx_http_ssl_module.xml > --- a/xml/en/docs/http/ngx_http_ssl_module.xml Tue Aug 29 09:11:57 2023 +0100 > +++ b/xml/en/docs/http/ngx_http_ssl_module.xml Wed Aug 30 00:30:38 2023 +0300 > @@ -10,7 +10,7 @@ > link="/en/docs/http/ngx_http_ssl_module.html" > lang="en" > - rev="61"> > + rev="62"> > >
> > @@ -602,6 +602,17 @@ > > > Enables the specified protocols. > + > + > + > +If the directive is specified on the level, > +the value from the default server can be used. > +Details are provided in the > +“Virtual > +server selection” section. > + > + > + > > The TLSv1.1 and TLSv1.2 parameters > (1.1.13, 1.0.12) work only when OpenSSL 1.0.1 or higher is used. > diff -r 4e25281328fa -r 44c3f59a5433 xml/ru/docs/http/ngx_http_ssl_module.xml > --- a/xml/ru/docs/http/ngx_http_ssl_module.xml Tue Aug 29 09:11:57 2023 +0100 > +++ b/xml/ru/docs/http/ngx_http_ssl_module.xml Wed Aug 30 00:30:38 2023 +0300 > @@ -10,7 +10,7 @@ > link="/ru/docs/http/ngx_http_ssl_module.html" > lang="ru" > - rev="61"> > + rev="62"> > >
> > @@ -607,6 +607,17 @@ > > > Разрешает указанные протоколы. > + > + > + > +Если директива указана на уровне , то может > +использоваться значение из сервера по умолчанию. > +Подробнее см. в разделе > +“Выбор > +виртуального сервера”. > + > + > + > > Параметры TLSv1.1 и TLSv1.2 > (1.1.13, 1.0.12) работают только при использовании OpenSSL 1.0.1 и выше. Looks good. -- Maxim Dounin http://mdounin.ru/ From vbart at wbsrv.ru Mon Sep 4 19:11:47 2023 From: vbart at wbsrv.ru (=?iso-8859-1?q?Valentin_V=2E_Bartenev?=) Date: Mon, 04 Sep 2023 22:11:47 +0300 Subject: [PATCH] Allowed nesting arbitrary prefix "location" in regex "location" Message-ID: # HG changeset patch # User Valentin Bartenev # Date 1693854233 -10800 # Mon Sep 04 22:03:53 2023 +0300 # Node ID c706913db63c6862c13a0a540cdc37be0ccf0c81 # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa Allowed nesting arbitrary prefix "location" in regex "location". Previously, only prefix "location" blocks that literally matched the beginning of the regular expression were allowed inside. This restriction makes no sense because regular expressions have different matching semantics. 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 @@ -3202,6 +3202,7 @@ ngx_http_core_location(ngx_conf_t *cf, n #if (NGX_PCRE) if (clcf->regex == NULL + && pclcf->regex == NULL && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0) #else if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0) From xeioex at nginx.com Tue Sep 5 16:17:39 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 05 Sep 2023 16:17:39 +0000 Subject: [njs] Modules: added worker_affinity parameter for js_periodic directive. Message-ID: details: https://hg.nginx.org/njs/rev/e3c442561889 branches: changeset: 2190:e3c442561889 user: Dmitry Volyntsev date: Tue Sep 05 09:17:10 2023 -0700 description: Modules: added worker_affinity parameter for js_periodic directive. worker_affinity specifies on what set of workers the js_periodic handler should be executed. By default the js_handler is executed only on worker 0. The parameter accepts a binary mask or "all" to specify all workers. example.conf: worker_processes 4; ... location @periodics { # to be run at 1 minute intervals in worker 0 js_periodic main.handler interval=60s; # to be run at 1 minute intervals in all the workers js_periodic main.handler interval=60s worker_affinity=all; # to be run at 1 minute intervals in workers 1 and 3 js_periodic main.handler interval=60s worker_affinity=0101; } diffstat: nginx/ngx_http_js_module.c | 68 +++++++++++++++++++++++++++++++++++++++++++- nginx/ngx_stream_js_module.c | 68 +++++++++++++++++++++++++++++++++++++++++++- nginx/t/js_periodic.t | 67 +++++++++++++++--------------------------- nginx/t/stream_js_periodic.t | 66 +++++++++++++++--------------------------- 4 files changed, 182 insertions(+), 87 deletions(-) diffs (587 lines): diff -r 58d40fc80c52 -r e3c442561889 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Thu Aug 31 08:24:17 2023 -0700 +++ b/nginx/ngx_http_js_module.c Tue Sep 05 09:17:10 2023 -0700 @@ -25,7 +25,7 @@ typedef struct { typedef struct { ngx_http_conf_ctx_t *conf_ctx; ngx_connection_t *connection; - void *padding; + uint8_t *worker_affinity; /** * fd is used for event debug and should be at the same position @@ -4544,6 +4544,16 @@ ngx_http_js_init_worker(ngx_cycle_t *cyc periodics = jmcf->periodics->elts; for (i = 0; i < jmcf->periodics->nelts; i++) { + if (periodics[i].worker_affinity != NULL + && !periodics[i].worker_affinity[ngx_worker]) + { + continue; + } + + if (periodics[i].worker_affinity == NULL && ngx_worker != 0) { + continue; + } + periodics[i].fd = 1000000 + i; if (ngx_http_js_periodic_init(&periodics[i]) != NGX_OK) { @@ -4558,9 +4568,11 @@ ngx_http_js_init_worker(ngx_cycle_t *cyc static char * ngx_http_js_periodic(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { + uint8_t *mask; ngx_str_t *value, s; ngx_msec_t interval, jitter; ngx_uint_t i; + ngx_core_conf_t *ccf; ngx_js_periodic_t *periodic; ngx_js_main_conf_t *jmcf; @@ -4586,6 +4598,7 @@ ngx_http_js_periodic(ngx_conf_t *cf, ngx ngx_memzero(periodic, sizeof(ngx_js_periodic_t)); + mask = NULL; jitter = 0; interval = 5000; @@ -4619,6 +4632,58 @@ ngx_http_js_periodic(ngx_conf_t *cf, ngx continue; } + if (ngx_strncmp(value[i].data, "worker_affinity=", 16) == 0) { + s.len = value[i].len - 16; + s.data = value[i].data + 16; + + ccf = (ngx_core_conf_t *) ngx_get_conf(cf->cycle->conf_ctx, + ngx_core_module); + + if (ccf->worker_processes == NGX_CONF_UNSET) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"worker_affinity\" is not supported " + "with unset \"worker_processes\" directive"); + return NGX_CONF_ERROR; + } + + mask = ngx_palloc(cf->pool, ccf->worker_processes); + if (mask == NULL) { + return NGX_CONF_ERROR; + } + + if (ngx_strncmp(s.data, "all", 3) == 0) { + memset(mask, 1, ccf->worker_processes); + continue; + } + + if ((size_t) ccf->worker_processes != s.len) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the number of " + "\"worker_processes\" is not equal to the " + "size of \"worker_affinity\" mask"); + return NGX_CONF_ERROR; + } + + for (i = 0; i < s.len; i++) { + if (s.data[i] == '0') { + mask[i] = 0; + continue; + } + + if (s.data[i] == '1') { + mask[i] = 1; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid character \"%c\" in \"worker_affinity=\"", + s.data[i]); + + return NGX_CONF_ERROR; + } + + continue; + } + invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -4629,6 +4694,7 @@ invalid: periodic->method = value[1]; periodic->interval = interval; periodic->jitter = jitter; + periodic->worker_affinity = mask; periodic->conf_ctx = cf->ctx; return NGX_CONF_OK; diff -r 58d40fc80c52 -r e3c442561889 nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Thu Aug 31 08:24:17 2023 -0700 +++ b/nginx/ngx_stream_js_module.c Tue Sep 05 09:17:10 2023 -0700 @@ -30,7 +30,7 @@ typedef struct { typedef struct { ngx_stream_conf_ctx_t *conf_ctx; ngx_connection_t *connection; - void *padding; + uint8_t *worker_affinity; /** * fd is used for event debug and should be at the same position @@ -2049,6 +2049,16 @@ ngx_stream_js_init_worker(ngx_cycle_t *c periodics = jmcf->periodics->elts; for (i = 0; i < jmcf->periodics->nelts; i++) { + if (periodics[i].worker_affinity != NULL + && !periodics[i].worker_affinity[ngx_worker]) + { + continue; + } + + if (periodics[i].worker_affinity == NULL && ngx_worker != 0) { + continue; + } + periodics[i].fd = 1000000 + i; if (ngx_stream_js_periodic_init(&periodics[i]) != NGX_OK) { @@ -2063,9 +2073,11 @@ ngx_stream_js_init_worker(ngx_cycle_t *c static char * ngx_stream_js_periodic(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { + uint8_t *mask; ngx_str_t *value, s; ngx_msec_t interval, jitter; ngx_uint_t i; + ngx_core_conf_t *ccf; ngx_js_periodic_t *periodic; ngx_js_main_conf_t *jmcf; @@ -2091,6 +2103,7 @@ ngx_stream_js_periodic(ngx_conf_t *cf, n ngx_memzero(periodic, sizeof(ngx_js_periodic_t)); + mask = NULL; jitter = 0; interval = 5000; @@ -2124,6 +2137,58 @@ ngx_stream_js_periodic(ngx_conf_t *cf, n continue; } + if (ngx_strncmp(value[i].data, "worker_affinity=", 16) == 0) { + s.len = value[i].len - 16; + s.data = value[i].data + 16; + + ccf = (ngx_core_conf_t *) ngx_get_conf(cf->cycle->conf_ctx, + ngx_core_module); + + if (ccf->worker_processes == NGX_CONF_UNSET) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"worker_affinity\" is not supported " + "with unset \"worker_processes\" directive"); + return NGX_CONF_ERROR; + } + + mask = ngx_palloc(cf->pool, ccf->worker_processes); + if (mask == NULL) { + return NGX_CONF_ERROR; + } + + if (ngx_strncmp(s.data, "all", 3) == 0) { + memset(mask, 1, ccf->worker_processes); + continue; + } + + if ((size_t) ccf->worker_processes != s.len) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the number of " + "\"worker_processes\" is not equal to the " + "size of \"worker_affinity\" mask"); + return NGX_CONF_ERROR; + } + + for (i = 0; i < s.len; i++) { + if (s.data[i] == '0') { + mask[i] = 0; + continue; + } + + if (s.data[i] == '1') { + mask[i] = 1; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid character \"%c\" in \"worker_affinity=\"", + s.data[i]); + + return NGX_CONF_ERROR; + } + + continue; + } + invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -2134,6 +2199,7 @@ invalid: periodic->method = value[1]; periodic->interval = interval; periodic->jitter = jitter; + periodic->worker_affinity = mask; periodic->conf_ctx = cf->ctx; return NGX_CONF_OK; diff -r 58d40fc80c52 -r e3c442561889 nginx/t/js_periodic.t --- a/nginx/t/js_periodic.t Thu Aug 31 08:24:17 2023 -0700 +++ b/nginx/t/js_periodic.t Tue Sep 05 09:17:10 2023 -0700 @@ -23,12 +23,13 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http/) +my $t = Test::Nginx->new()->has(qw/http rewrite/) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% daemon off; +worker_processes 4; events { } @@ -42,6 +43,7 @@ http { js_shared_dict_zone zone=nums:32k type=number; js_shared_dict_zone zone=strings:32k; + js_shared_dict_zone zone=workers:32k type=number; server { listen 127.0.0.1:8080; @@ -49,11 +51,12 @@ http { location @periodic { js_periodic test.tick interval=30ms jitter=1ms; - js_periodic test.timer interval=1s; + js_periodic test.timer interval=1s worker_affinity=all; js_periodic test.overrun interval=30ms; js_periodic test.file interval=1s; js_periodic test.fetch interval=40ms; js_periodic test.multiple_fetches interval=1s; + js_periodic test.affinity interval=50ms worker_affinity=0101; js_periodic test.fetch_exception interval=1s; js_periodic test.tick_exception interval=1s; @@ -69,6 +72,10 @@ http { return 200 'foo'; } + location /test_affinity { + js_content test.test_affinity; + } + location /test_fetch { js_content test.test_fetch; } @@ -102,11 +109,11 @@ my $p0 = port(8080); $t->write_file('test.js', < {}, 100000); } function tick() { - if (ngx.worker_id != 0) { - return; - } - ngx.shared.nums.incr('tick', 1); } function tick_exception() { - if (ngx.worker_id != 0) { - return; - } - throw new Error("EXCEPTION"); } @@ -180,19 +163,11 @@ my $p0 = port(8080); } function timer_exception() { - if (ngx.worker_id != 0) { - return; - } - setTimeout(() => {ngx.log(ngx.ERR, 'should not be seen')}, 10); throw new Error("EXCEPTION"); } function timeout_exception() { - if (ngx.worker_id != 0) { - return; - } - setTimeout(() => { var v = ngx.shared.nums.get('timeout_exception') || 0; @@ -206,6 +181,10 @@ my $p0 = port(8080); }, 1); } + function test_affinity(r) { + r.return(200, `[\${ngx.shared.workers.keys().toSorted()}]`); + } + function test_fetch(r) { r.return(200, ngx.shared.strings.get('fetch').startsWith('okok')); } @@ -232,18 +211,20 @@ my $p0 = port(8080); r.return(200, ngx.shared.nums.get('timeout_exception') >= 2); } - export default { fetch, fetch_exception, file, multiple_fetches, overrun, - test_fetch, test_file, test_multiple_fetches, test_tick, - test_timeout_exception, test_timer, tick, tick_exception, - timer, timer_exception, timeout_exception }; + export default { affinity, fetch, fetch_exception, file, multiple_fetches, + overrun, test_affinity, test_fetch, test_file, + test_multiple_fetches, test_tick, test_timeout_exception, + test_timer, tick, tick_exception, timer, timer_exception, + timeout_exception }; EOF -$t->try_run('no js_periodic')->plan(7); +$t->try_run('no js_periodic')->plan(8); ############################################################################### select undef, undef, undef, 0.1; +like(http_get('/test_affinity'), qr/\[1,3]/, 'affinity test'); like(http_get('/test_tick'), qr/true/, '3x tick test'); like(http_get('/test_timer'), qr/true/, 'timer test'); like(http_get('/test_file'), qr/true/, 'file test'); diff -r 58d40fc80c52 -r e3c442561889 nginx/t/stream_js_periodic.t --- a/nginx/t/stream_js_periodic.t Thu Aug 31 08:24:17 2023 -0700 +++ b/nginx/t/stream_js_periodic.t Tue Sep 05 09:17:10 2023 -0700 @@ -24,12 +24,13 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http stream/) +my $t = Test::Nginx->new()->has(qw/http rewrite stream/) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% daemon off; +worker_processes 4; events { } @@ -43,16 +44,18 @@ stream { js_shared_dict_zone zone=nums:32k type=number; js_shared_dict_zone zone=strings:32k; + js_shared_dict_zone zone=workers:32k type=number; server { listen 127.0.0.1:8080; js_periodic test.tick interval=30ms jitter=1ms; - js_periodic test.timer interval=1s; + js_periodic test.timer interval=1s worker_affinity=all; js_periodic test.overrun interval=30ms; js_periodic test.file interval=1s; js_periodic test.fetch interval=40ms; js_periodic test.multiple_fetches interval=1s; + js_periodic test.affinity interval=50ms worker_affinity=0101; js_periodic test.fetch_exception interval=1s; js_periodic test.tick_exception interval=1s; @@ -89,11 +92,11 @@ my $p1 = port(8081); $t->write_file('test.js', < {}, 100000); } function tick() { - if (ngx.worker_id != 0) { - return; - } - ngx.shared.nums.incr('tick', 1); } function tick_exception() { - if (ngx.worker_id != 0) { - return; - } - throw new Error("EXCEPTION"); } @@ -166,19 +145,11 @@ my $p1 = port(8081); } function timer_exception() { - if (ngx.worker_id != 0) { - return; - } - setTimeout(() => {ngx.log(ngx.ERR, 'should not be seen')}, 10); throw new Error("EXCEPTION"); } function timeout_exception() { - if (ngx.worker_id != 0) { - return; - } - setTimeout(() => { var v = ngx.shared.nums.get('timeout_exception') || 0; @@ -196,6 +167,15 @@ my $p1 = port(8081); s.on('upload', function (data) { if (data.length > 0) { switch (data) { + case 'affinity': + if (ngx.shared.workers.keys().toSorted().toString() + == '1,3') + { + s.done(); + return; + } + + break; case 'fetch': if (ngx.shared.strings.get('fetch').startsWith('okok')) { s.done(); @@ -258,19 +238,21 @@ my $p1 = port(8081); }); } - export default { fetch, fetch_exception, multiple_fetches, file, overrun, - test, tick, tick_exception, timer, timer_exception, - timeout_exception }; + export default { affinity, fetch, fetch_exception, multiple_fetches, file, + overrun, test, tick, tick_exception, timer, + timer_exception, timeout_exception }; EOF $t->run_daemon(\&stream_daemon, port(8090)); -$t->try_run('no js_periodic')->plan(7); +$t->try_run('no js_periodic')->plan(8); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### select undef, undef, undef, 0.1; +is(stream('127.0.0.1:' . port(8080))->io('affinity'), 'affinity', + 'affinity test'); is(stream('127.0.0.1:' . port(8080))->io('tick'), 'tick', '3x tick test'); is(stream('127.0.0.1:' . port(8080))->io('timer'), 'timer', 'timer test'); is(stream('127.0.0.1:' . port(8080))->io('file'), 'file', 'file test'); From xeioex at nginx.com Wed Sep 6 23:47:07 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 06 Sep 2023 23:47:07 +0000 Subject: [njs] Modules: added a session object for js_periodic handler. Message-ID: details: https://hg.nginx.org/njs/rev/0f1e76ab9d45 branches: changeset: 2191:0f1e76ab9d45 user: Dmitry Volyntsev date: Tue Sep 05 18:15:14 2023 -0700 description: Modules: added a session object for js_periodic handler. Now js_periodic handler is provided with a session object as its first argument. Session object can be used to access variables created with js_set, js_var or map directives. example.conf: js_var $js_var JS-VAR; location @periodics { js_periodic main.handler interval=60s; } example.js: function handler(s) { ngx.log(ngx.INFO, s.variables.js_var); } diffstat: nginx/ngx_http_js_module.c | 115 +++++++++++++++++++++++++++++++++--------- nginx/ngx_stream_js_module.c | 112 +++++++++++++++++++++++++++++++++-------- nginx/t/js_periodic.t | 37 +++++++++++- nginx/t/stream_js_periodic.t | 35 +++++++++++- ts/ngx_http_js_module.d.ts | 22 ++++++++ ts/ngx_stream_js_module.d.ts | 22 ++++++++ 6 files changed, 286 insertions(+), 57 deletions(-) diffs (694 lines): diff -r e3c442561889 -r 0f1e76ab9d45 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Tue Sep 05 09:17:10 2023 -0700 +++ b/nginx/ngx_http_js_module.c Tue Sep 05 18:15:14 2023 -0700 @@ -111,8 +111,7 @@ static ngx_int_t ngx_http_js_variable_se ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_js_variable_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r, - unsigned inject_request); +static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id); static void ngx_http_js_cleanup_ctx(void *data); static njs_int_t ngx_http_js_ext_keys_header(njs_vm_t *vm, njs_value_t *value, @@ -217,6 +216,9 @@ static njs_int_t ngx_http_js_ext_keys_he static njs_int_t ngx_http_js_ext_variables(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_http_js_periodic_session_variables(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_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static ngx_int_t ngx_http_js_subrequest(ngx_http_request_t *r, @@ -494,6 +496,7 @@ static ngx_http_output_body_filter_pt static njs_int_t ngx_http_js_request_proto_id; +static njs_int_t ngx_http_js_periodic_session_proto_id; static njs_external_t ngx_http_js_ext_request[] = { @@ -817,6 +820,38 @@ static njs_external_t ngx_http_js_ext_r }; +static njs_external_t ngx_http_js_ext_periodic_session[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "PeriodicSession", + } + }, + + { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("rawVariables"), + .u.object = { + .writable = 1, + .prop_handler = ngx_http_js_periodic_session_variables, + .magic32 = NGX_JS_BUFFER, + } + }, + + { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("variables"), + .u.object = { + .writable = 1, + .prop_handler = ngx_http_js_periodic_session_variables, + .magic32 = NGX_JS_STRING, + } + }, +}; + + static njs_vm_ops_t ngx_http_js_ops = { ngx_http_js_set_timer, ngx_http_js_clear_timer, @@ -904,7 +939,7 @@ ngx_http_js_content_event_handler(ngx_ht ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http js content event handler"); - rc = ngx_http_js_init_vm(r, 1); + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR || rc == NGX_DECLINED) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); @@ -1040,7 +1075,7 @@ ngx_http_js_header_filter(ngx_http_reque return ngx_http_next_header_filter(r); } - rc = ngx_http_js_init_vm(r, 1); + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR || rc == NGX_DECLINED) { return NGX_ERROR; @@ -1092,7 +1127,7 @@ ngx_http_js_body_filter(ngx_http_request return ngx_http_next_body_filter(r, in); } - rc = ngx_http_js_init_vm(r, 1); + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR || rc == NGX_DECLINED) { return NGX_ERROR; @@ -1206,7 +1241,7 @@ ngx_http_js_variable_set(ngx_http_reques ngx_str_t value; ngx_http_js_ctx_t *ctx; - rc = ngx_http_js_init_vm(r, 1); + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -1280,7 +1315,7 @@ ngx_http_js_variable_var(ngx_http_reques static ngx_int_t -ngx_http_js_init_vm(ngx_http_request_t *r, unsigned inject_request) +ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) { njs_int_t rc; ngx_str_t exception; @@ -1359,12 +1394,10 @@ ngx_http_js_init_vm(ngx_http_request_t * return NGX_ERROR; } - if (inject_request) { - rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->request), - ngx_http_js_request_proto_id, r, 0); - if (rc != NJS_OK) { - return NGX_ERROR; - } + rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->request), + proto_id, r, 0); + if (rc != NJS_OK) { + return NGX_ERROR; } return NGX_OK; @@ -2904,24 +2937,17 @@ ngx_http_js_ext_keys_header_in(njs_vm_t static njs_int_t -ngx_http_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop, - njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, + ngx_http_request_t *r, njs_value_t *setval, njs_value_t *retval) { njs_int_t rc; njs_str_t val, s; ngx_str_t name; ngx_uint_t key; - ngx_http_request_t *r; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; ngx_http_variable_value_t *vv; - r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); - if (r == NULL) { - njs_value_undefined_set(retval); - return NJS_DECLINED; - } - rc = njs_vm_prop_name(vm, prop, &val); if (rc != NJS_OK) { njs_value_undefined_set(retval); @@ -3001,6 +3027,38 @@ ngx_http_js_ext_variables(njs_vm_t *vm, static njs_int_t +ngx_http_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + ngx_http_request_t *r; + + r = njs_vm_external(vm, ngx_http_js_request_proto_id, value); + if (r == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + return ngx_http_js_request_variables(vm, prop, r, setval, retval); +} + + +static njs_int_t +ngx_http_js_periodic_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + ngx_http_request_t *r; + + r = njs_vm_external(vm, ngx_http_js_periodic_session_proto_id, value); + if (r == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + return ngx_http_js_request_variables(vm, prop, r, setval, retval); +} + + +static njs_int_t ngx_http_js_promise_trampoline(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { @@ -4168,7 +4226,7 @@ ngx_http_js_periodic_handler(ngx_event_t r->health_check = 1; r->write_event_handler = ngx_http_js_periodic_write_event_handler; - rc = ngx_http_js_init_vm(r, 0); + rc = ngx_http_js_init_vm(r, ngx_http_js_periodic_session_proto_id); if (rc != NGX_OK) { ngx_http_js_periodic_destroy(r, periodic); @@ -4181,8 +4239,8 @@ ngx_http_js_periodic_handler(ngx_event_t r->count++; - rc = ngx_js_invoke(ctx->vm, &periodic->method, &periodic->log, NULL, 0, - &ctx->retval); + rc = ngx_js_invoke(ctx->vm, &periodic->method, &periodic->log, + &ctx->request, 1, &ctx->retval); if (rc == NGX_AGAIN) { rc = NGX_OK; @@ -4482,6 +4540,13 @@ ngx_js_http_init(njs_vm_t *vm) return NJS_ERROR; } + ngx_http_js_periodic_session_proto_id = njs_vm_external_prototype(vm, + ngx_http_js_ext_periodic_session, + njs_nitems(ngx_http_js_ext_periodic_session)); + if (ngx_http_js_periodic_session_proto_id < 0) { + return NJS_ERROR; + } + return NJS_OK; } diff -r e3c442561889 -r 0f1e76ab9d45 nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Tue Sep 05 09:17:10 2023 -0700 +++ b/nginx/ngx_stream_js_module.c Tue Sep 05 18:15:14 2023 -0700 @@ -88,7 +88,7 @@ static ngx_int_t ngx_stream_js_variable_ static ngx_int_t ngx_stream_js_variable_var(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s, - unsigned inject_session); + njs_int_t proto_id); static void ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx); static void ngx_stream_js_cleanup(void *data); static njs_int_t ngx_stream_js_run_event(ngx_stream_session_t *s, @@ -117,6 +117,9 @@ static njs_int_t ngx_stream_js_ext_set_r static njs_int_t ngx_stream_js_ext_variables(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_stream_js_periodic_variables(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); static njs_host_event_t ngx_stream_js_set_timer(njs_external_ptr_t external, uint64_t delay, njs_vm_event_t vm_event); @@ -546,6 +549,38 @@ static njs_external_t ngx_stream_js_ext }; +static njs_external_t ngx_stream_js_ext_periodic_session[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "PeriodicSession", + } + }, + + { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("rawVariables"), + .u.object = { + .writable = 1, + .prop_handler = ngx_stream_js_periodic_variables, + .magic32 = NGX_JS_BUFFER, + } + }, + + { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("variables"), + .u.object = { + .writable = 1, + .prop_handler = ngx_stream_js_periodic_variables, + .magic32 = NGX_JS_STRING, + } + }, +}; + + static njs_external_t ngx_stream_js_ext_session_flags[] = { { @@ -613,6 +648,7 @@ static ngx_stream_filter_pt ngx_stream_ static njs_int_t ngx_stream_js_session_proto_id; +static njs_int_t ngx_stream_js_periodic_session_proto_id; static njs_int_t ngx_stream_js_session_flags_proto_id; @@ -686,7 +722,7 @@ ngx_stream_js_phase_handler(ngx_stream_s return NGX_DECLINED; } - rc = ngx_stream_js_init_vm(s, 1); + rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); if (rc != NGX_OK) { return rc; } @@ -767,7 +803,7 @@ ngx_stream_js_body_filter(ngx_stream_ses ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js filter u:%ui", from_upstream); - rc = ngx_stream_js_init_vm(s, 1); + rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -875,7 +911,7 @@ ngx_stream_js_variable_set(ngx_stream_se ngx_str_t value; ngx_stream_js_ctx_t *ctx; - rc = ngx_stream_js_init_vm(s, 1); + rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -949,7 +985,7 @@ ngx_stream_js_variable_var(ngx_stream_se static ngx_int_t -ngx_stream_js_init_vm(ngx_stream_session_t *s, unsigned inject_session) +ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id) { njs_int_t rc; njs_str_t key; @@ -1026,12 +1062,10 @@ ngx_stream_js_init_vm(ngx_stream_session return NGX_ERROR; } - if (inject_session) { - rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[0]), - ngx_stream_js_session_proto_id, s, 0); - if (rc != NJS_OK) { - return NGX_ERROR; - } + rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[0]), + proto_id, s, 0); + if (rc != NJS_OK) { + return NGX_ERROR; } return NGX_OK; @@ -1507,24 +1541,17 @@ ngx_stream_js_ext_set_return_value(njs_v static njs_int_t -ngx_stream_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop, - njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, + ngx_stream_session_t *s, njs_value_t *setval, njs_value_t *retval) { njs_int_t rc; njs_str_t val; ngx_str_t name; ngx_uint_t key; ngx_stream_variable_t *v; - ngx_stream_session_t *s; ngx_stream_core_main_conf_t *cmcf; ngx_stream_variable_value_t *vv; - s = njs_vm_external(vm, ngx_stream_js_session_proto_id, value); - if (s == NULL) { - njs_value_undefined_set(retval); - return NJS_DECLINED; - } - rc = njs_vm_prop_name(vm, prop, &val); if (rc != NJS_OK) { njs_value_undefined_set(retval); @@ -1601,6 +1628,38 @@ ngx_stream_js_ext_variables(njs_vm_t *vm } +static njs_int_t +ngx_stream_js_ext_variables(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + ngx_stream_session_t *s; + + s = njs_vm_external(vm, ngx_stream_js_session_proto_id, value); + if (s == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + return ngx_stream_js_session_variables(vm, prop, s, setval, retval); +} + + +static njs_int_t +ngx_stream_js_periodic_variables(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + ngx_stream_session_t *s; + + s = njs_vm_external(vm, ngx_stream_js_periodic_session_proto_id, value); + if (s == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + return ngx_stream_js_session_variables(vm, prop, s, setval, retval); +} + + static njs_host_event_t ngx_stream_js_set_timer(njs_external_ptr_t external, uint64_t delay, njs_vm_event_t vm_event) @@ -1770,6 +1829,13 @@ ngx_js_stream_init(njs_vm_t *vm) return NJS_ERROR; } + ngx_stream_js_periodic_session_proto_id = njs_vm_external_prototype(vm, + ngx_stream_js_ext_periodic_session, + njs_nitems(ngx_stream_js_ext_periodic_session)); + if (ngx_stream_js_periodic_session_proto_id < 0) { + return NJS_ERROR; + } + ngx_stream_js_session_flags_proto_id = njs_vm_external_prototype(vm, ngx_stream_js_ext_session_flags, njs_nitems(ngx_stream_js_ext_session_flags)); @@ -1885,7 +1951,7 @@ ngx_stream_js_periodic_handler(ngx_event s->health_check = 1; - rc = ngx_stream_js_init_vm(s, 0); + rc = ngx_stream_js_init_vm(s, ngx_stream_js_periodic_session_proto_id); if (rc != NGX_OK) { ngx_stream_js_periodic_destroy(s, periodic); @@ -1900,8 +1966,8 @@ ngx_stream_js_periodic_handler(ngx_event s->received++; - rc = ngx_js_invoke(ctx->vm, &periodic->method, &periodic->log, NULL, 0, - &ctx->retval); + rc = ngx_js_invoke(ctx->vm, &periodic->method, &periodic->log, + &ctx->args[0], 1, &ctx->retval); if (rc == NGX_AGAIN) { rc = NGX_OK; diff -r e3c442561889 -r 0f1e76ab9d45 nginx/t/js_periodic.t --- a/nginx/t/js_periodic.t Tue Sep 05 09:17:10 2023 -0700 +++ b/nginx/t/js_periodic.t Tue Sep 05 18:15:14 2023 -0700 @@ -45,6 +45,12 @@ http { js_shared_dict_zone zone=strings:32k; js_shared_dict_zone zone=workers:32k type=number; + js_set $js_set test.js_set; + js_var $js_var JS-VAR; + map _ $map_var { + default "MAP-VAR"; + } + server { listen 127.0.0.1:8080; server_name localhost; @@ -57,6 +63,7 @@ http { js_periodic test.fetch interval=40ms; js_periodic test.multiple_fetches interval=1s; js_periodic test.affinity interval=50ms worker_affinity=0101; + js_periodic test.vars interval=10s; js_periodic test.fetch_exception interval=1s; js_periodic test.tick_exception interval=1s; @@ -99,6 +106,10 @@ http { location /test_timeout_exception { js_content test.test_timeout_exception; } + + location /test_vars { + js_content test.test_vars; + } } } @@ -121,6 +132,10 @@ my $p0 = port(8080); ngx.shared.strings.set('fetch', v + body); } + function js_set() { + return 'JS-SET'; + } + async function multiple_fetches() { let reply = await ngx.fetch('http://127.0.0.1:$p0/fetch_ok'); let reply2 = await ngx.fetch('http://127.0.0.1:$p0/fetch_foo'); @@ -181,6 +196,12 @@ my $p0 = port(8080); }, 1); } + function vars(s) { + var v = s.variables; + ngx.shared.strings.set('vars', + `\${v.js_var}|\${v.js_set}|\${v.map_var}`); + } + function test_affinity(r) { r.return(200, `[\${ngx.shared.workers.keys().toSorted()}]`); } @@ -211,14 +232,19 @@ my $p0 = port(8080); r.return(200, ngx.shared.nums.get('timeout_exception') >= 2); } - export default { affinity, fetch, fetch_exception, file, multiple_fetches, - overrun, test_affinity, test_fetch, test_file, - test_multiple_fetches, test_tick, test_timeout_exception, - test_timer, tick, tick_exception, timer, timer_exception, + function test_vars(r) { + r.return(200, ngx.shared.strings.get('vars')); + } + + export default { affinity, fetch, fetch_exception, file, js_set, + multiple_fetches, overrun, vars, test_affinity, test_fetch, + test_file, test_multiple_fetches, test_tick, + test_timeout_exception, test_timer, test_vars, tick, + tick_exception, timer, timer_exception, timeout_exception }; EOF -$t->try_run('no js_periodic')->plan(8); +$t->try_run('no js_periodic')->plan(9); ############################################################################### @@ -232,6 +258,7 @@ like(http_get('/test_fetch'), qr/true/, like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); like(http_get('/test_timeout_exception'), qr/true/, 'timeout exception test'); +like(http_get('/test_vars'), qr/JS-VAR\|JS-SET\|MAP-VAR/, 'vars test'); $t->stop(); diff -r e3c442561889 -r 0f1e76ab9d45 nginx/t/stream_js_periodic.t --- a/nginx/t/stream_js_periodic.t Tue Sep 05 09:17:10 2023 -0700 +++ b/nginx/t/stream_js_periodic.t Tue Sep 05 18:15:14 2023 -0700 @@ -46,6 +46,12 @@ stream { js_shared_dict_zone zone=strings:32k; js_shared_dict_zone zone=workers:32k type=number; + js_set $js_set test.js_set; + js_var $js_var JS-VAR; + map _ $map_var { + default "MAP-VAR"; + } + server { listen 127.0.0.1:8080; @@ -56,6 +62,7 @@ stream { js_periodic test.fetch interval=40ms; js_periodic test.multiple_fetches interval=1s; js_periodic test.affinity interval=50ms worker_affinity=0101; + js_periodic test.vars interval=10s; js_periodic test.fetch_exception interval=1s; js_periodic test.tick_exception interval=1s; @@ -108,6 +115,10 @@ my $p1 = port(8081); let reply = await ngx.fetch('garbage'); } + function js_set() { + return 'JS-SET'; + } + async function multiple_fetches() { let reply = await ngx.fetch('http://127.0.0.1:$p1/fetch_ok'); let reply2 = await ngx.fetch('http://127.0.0.1:$p1/fetch_foo'); @@ -163,6 +174,12 @@ my $p1 = port(8081); }, 1); } + function vars(s) { + var v = s.variables; + ngx.shared.strings.set('vars', + `\${v.js_var}|\${v.js_set}|\${v.map_var}`); + } + function test(s) { s.on('upload', function (data) { if (data.length > 0) { @@ -229,6 +246,15 @@ my $p1 = port(8081); break; + case 'vars': + var vars = ngx.shared.strings.get('vars'); + if (vars === 'JS-VAR|JS-SET|MAP-VAR') { + s.done(); + return; + } + + break; + default: throw new Error(`Unknown test "\${data}"`); } @@ -238,13 +264,13 @@ my $p1 = port(8081); }); } - export default { affinity, fetch, fetch_exception, multiple_fetches, file, - overrun, test, tick, tick_exception, timer, - timer_exception, timeout_exception }; + export default { affinity, fetch, fetch_exception, js_set, multiple_fetches, + file, overrun, test, tick, tick_exception, timer, + timer_exception, timeout_exception, vars }; EOF $t->run_daemon(\&stream_daemon, port(8090)); -$t->try_run('no js_periodic')->plan(8); +$t->try_run('no js_periodic')->plan(9); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### @@ -261,6 +287,7 @@ is(stream('127.0.0.1:' . port(8080))->io 'multiple_fetches', 'muliple fetches test'); is(stream('127.0.0.1:' . port(8080))->io('timeout_exception'), 'timeout_exception', 'timeout exception test'); +is(stream('127.0.0.1:' . port(8080))->io('vars'), 'vars', 'vars test'); $t->stop(); diff -r e3c442561889 -r 0f1e76ab9d45 ts/ngx_http_js_module.d.ts --- a/ts/ngx_http_js_module.d.ts Tue Sep 05 09:17:10 2023 -0700 +++ b/ts/ngx_http_js_module.d.ts Tue Sep 05 18:15:14 2023 -0700 @@ -479,3 +479,25 @@ interface NginxHTTPRequest { */ warn(message: NjsStringOrBuffer): void; } + + +/** + * NginxPeriodicSession object is available as the first argument in the js_periodic handler. + * @since 0.8.1 + */ +interface NginxPeriodicSession { + /** + * nginx variables as Buffers. + * + * @see variables + */ + readonly rawVariables: NginxRawVariables; + /** + * nginx variables as strings. + * + * **Warning:** Bytes invalid in UTF-8 encoding may be converted into the replacement character. + * + * @see rawVariables + */ + readonly variables: NginxVariables; +} diff -r e3c442561889 -r 0f1e76ab9d45 ts/ngx_stream_js_module.d.ts --- a/ts/ngx_stream_js_module.d.ts Tue Sep 05 09:17:10 2023 -0700 +++ b/ts/ngx_stream_js_module.d.ts Tue Sep 05 18:15:14 2023 -0700 @@ -211,3 +211,25 @@ interface NginxStreamRequest { */ warn(message: NjsStringOrBuffer): void; } + + +/** + * NginxPeriodicSession object is available as the first argument in the js_periodic handler. + * @since 0.8.1 + */ +interface NginxPeriodicSession { + /** + * nginx variables as Buffers. + * + * @see variables + */ + readonly rawVariables: NginxRawVariables; + /** + * nginx variables as strings. + * + * **Warning:** Bytes invalid in UTF-8 encoding may be converted into the replacement character. + * + * @see rawVariables + */ + readonly variables: NginxVariables; +} From xeioex at nginx.com Thu Sep 7 01:03:42 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 07 Sep 2023 01:03:42 +0000 Subject: [njs] Shell: fixed file error message on CLI without interactive mode. Message-ID: details: https://hg.nginx.org/njs/rev/1f0adb4b81da branches: changeset: 2192:1f0adb4b81da user: Dmitry Volyntsev date: Wed Sep 06 18:02:50 2023 -0700 description: Shell: fixed file error message on CLI without interactive mode. This closes #669 issue on Github. diffstat: external/njs_shell.c | 16 ++++++++++++++-- 1 files changed, 14 insertions(+), 2 deletions(-) diffs (26 lines): diff -r 0f1e76ab9d45 -r 1f0adb4b81da external/njs_shell.c --- a/external/njs_shell.c Tue Sep 05 18:15:14 2023 -0700 +++ b/external/njs_shell.c Wed Sep 06 18:02:50 2023 -0700 @@ -311,8 +311,20 @@ main(int argc, char **argv) njs_vm_opt_init(&vm_options); if (opts.file == NULL) { - opts.file = (opts.command == NULL) ? (char *) "shell" - : (char *) "string"; + if (opts.command != NULL) { + opts.file = (char *) "string"; + } + +#ifdef NJS_HAVE_READLINE + else if (opts.interactive) { + opts.file = (char *) "shell"; + } +#endif + + if (opts.file == NULL) { + njs_stderror("file name is required in non-interactive mode\n"); + goto done; + } } vm_options.file.start = (u_char *) opts.file; From pl080516 at gmail.com Thu Sep 7 08:49:34 2023 From: pl080516 at gmail.com (Yu Zhu) Date: Thu, 7 Sep 2023 16:49:34 +0800 Subject: keep a reference of ngx_conneciton_t to the ngx_quic_path_t? Message-ID: Hi Some function declarations contain ngx_connection_t and quic_quic_path_t: 1. ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); 2. ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) 3. void ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path) 4. static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path); 5. static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path) 6. static ngx_int_t ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, ngx_quic_path_t *path) 7. static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); 8. ... and others... Is it possible to keep a reference of ngx_connection_t to the ngx_quic_path_t? Best regards Yu Zhu -------------- next part -------------- An HTML attachment was scrubbed... URL: From arut at nginx.com Thu Sep 7 13:03:22 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 07 Sep 2023 17:03:22 +0400 Subject: [PATCH 0 of 2] QUIC module compatibility issues Message-ID: QUIC module compatibility issues reported in ticket #2539 From arut at nginx.com Thu Sep 7 13:03:23 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 07 Sep 2023 17:03:23 +0400 Subject: [PATCH 1 of 2] HTTP/3: eliminated v3_session field from ngx_http_connection_t In-Reply-To: References: Message-ID: <4e312ff4b6ba742a2708.1694091803@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694088991 -14400 # Thu Sep 07 16:16:31 2023 +0400 # Node ID 4e312ff4b6ba742a270864b9c6ad7d0484355a7b # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa HTTP/3: eliminated v3_session field from ngx_http_connection_t. The field was under NGX_HTTP_V3 macro, which was a source of binary compatibility problems when nginx/module is build with/without HTTP/3 support. Now ngx_http_connection_t is copied to the first field of ngx_http_v3_session_t, which is assigned to c->data. A better solution would be to reference it instead of copying, similar to the way ngx_http_v2_connection_t and ngx_http_request_t do. However, TLS handshake callbacks such as ngx_http_ssl_servername() and ngx_http_ssl_alpn_select() expect c->data to reference ngx_http_connection_t. 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 @@ -324,10 +324,6 @@ 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; 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,6 +30,8 @@ ngx_http_v3_init_session(ngx_connection_ goto failed; } + h3c->http_connection = *hc; + ngx_queue_init(&h3c->blocked); h3c->keepalive.log = c->log; @@ -48,7 +50,7 @@ ngx_http_v3_init_session(ngx_connection_ cln->handler = ngx_http_v3_cleanup_session; cln->data = h3c; - hc->v3_session = h3c; + c->data = h3c; return NGX_OK; 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 @@ -82,7 +82,8 @@ ((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_session(c) \ + ((ngx_http_v3_session_t *) ngx_http_quic_get_connection(c)) #define ngx_http_v3_get_module_loc_conf(c, module) \ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ @@ -120,6 +121,8 @@ struct ngx_http_v3_parse_s { struct ngx_http_v3_session_s { + ngx_http_connection_t http_connection; + ngx_http_v3_dynamic_table_t table; ngx_event_t keepalive; 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 @@ -78,7 +78,7 @@ ngx_http_v3_init_stream(ngx_connection_t return; } - h3c = hc->v3_session; + h3c = ngx_http_v3_get_session(c); ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); h3scf->quic.timeout = clcf->keepalive_timeout; From arut at nginx.com Thu Sep 7 13:03:24 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 07 Sep 2023 17:03:24 +0400 Subject: [PATCH 2 of 2] Modules compatibility: added QUIC to signature (ticket #2539) In-Reply-To: References: Message-ID: <5fc61f98315ec462ef0f.1694091804@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694088909 -14400 # Thu Sep 07 16:15:09 2023 +0400 # Node ID 5fc61f98315ec462ef0ff7f7b4bec03109ccc0eb # Parent 4e312ff4b6ba742a270864b9c6ad7d0484355a7b Modules compatibility: added QUIC to signature (ticket #2539). Enabling QUIC changes ngx_connection_t layout, which is why it should be added to the signature. diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -191,12 +191,18 @@ #define NGX_MODULE_SIGNATURE_33 "0" #endif -#if (NGX_COMPAT) +#if (NGX_QUIC || NGX_COMPAT) #define NGX_MODULE_SIGNATURE_34 "1" #else #define NGX_MODULE_SIGNATURE_34 "0" #endif +#if (NGX_COMPAT) +#define NGX_MODULE_SIGNATURE_35 "1" +#else +#define NGX_MODULE_SIGNATURE_35 "0" +#endif + #define NGX_MODULE_SIGNATURE \ NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2 \ NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5 \ @@ -209,7 +215,7 @@ NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26 \ NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29 \ NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32 \ - NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 + NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 NGX_MODULE_SIGNATURE_35 #define NGX_MODULE_V1 \ From pluknet at nginx.com Thu Sep 7 15:13:52 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:52 +0400 Subject: [PATCH 0 of 8] [quic] reusing crypto contexts, and more Message-ID: This series adds reuse of crypto contexts, it should improve performance. Also, it clears out keying material where possible. Minor bugs fixed on the way. From pluknet at nginx.com Thu Sep 7 15:13:53 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:53 +0400 Subject: [PATCH 1 of 8] QUIC: split keys availability checks to read and write sides In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1693497250 -14400 # Thu Aug 31 19:54:10 2023 +0400 # Node ID be1862a28fd8575a88475215ccfce995e392dfab # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa QUIC: split keys availability checks to read and write sides. Keys may be released by TLS stack in different time, so it makes sense to check this independently as well. This allows to fine-tune what key direction is used when checking keys availability. When discarding, server keys are now marked in addition to client keys. 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 @@ -524,7 +524,7 @@ ngx_quic_close_connection(ngx_connection for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ctx = &qc->send_ctx[i]; - if (!ngx_quic_keys_available(qc->keys, ctx->level)) { + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { continue; } @@ -953,7 +953,7 @@ ngx_quic_handle_payload(ngx_connection_t c->log->action = "decrypting packet"; - if (!ngx_quic_keys_available(qc->keys, pkt->level)) { + if (!ngx_quic_keys_available(qc->keys, pkt->level, 0)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic no %s keys, ignoring packet", ngx_quic_level_name(pkt->level)); @@ -1076,7 +1076,9 @@ ngx_quic_discard_ctx(ngx_connection_t *c qc = ngx_quic_get_connection(c); - if (!ngx_quic_keys_available(qc->keys, level)) { + if (!ngx_quic_keys_available(qc->keys, level, 0) + && !ngx_quic_keys_available(qc->keys, level, 1)) + { return; } diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -672,9 +672,13 @@ ngx_quic_keys_set_encryption_secret(ngx_ ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level) + enum ssl_encryption_level_t level, ngx_uint_t is_write) { - return keys->secrets[level].client.key.len != 0; + if (is_write == 0) { + return keys->secrets[level].client.key.len != 0; + } + + return keys->secrets[level].server.key.len != 0; } @@ -683,6 +687,7 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k enum ssl_encryption_level_t level) { keys->secrets[level].client.key.len = 0; + keys->secrets[level].server.key.len = 0; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -95,7 +95,7 @@ ngx_int_t ngx_quic_keys_set_encryption_s 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); + enum ssl_encryption_level_t level, ngx_uint_t is_write); 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); 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 @@ -434,7 +434,7 @@ ngx_quic_crypto_input(ngx_connection_t * } if (n <= 0 || SSL_in_init(ssl_conn)) { - if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data) + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data, 0) && qc->client_tp_done) { if (ngx_quic_init_streams(c) != NGX_OK) { From pluknet at nginx.com Thu Sep 7 15:13:54 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:54 +0400 Subject: [PATCH 2 of 8] QUIC: added check to prevent packet output with discarded keys In-Reply-To: References: Message-ID: <02c86eac80c907adb779.1694099634@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1694041352 -14400 # Thu Sep 07 03:02:32 2023 +0400 # Node ID 02c86eac80c907adb7790c79ac6892afabcee5f4 # Parent be1862a28fd8575a88475215ccfce995e392dfab QUIC: added check to prevent packet output with discarded keys. In addition to triggering alert, it ensures that such packets won't be sent. With the previous change that marks server keys as discarded by zeroing the key lengh, it is now an error to send packets with discarded keys. OpenSSL based stacks tolerate such behaviour because key length isn't used in packet protection, but BoringSSL will raise the UNSUPPORTED_KEY_SIZE cipher error. It won't be possible to use discarded keys with reused crypto contexts. 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 @@ -519,6 +519,21 @@ ngx_quic_output_packet(ngx_connection_t qc = ngx_quic_get_connection(c); + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "quic %s write keys discarded", + ngx_quic_level_name(ctx->level)); + + 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); + } + + return 0; + } + ngx_quic_init_packet(c, ctx, &pkt, qc->path); min_payload = ngx_quic_payload_size(&pkt, min); From pluknet at nginx.com Thu Sep 7 15:13:55 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:55 +0400 Subject: [PATCH 3 of 8] QUIC: prevented output of ACK frame when discarding handshake keys In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1694041354 -14400 # Thu Sep 07 03:02:34 2023 +0400 # Node ID b05feba278a8b766cddd4cc35d73ff43e8d77092 # Parent 02c86eac80c907adb7790c79ac6892afabcee5f4 QUIC: prevented output of ACK frame when discarding handshake keys. Previously, ACK frames were generated to acknowledge the Handshake packet that carries remaining CRYPTO payload used to complete the TLS handshake. At the server, when the handshake is complete, it is considered confirmed, and to meet the RFC 9001 specification, handshake keys must be discarded. This includes cleaning up all stale frames from the send queue that might be generated prior to discarding the keys, but this doesn't apply to ACK frames: ACK generation happens late in packet processing, after the keys could have been discarded, so this needs a special treatment. 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 @@ -44,7 +44,7 @@ static int ngx_quic_flush_flight(ngx_ssl 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, - enum ssl_encryption_level_t level); + ngx_quic_header_t *pkt); #if (NGX_QUIC_BORINGSSL_API) @@ -355,7 +355,7 @@ ngx_quic_handle_crypto_frame(ngx_connect } if (f->offset == ctx->crypto.offset) { - if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { + if (ngx_quic_crypto_input(c, frame->data, pkt) != NGX_OK) { return NGX_ERROR; } @@ -373,7 +373,7 @@ ngx_quic_handle_crypto_frame(ngx_connect cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); if (cl) { - if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { + if (ngx_quic_crypto_input(c, cl, pkt) != NGX_OK) { return NGX_ERROR; } @@ -386,7 +386,7 @@ ngx_quic_handle_crypto_frame(ngx_connect static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, - enum ssl_encryption_level_t level) + ngx_quic_header_t *pkt) { int n, sslerr; ngx_buf_t *b; @@ -402,7 +402,9 @@ ngx_quic_crypto_input(ngx_connection_t * for (cl = data; cl; cl = cl->next) { b = cl->buf; - if (!SSL_provide_quic_data(ssl_conn, level, b->pos, b->last - b->pos)) { + if (!SSL_provide_quic_data(ssl_conn, pkt->level, + b->pos, b->last - b->pos)) + { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "SSL_provide_quic_data() failed"); return NGX_ERROR; @@ -482,6 +484,9 @@ ngx_quic_crypto_input(ngx_connection_t * */ ngx_quic_discard_ctx(c, ssl_encryption_handshake); + /* no need to ack the Handshake packet that brought us there */ + pkt->need_ack = 0; + ngx_quic_discover_path_mtu(c, qc->path); /* start accepting clients on negotiated number of server ids */ From pluknet at nginx.com Thu Sep 7 15:13:56 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:56 +0400 Subject: [PATCH 4 of 8] QUIC: renamed protection functions In-Reply-To: References: Message-ID: <24e5d652ecc861f0c686.1694099636@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1694099421 -14400 # Thu Sep 07 19:10:21 2023 +0400 # Node ID 24e5d652ecc861f0c68607d20941abbf3726fdf1 # Parent b05feba278a8b766cddd4cc35d73ff43e8d77092 QUIC: renamed protection functions. Now these functions have names ngx_quic_crypto_XXX(): - ngx_quic_tls_open() -> ngx_quic_crypto_open() - ngx_quic_tls_seal() -> ngx_quic_crypto_seal() - ngx_quic_tls_hp() -> ngx_quic_crypto_hp() 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 @@ -568,8 +568,8 @@ ngx_quic_compat_create_record(ngx_quic_c 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) + if (ngx_quic_crypto_seal(ciphers.c, secret, &out, + nonce, &rec->payload, &ad, rec->log) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -26,10 +26,10 @@ static ngx_int_t ngx_hkdf_extract(u_char 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, +static ngx_int_t ngx_quic_crypto_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, +static ngx_int_t ngx_quic_crypto_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, @@ -344,7 +344,7 @@ failed: static ngx_int_t -ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, +ngx_quic_crypto_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) { @@ -449,7 +449,7 @@ ngx_quic_tls_open(const ngx_quic_cipher_ ngx_int_t -ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, +ngx_quic_crypto_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) { @@ -565,7 +565,7 @@ ngx_quic_tls_seal(const ngx_quic_cipher_ static ngx_int_t -ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, +ngx_quic_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in) { int outlen; @@ -801,15 +801,15 @@ ngx_quic_create_packet(ngx_quic_header_t 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) + if (ngx_quic_crypto_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) + if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { return NGX_ERROR; @@ -862,7 +862,8 @@ ngx_quic_create_retry_packet(ngx_quic_he 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) + if (ngx_quic_crypto_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, + pkt->log) != NGX_OK) { return NGX_ERROR; @@ -1032,7 +1033,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, /* header protection */ - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) + if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { return NGX_DECLINED; @@ -1087,8 +1088,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, pkt->payload.len = in.len - NGX_QUIC_TAG_LEN; pkt->payload.data = pkt->plaintext + ad.len; - rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, - nonce, &in, &ad, pkt->log); + rc = ngx_quic_crypto_open(ciphers.c, secret, &pkt->payload, + nonce, &in, &ad, pkt->log); if (rc != NGX_OK) { return NGX_DECLINED; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -105,7 +105,7 @@ ngx_int_t ngx_quic_decrypt(ngx_quic_head 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_int_t ngx_quic_crypto_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, From pluknet at nginx.com Thu Sep 7 15:13:57 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:57 +0400 Subject: [PATCH 5 of 8] QUIC: reusing crypto contexts for packet protection In-Reply-To: References: Message-ID: <28f7491bc79771f9cfa8.1694099637@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1694099424 -14400 # Thu Sep 07 19:10:24 2023 +0400 # Node ID 28f7491bc79771f9cfa882b1b5584fa48ea42e6b # Parent 24e5d652ecc861f0c68607d20941abbf3726fdf1 QUIC: reusing crypto contexts for packet protection. 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 @@ -225,6 +225,7 @@ ngx_quic_new_connection(ngx_connection_t { ngx_uint_t i; ngx_quic_tp_t *ctp; + ngx_pool_cleanup_t *cln; ngx_quic_connection_t *qc; qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); @@ -237,6 +238,14 @@ ngx_quic_new_connection(ngx_connection_t return NULL; } + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_quic_keys_cleanup; + cln->data = qc->keys; + qc->version = pkt->version; ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, 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 @@ -54,9 +54,10 @@ struct ngx_quic_compat_s { 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, +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, 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 void ngx_quic_compat_cleanup_encryption_secret(void *data); 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); @@ -214,14 +215,14 @@ ngx_quic_compat_keylog_callback(const SS com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); com->read_record = 0; - (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level, + (void) ngx_quic_compat_set_encryption_secret(c, &com->keys, level, cipher, secret, n); } } static ngx_int_t -ngx_quic_compat_set_encryption_secret(ngx_log_t *log, +ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) { @@ -231,6 +232,7 @@ ngx_quic_compat_set_encryption_secret(ng ngx_quic_hkdf_t seq[2]; ngx_quic_secret_t *peer_secret; ngx_quic_ciphers_t ciphers; + ngx_pool_cleanup_t *cln; peer_secret = &keys->secret; @@ -239,12 +241,12 @@ ngx_quic_compat_set_encryption_secret(ng key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); if (key_len == NGX_ERROR) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); return NGX_ERROR; } if (sizeof(peer_secret->secret.data) < secret_len) { - ngx_log_error(NGX_LOG_ALERT, log, 0, + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "unexpected secret len: %uz", secret_len); return NGX_ERROR; } @@ -262,15 +264,42 @@ ngx_quic_compat_set_encryption_secret(ng 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) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { return NGX_ERROR; } } + ngx_quic_crypto_cleanup(peer_secret); + + if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) { + return NGX_ERROR; + } + + /* register cleanup handler once */ + + if (level == ssl_encryption_handshake) { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_quic_compat_cleanup_encryption_secret; + cln->data = peer_secret; + } + return NGX_OK; } +static void +ngx_quic_compat_cleanup_encryption_secret(void *data) +{ + ngx_quic_secret_t *secret = data; + + ngx_quic_crypto_cleanup(secret); +} + + 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, @@ -568,8 +597,7 @@ ngx_quic_compat_create_record(ngx_quic_c ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); - if (ngx_quic_crypto_seal(ciphers.c, secret, &out, - nonce, &rec->payload, &ad, rec->log) + if (ngx_quic_crypto_seal(secret, &out, nonce, &rec->payload, &ad, rec->log) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -26,9 +26,8 @@ static ngx_int_t ngx_hkdf_extract(u_char 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_crypto_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_crypto_open(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_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); @@ -108,13 +107,14 @@ 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; + 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; + ngx_quic_ciphers_t ciphers; static const uint8_t salt[20] = "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" @@ -180,6 +180,18 @@ ngx_quic_keys_set_initial_secret(ngx_qui } } + if (ngx_quic_ciphers(0, &ciphers, ssl_encryption_initial) == NGX_ERROR) { + return NGX_ERROR; + } + + if (ngx_quic_crypto_init(ciphers.c, client, 0, log) == NGX_ERROR) { + return NGX_ERROR; + } + + if (ngx_quic_crypto_init(ciphers.c, server, 1, log) == NGX_ERROR) { + return NGX_ERROR; + } + return NGX_OK; } @@ -343,9 +355,9 @@ failed: } -static ngx_int_t -ngx_quic_crypto_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) +ngx_int_t +ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_int_t enc, ngx_log_t *log) { #ifdef OPENSSL_IS_BORINGSSL @@ -357,19 +369,7 @@ ngx_quic_crypto_open(const ngx_quic_ciph 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; EVP_CIPHER_CTX *ctx; ctx = EVP_CIPHER_CTX_new(); @@ -378,114 +378,9 @@ ngx_quic_crypto_open(const ngx_quic_ciph 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; - } - - in->len -= NGX_QUIC_TAG_LEN; - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, - in->data + in->len) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); - return NGX_ERROR; - } - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_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_AEAD_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_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE - && EVP_DecryptUpdate(ctx, NULL, &len, NULL, in->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, 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) != 1) { + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc) != 1) { EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); - return NGX_ERROR; - } - - out->len = len; - - if (EVP_DecryptFinal_ex(ctx, out->data + out->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_crypto_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"); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); return NGX_ERROR; } @@ -509,28 +404,121 @@ ngx_quic_crypto_seal(const ngx_quic_ciph return NGX_ERROR; } - if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + if (EVP_CipherInit_ex(ctx, NULL, NULL, s->key.data, NULL, enc) != 1) { EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } +#endif + + s->ctx = ctx; + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_open(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_quic_crypto_ctx_t *ctx; + + ctx = s->ctx; + +#ifdef OPENSSL_IS_BORINGSSL + 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) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } +#else + int len; + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, nonce) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + in->len -= NGX_QUIC_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, + in->data + in->len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(ctx)) == EVP_CIPH_CCM_MODE + && EVP_DecryptUpdate(ctx, NULL, &len, NULL, in->len) != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + 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) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_DecryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_crypto_seal(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_quic_crypto_ctx_t *ctx; + + ctx = s->ctx; + +#ifdef OPENSSL_IS_BORINGSSL + 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) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } +#else + int len; + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, nonce) != 1) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); return NGX_ERROR; } - if (EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE + if (EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(ctx)) == EVP_CIPH_CCM_MODE && EVP_EncryptUpdate(ctx, NULL, &len, NULL, in->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, 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; } @@ -538,7 +526,6 @@ ngx_quic_crypto_seal(const ngx_quic_ciph 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; } @@ -549,21 +536,30 @@ ngx_quic_crypto_seal(const ngx_quic_ciph out->data + out->len) == 0) { - EVP_CIPHER_CTX_free(ctx); ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_GET_TAG) failed"); return NGX_ERROR; } out->len += NGX_QUIC_TAG_LEN; - - EVP_CIPHER_CTX_free(ctx); #endif return NGX_OK; } +void +ngx_quic_crypto_cleanup(ngx_quic_secret_t *s) +{ +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX_free(s->ctx); +#else + EVP_CIPHER_CTX_free(s->ctx); +#endif + s->ctx = NULL; +} + + static ngx_int_t ngx_quic_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in) @@ -666,6 +662,12 @@ ngx_quic_keys_set_encryption_secret(ngx_ } } + if (ngx_quic_crypto_init(ciphers.c, peer_secret, is_write, log) + == NGX_ERROR) + { + return NGX_ERROR; + } + return NGX_OK; } @@ -675,10 +677,10 @@ ngx_quic_keys_available(ngx_quic_keys_t enum ssl_encryption_level_t level, ngx_uint_t is_write) { if (is_write == 0) { - return keys->secrets[level].client.key.len != 0; + return keys->secrets[level].client.ctx != NULL; } - return keys->secrets[level].server.key.len != 0; + return keys->secrets[level].server.ctx != 0; } @@ -686,8 +688,13 @@ void ngx_quic_keys_discard(ngx_quic_keys_t *keys, enum ssl_encryption_level_t level) { - keys->secrets[level].client.key.len = 0; - keys->secrets[level].server.key.len = 0; + ngx_quic_secret_t *client, *server; + + client = &keys->secrets[level].client; + server = &keys->secrets[level].server; + + ngx_quic_crypto_cleanup(client); + ngx_quic_crypto_cleanup(server); } @@ -699,6 +706,9 @@ ngx_quic_keys_switch(ngx_connection_t *c current = &keys->secrets[ssl_encryption_application]; next = &keys->next_key; + ngx_quic_crypto_cleanup(¤t->client); + ngx_quic_crypto_cleanup(¤t->server); + tmp = *current; *current = *next; *next = tmp; @@ -762,6 +772,16 @@ ngx_quic_keys_update(ngx_event_t *ev) } } + if (ngx_quic_crypto_init(ciphers.c, &next->client, 0, c->log) == NGX_ERROR) + { + goto failed; + } + + if (ngx_quic_crypto_init(ciphers.c, &next->server, 1, c->log) == NGX_ERROR) + { + goto failed; + } + return; failed: @@ -770,6 +790,26 @@ failed: } +void +ngx_quic_keys_cleanup(void *data) +{ + ngx_quic_keys_t *keys = data; + + size_t i; + ngx_quic_secrets_t *secrets; + + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + secrets = &keys->secrets[i]; + ngx_quic_crypto_cleanup(&secrets->client); + ngx_quic_crypto_cleanup(&secrets->server); + } + + secrets = &keys->next_key; + ngx_quic_crypto_cleanup(&secrets->client); + ngx_quic_crypto_cleanup(&secrets->server); +} + + static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) { @@ -801,8 +841,7 @@ ngx_quic_create_packet(ngx_quic_header_t ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); - if (ngx_quic_crypto_seal(ciphers.c, secret, &out, - nonce, &pkt->payload, &ad, pkt->log) + if (ngx_quic_crypto_seal(secret, &out, nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) { return NGX_ERROR; @@ -862,13 +901,18 @@ ngx_quic_create_retry_packet(ngx_quic_he ngx_memcpy(secret.key.data, key, sizeof(key)); secret.iv.len = NGX_QUIC_IV_LEN; - if (ngx_quic_crypto_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, - pkt->log) + if (ngx_quic_crypto_init(ciphers.c, &secret, 1, pkt->log) == NGX_ERROR) { + return NGX_ERROR; + } + + if (ngx_quic_crypto_seal(&secret, &itag, nonce, &in, &ad, pkt->log) != NGX_OK) { return NGX_ERROR; } + ngx_quic_crypto_cleanup(&secret); + res->len = itag.data + itag.len - start; res->data = start; @@ -999,7 +1043,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, u_char *p, *sample; size_t len; uint64_t pn, lpn; - ngx_int_t pnl, rc; + ngx_int_t pnl; ngx_str_t in, ad; ngx_uint_t key_phase; ngx_quic_secret_t *secret; @@ -1088,9 +1132,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, pkt->payload.len = in.len - NGX_QUIC_TAG_LEN; pkt->payload.data = pkt->plaintext + ad.len; - rc = ngx_quic_crypto_open(ciphers.c, secret, &pkt->payload, - nonce, &in, &ad, pkt->log); - if (rc != NGX_OK) { + if (ngx_quic_crypto_open(secret, &pkt->payload, nonce, &in, &ad, pkt->log) + != NGX_OK) + { return NGX_DECLINED; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -26,8 +26,10 @@ #ifdef OPENSSL_IS_BORINGSSL #define ngx_quic_cipher_t EVP_AEAD +#define ngx_quic_crypto_ctx_t EVP_AEAD_CTX #else #define ngx_quic_cipher_t EVP_CIPHER +#define ngx_quic_crypto_ctx_t EVP_CIPHER_CTX #endif @@ -48,6 +50,7 @@ typedef struct { ngx_quic_md_t key; ngx_quic_iv_t iv; ngx_quic_md_t hp; + ngx_quic_crypto_ctx_t *ctx; } ngx_quic_secret_t; @@ -100,14 +103,17 @@ void ngx_quic_keys_discard(ngx_quic_keys enum ssl_encryption_level_t level); void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); void ngx_quic_keys_update(ngx_event_t *ev); +void ngx_quic_keys_cleanup(void *data); 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_crypto_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_crypto_init(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_int_t enc, ngx_log_t *log); +ngx_int_t ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s); ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, ngx_log_t *log); From pluknet at nginx.com Thu Sep 7 15:13:58 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:58 +0400 Subject: [PATCH 6 of 8] QUIC: reusing crypto contexts for header protection In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1694099424 -14400 # Thu Sep 07 19:10:24 2023 +0400 # Node ID cdc5b59309dbdc234c71e53fca142502884e6177 # Parent 28f7491bc79771f9cfa882b1b5584fa48ea42e6b QUIC: reusing crypto contexts for header protection. diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -28,8 +28,12 @@ static uint64_t ngx_quic_parse_pn(u_char static ngx_int_t ngx_quic_crypto_open(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_crypto_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_crypto_hp_init(const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, ngx_log_t *log); +static ngx_int_t ngx_quic_crypto_hp(ngx_quic_secret_t *s, + u_char *out, u_char *in, ngx_log_t *log); +static void ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s); static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res); @@ -192,6 +196,14 @@ ngx_quic_keys_set_initial_secret(ngx_qui return NGX_ERROR; } + if (ngx_quic_crypto_hp_init(ciphers.hp, client, log) == NGX_ERROR) { + return NGX_ERROR; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, server, log) == NGX_ERROR) { + return NGX_ERROR; + } + return NGX_OK; } @@ -561,53 +573,88 @@ ngx_quic_crypto_cleanup(ngx_quic_secret_ static ngx_int_t -ngx_quic_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, u_char *out, u_char *in) +ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, ngx_quic_secret_t *s, + ngx_log_t *log) { - 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); + if (cipher == (EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + /* some bogus value to distinguish ChaCha20 cipher */ + s->hp_ctx = (EVP_CIPHER_CTX *) cipher; return NGX_OK; } #endif 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, s->hp.data, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); return NGX_ERROR; } - if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { + s->hp_ctx = ctx; + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, + ngx_log_t *log) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + u_char zero[NGX_QUIC_HP_LEN] = {0}; + + ctx = s->hp_ctx; + +#ifdef OPENSSL_IS_BORINGSSL + uint32_t cnt; + + if (ctx == (EVP_CIPHER_CTX *) EVP_aead_chacha20_poly1305()) { + ngx_memcpy(&cnt, in, sizeof(uint32_t)); + CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); + return NGX_OK; + } +#endif + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, in) != 1) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); - goto failed; + return NGX_ERROR; } if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); - goto failed; + return NGX_ERROR; } 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; + return NGX_ERROR; } - EVP_CIPHER_CTX_free(ctx); - return NGX_OK; +} -failed: + +static void +ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s) +{ - EVP_CIPHER_CTX_free(ctx); +#ifdef OPENSSL_IS_BORINGSSL + if (s->hp_ctx == (EVP_CIPHER_CTX *) EVP_aead_chacha20_poly1305()) { + s->hp_ctx = NULL; + return; + } +#endif - return NGX_ERROR; + EVP_CIPHER_CTX_free(s->hp_ctx); + s->hp_ctx = NULL; } @@ -668,6 +715,10 @@ ngx_quic_keys_set_encryption_secret(ngx_ return NGX_ERROR; } + if (ngx_quic_crypto_hp_init(ciphers.hp, peer_secret, log) == NGX_ERROR) { + return NGX_ERROR; + } + return NGX_OK; } @@ -695,6 +746,9 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k ngx_quic_crypto_cleanup(client); ngx_quic_crypto_cleanup(server); + + ngx_quic_crypto_hp_cleanup(client); + ngx_quic_crypto_hp_cleanup(server); } @@ -747,11 +801,13 @@ ngx_quic_keys_update(ngx_event_t *ev) next->client.key.len = current->client.key.len; next->client.iv.len = NGX_QUIC_IV_LEN; next->client.hp = current->client.hp; + next->client.hp_ctx = current->client.hp_ctx; 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; + next->server.hp_ctx = current->server.hp_ctx; ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", &next->client.secret, ¤t->client.secret); @@ -802,6 +858,9 @@ ngx_quic_keys_cleanup(void *data) secrets = &keys->secrets[i]; ngx_quic_crypto_cleanup(&secrets->client); ngx_quic_crypto_cleanup(&secrets->server); + + ngx_quic_crypto_hp_cleanup(&secrets->client); + ngx_quic_crypto_hp_cleanup(&secrets->server); } secrets = &keys->next_key; @@ -848,9 +907,7 @@ ngx_quic_create_packet(ngx_quic_header_t } sample = &out.data[4 - pkt->num_len]; - if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) - != NGX_OK) - { + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { return NGX_ERROR; } @@ -1077,9 +1134,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, /* header protection */ - if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) - != NGX_OK) - { + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { return NGX_DECLINED; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -51,6 +51,7 @@ typedef struct { ngx_quic_iv_t iv; ngx_quic_md_t hp; ngx_quic_crypto_ctx_t *ctx; + EVP_CIPHER_CTX *hp_ctx; } ngx_quic_secret_t; From pluknet at nginx.com Thu Sep 7 15:13:59 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:13:59 +0400 Subject: [PATCH 7 of 8] QUIC: cleaned up now unused ngx_quic_ciphers() calls In-Reply-To: References: Message-ID: <8bd0104b7e6b658a1696.1694099639@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1694099425 -14400 # Thu Sep 07 19:10:25 2023 +0400 # Node ID 8bd0104b7e6b658a1696fe7f3e2f1868ac2ae1f9 # Parent cdc5b59309dbdc234c71e53fca142502884e6177 QUIC: cleaned up now unused ngx_quic_ciphers() calls. 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 @@ -571,10 +571,9 @@ ngx_quic_compat_create_header(ngx_quic_c 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]; + ngx_str_t ad, out; + ngx_quic_secret_t *secret; + u_char nonce[NGX_QUIC_IV_LEN]; ad.data = res->data; ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); @@ -587,11 +586,6 @@ ngx_quic_compat_create_record(ngx_quic_c "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); diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -872,12 +872,11 @@ ngx_quic_keys_cleanup(void *data) 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]; + u_char *pnp, *sample; + ngx_str_t ad, out; + ngx_uint_t i; + ngx_quic_secret_t *secret; + 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); @@ -890,11 +889,6 @@ ngx_quic_create_packet(ngx_quic_header_t "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); @@ -1097,20 +1091,14 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, 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; - 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; - } + u_char *p, *sample; + size_t len; + uint64_t pn, lpn; + ngx_int_t pnl; + ngx_str_t in, ad; + ngx_uint_t key_phase; + ngx_quic_secret_t *secret; + uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; secret = &pkt->keys->secrets[pkt->level].client; From pluknet at nginx.com Thu Sep 7 15:14:00 2023 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 07 Sep 2023 19:14:00 +0400 Subject: [PATCH 8 of 8] QUIC: explicitly zero out unused keying material In-Reply-To: References: Message-ID: <813128cee322830435a9.1694099640@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1694099425 -14400 # Thu Sep 07 19:10:25 2023 +0400 # Node ID 813128cee322830435a95903993b17fb24683da7 # Parent 8bd0104b7e6b658a1696fe7f3e2f1868ac2ae1f9 QUIC: explicitly zero out unused keying material. 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 @@ -245,15 +245,6 @@ ngx_quic_compat_set_encryption_secret(ng return NGX_ERROR; } - if (sizeof(peer_secret->secret.data) < secret_len) { - ngx_log_error(NGX_LOG_ALERT, c->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; @@ -275,6 +266,9 @@ ngx_quic_compat_set_encryption_secret(ng return NGX_ERROR; } + ngx_explicit_memzero(secret_str.data, secret_str.len); + ngx_explicit_memzero(peer_secret->key.data, peer_secret->key.len); + /* register cleanup handler once */ if (level == ssl_encryption_handshake) { diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -719,6 +719,8 @@ ngx_quic_keys_set_encryption_secret(ngx_ return NGX_ERROR; } + ngx_explicit_memzero(peer_secret->key.data, peer_secret->key.len); + return NGX_OK; } @@ -749,6 +751,12 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k ngx_quic_crypto_hp_cleanup(client); ngx_quic_crypto_hp_cleanup(server); + + ngx_explicit_memzero(client->secret.data, client->secret.len); + ngx_explicit_memzero(client->key.data, client->key.len); + + ngx_explicit_memzero(server->secret.data, server->secret.len); + ngx_explicit_memzero(server->key.data, server->key.len); } @@ -838,6 +846,14 @@ ngx_quic_keys_update(ngx_event_t *ev) goto failed; } + ngx_explicit_memzero(current->client.secret.data, + current->client.secret.len); + ngx_explicit_memzero(current->server.secret.data, + current->server.secret.len); + + ngx_explicit_memzero(next->client.key.data, next->client.key.len); + ngx_explicit_memzero(next->server.key.data, next->server.key.len); + return; failed: @@ -866,6 +882,12 @@ ngx_quic_keys_cleanup(void *data) secrets = &keys->next_key; ngx_quic_crypto_cleanup(&secrets->client); ngx_quic_crypto_cleanup(&secrets->server); + + ngx_explicit_memzero(secrets->client.secret.data, + secrets->client.secret.len); + + ngx_explicit_memzero(secrets->server.secret.data, + secrets->server.secret.len); } From xeioex at nginx.com Thu Sep 7 23:13:03 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 07 Sep 2023 23:13:03 +0000 Subject: [njs] Tests: fixed incr() tests for a shared dictionary. Message-ID: details: https://hg.nginx.org/njs/rev/5b52293ad769 branches: changeset: 2193:5b52293ad769 user: Dmitry Volyntsev date: Thu Sep 07 16:12:31 2023 -0700 description: Tests: fixed incr() tests for a shared dictionary. Previously, the incr() method called SharedDict.incr() with a NaN value as a second argument because parseInt(undefined) returns NaN. The test itself failed to capture the issue because it matches the whole HTTP response and the pattern itself was too short, so it matched accidentally. diffstat: nginx/t/js_shared_dict.t | 11 ++++++----- 1 files changed, 6 insertions(+), 5 deletions(-) diffs (28 lines): diff -r 1f0adb4b81da -r 5b52293ad769 nginx/t/js_shared_dict.t --- a/nginx/t/js_shared_dict.t Wed Sep 06 18:02:50 2023 -0700 +++ b/nginx/t/js_shared_dict.t Thu Sep 07 16:12:31 2023 -0700 @@ -190,8 +190,8 @@ EOF function incr(r) { var dict = ngx.shared[r.args.dict]; - var val = dict.incr(r.args.key, parseInt(r.args.by), - parseInt(r.args.def)); + var def = r.args.def ? parseInt(r.args.def) : 0; + var val = dict.incr(r.args.key, parseInt(r.args.by), def); r.return(200, val); } @@ -272,9 +272,10 @@ like(http_get('/set?dict=waka&key=FOO&va like(http_get('/chain?dict=bar&key=FOO2&value=aaa'), qr/aaa/, 'chain bar.FOO2'); like(http_get('/incr?dict=waka&key=FOO&by=5'), qr/47/, 'incr waka.FOO'); -like(http_get('/incr?dict=waka&key=FOO2&by=1'), qr/1/, 'incr waka.FOO2'); -like(http_get('/incr?dict=waka&key=FOO2&by=2'), qr/3/, 'incr waka.FOO2'); -like(http_get('/incr?dict=waka&key=FOO3&by=3&def=5'), qr/8/, 'incr waka.FOO3'); +like(http_get('/incr?dict=waka&key=FOO2&by=7777'), qr/7777/, 'incr waka.FOO2'); +like(http_get('/incr?dict=waka&key=FOO2&by=2'), qr/7779/, 'incr waka.FOO2'); +like(http_get('/incr?dict=waka&key=FOO3&by=3333&def=5'), qr/3338/, + 'incr waka.FOO3'); like(http_get('/has?dict=foo&key=FOO'), qr/true/, 'has foo.FOO'); like(http_get('/has?dict=foo&key=NOT_EXISTING'), qr/false/, From xeioex at nginx.com Thu Sep 7 23:13:05 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 07 Sep 2023 23:13:05 +0000 Subject: [njs] Modules: implemented items() method of a shared dictionary. Message-ID: details: https://hg.nginx.org/njs/rev/f5428bc87159 branches: changeset: 2194:f5428bc87159 user: Dmitry Volyntsev date: Thu Sep 07 16:12:40 2023 -0700 description: Modules: implemented items() method of a shared dictionary. diffstat: nginx/ngx_js_shared_dict.c | 120 +++++++++++++++++++++++++++++++++++++++++++++ nginx/t/js_shared_dict.t | 27 +++++++++- 2 files changed, 144 insertions(+), 3 deletions(-) diffs (204 lines): diff -r 5b52293ad769 -r f5428bc87159 nginx/ngx_js_shared_dict.c --- a/nginx/ngx_js_shared_dict.c Thu Sep 07 16:12:31 2023 -0700 +++ b/nginx/ngx_js_shared_dict.c Thu Sep 07 16:12:40 2023 -0700 @@ -64,6 +64,8 @@ static njs_int_t njs_js_ext_shared_dict_ njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_js_ext_shared_dict_incr(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_js_ext_shared_dict_items(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_js_ext_shared_dict_name(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); @@ -186,6 +188,17 @@ static njs_external_t ngx_js_ext_shared { .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("items"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_items, + } + }, + + { + .flags = NJS_EXTERN_METHOD, .name.string = njs_str("get"), .writable = 1, .configurable = 1, @@ -739,6 +752,113 @@ njs_js_ext_shared_dict_incr(njs_vm_t *vm static njs_int_t +njs_js_ext_shared_dict_items(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_int_t rc; + ngx_int_t max_count; + ngx_msec_t now; + ngx_time_t *tp; + njs_value_t *value, *kv; + ngx_rbtree_t *rbtree; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + ngx_rbtree_node_t *rn; + ngx_js_dict_node_t *node; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + dict = shm_zone->data; + + max_count = 1024; + + if (nargs > 1) { + if (ngx_js_integer(vm, njs_arg(args, nargs, 1), &max_count) != NGX_OK) { + return NJS_ERROR; + } + } + + rc = njs_vm_array_alloc(vm, retval, 8); + if (rc != NJS_OK) { + return NJS_ERROR; + } + + ngx_rwlock_rlock(&dict->sh->rwlock); + + if (dict->timeout) { + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + ngx_js_dict_expire(dict, now); + } + + rbtree = &dict->sh->rbtree; + + if (rbtree->root == rbtree->sentinel) { + goto done; + } + + for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel); + rn != NULL; + rn = ngx_rbtree_next(rbtree, rn)) + { + if (max_count-- == 0) { + break; + } + + node = (ngx_js_dict_node_t *) rn; + + kv = njs_vm_array_push(vm, retval); + if (kv == NULL) { + goto fail; + } + + rc = njs_vm_array_alloc(vm, kv, 2); + if (rc != NJS_OK) { + return NJS_ERROR; + } + + value = njs_vm_array_push(vm, kv); + if (value == NULL) { + goto fail; + } + + rc = njs_vm_value_string_set(vm, value, node->sn.str.data, + node->sn.str.len); + if (rc != NJS_OK) { + goto fail; + } + + value = njs_vm_array_push(vm, kv); + if (value == NULL) { + goto fail; + } + + rc = ngx_js_dict_copy_value_locked(vm, dict, node, value); + if (rc != NJS_OK) { + goto fail; + } + } + +done: + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return NJS_OK; + +fail: + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return NJS_ERROR; +} + + +static njs_int_t njs_js_ext_shared_dict_name(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { diff -r 5b52293ad769 -r f5428bc87159 nginx/t/js_shared_dict.t --- a/nginx/t/js_shared_dict.t Thu Sep 07 16:12:31 2023 -0700 +++ b/nginx/t/js_shared_dict.t Thu Sep 07 16:12:40 2023 -0700 @@ -86,6 +86,10 @@ http { js_content test.incr; } + location /items { + js_content test.items; + } + location /keys { js_content test.keys; } @@ -208,6 +212,19 @@ EOF r.return(200, `[${ks.toSorted()}]`); } + function items(r) { + var kvs; + + if (r.args.max) { + kvs = ngx.shared[r.args.dict].items(parseInt(r.args.max)); + + } else { + kvs = ngx.shared[r.args.dict].items(); + } + + r.return(200, njs.dump(kvs.toSorted())); + } + function name(r) { r.return(200, ngx.shared[r.args.dict].name); } @@ -247,11 +264,11 @@ EOF } export default { add, capacity, chain, clear, del, free_space, get, has, - incr, keys, name, njs: test_njs, pop, replace, set, size, - zones }; + incr, items, keys, name, njs: test_njs, pop, replace, set, + size, zones }; EOF -$t->try_run('no js_shared_dict_zone')->plan(41); +$t->try_run('no js_shared_dict_zone')->plan(43); ############################################################################### @@ -311,6 +328,10 @@ like(http_get('/keys?dict=foo'), qr/\[]/ like(http_get('/keys?dict=bar'), qr/\[FOO\,FOO2]/, 'bar keys after a delay'); like(http_get('/size?dict=foo'), qr/size: 0/, 'no of items in foo after expire'); +like(http_get('/items?dict=bar'), qr/\[\['FOO','zzz'],\['FOO2','aaa']]/, + 'bar items'); +like(http_get('/items?dict=waka'), + qr/\[\['FOO',47],\['FOO2',7779],\['FOO3',3338]]/, 'waka items'); } From v.zhestikov at f5.com Fri Sep 8 23:07:52 2023 From: v.zhestikov at f5.com (Vadim Zhestikov) Date: Fri, 08 Sep 2023 23:07:52 +0000 Subject: [njs] Added flat hash overflow check. Message-ID: details: https://hg.nginx.org/njs/rev/78c1ef3eeaa9 branches: changeset: 2195:78c1ef3eeaa9 user: Vadim Zhestikov date: Fri Sep 08 16:05:24 2023 -0700 description: Added flat hash overflow check. diffstat: src/njs_flathsh.c | 26 ++++++++++++++++---------- 1 files changed, 16 insertions(+), 10 deletions(-) diffs (63 lines): diff -r f5428bc87159 -r 78c1ef3eeaa9 src/njs_flathsh.c --- a/src/njs_flathsh.c Thu Sep 07 16:12:40 2023 -0700 +++ b/src/njs_flathsh.c Fri Sep 08 16:05:24 2023 -0700 @@ -87,7 +87,7 @@ struct njs_flathsh_descr_s { static njs_flathsh_descr_t *njs_flathsh_alloc(njs_flathsh_query_t *fhq, size_t hash_size, size_t elts_size); static njs_flathsh_descr_t *njs_expand_elts(njs_flathsh_query_t *fhq, - njs_flathsh_descr_t *h, uint32_t count); + njs_flathsh_descr_t *h); njs_inline size_t @@ -204,8 +204,8 @@ njs_flathsh_add_elt(njs_flathsh_t *fh, n return NULL; } - if (njs_slow_path(h->elts_count >= h->elts_size)) { - h = njs_expand_elts(fhq, fh->slot, h->elts_count + 1); + if (njs_slow_path(h->elts_count == h->elts_size)) { + h = njs_expand_elts(fhq, h); if (njs_slow_path(h == NULL)) { return NULL; } @@ -228,26 +228,32 @@ njs_flathsh_add_elt(njs_flathsh_t *fh, n static njs_flathsh_descr_t * -njs_expand_elts(njs_flathsh_query_t *fhq, njs_flathsh_descr_t *h, - uint32_t count) +njs_expand_elts(njs_flathsh_query_t *fhq, njs_flathsh_descr_t *h) { void *chunk; - size_t size; - uint32_t new_elts_size, new_hash_size, new_hash_mask, i; + size_t size, new_elts_size, new_hash_size; + uint32_t new_hash_mask, i; njs_int_t cell_num; njs_flathsh_elt_t *elt; njs_flathsh_descr_t *h_src; - new_elts_size = h->elts_size * NJS_FLATHSH_ELTS_EXPAND_FACTOR_NUM / + new_elts_size = h->elts_size * (size_t) NJS_FLATHSH_ELTS_EXPAND_FACTOR_NUM / NJS_FLATHSH_ELTS_EXPAND_FACTOR_DENOM; - new_elts_size = njs_max(count, new_elts_size); - new_hash_size = h->hash_mask + 1; + new_elts_size = njs_max(h->elts_count + 1ul, new_elts_size); + + new_hash_size = h->hash_mask + 1ul; while (new_hash_size < new_elts_size) { new_hash_size = 2 * new_hash_size; } + /* Overflow check. */ + + if (njs_slow_path(new_hash_size > UINT32_MAX)) { + return NULL; + } + if (new_hash_size != (h->hash_mask + 1)) { /* Expand both hash table cells and its elts. */ From xeioex at nginx.com Sat Sep 9 01:01:19 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 09 Sep 2023 01:01:19 +0000 Subject: [njs] Fetch: renamed prefix http to fetch to avoid overlap with HTTP module. Message-ID: details: https://hg.nginx.org/njs/rev/947f3b18dde4 branches: changeset: 2196:947f3b18dde4 user: Dmitry Volyntsev date: Fri Sep 08 17:52:07 2023 -0700 description: Fetch: renamed prefix http to fetch to avoid overlap with HTTP module. diffstat: nginx/ngx_js_fetch.c | 36 ++++++++++++++++++------------------ 1 files changed, 18 insertions(+), 18 deletions(-) diffs (149 lines): diff -r 78c1ef3eeaa9 -r 947f3b18dde4 nginx/ngx_js_fetch.c --- a/nginx/ngx_js_fetch.c Fri Sep 08 16:05:24 2023 -0700 +++ b/nginx/ngx_js_fetch.c Fri Sep 08 17:52:07 2023 -0700 @@ -1308,7 +1308,7 @@ ngx_js_http_alloc(njs_vm_t *vm, ngx_pool http->vm_event = vm_event; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js http alloc:%p", http); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js fetch alloc:%p", http); return http; @@ -1408,7 +1408,7 @@ static void ngx_js_http_close_connection(ngx_connection_t *c) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "close js http connection: %d", c->fd); + "js fetch close connection: %d", c->fd); #if (NGX_SSL) if (c->ssl) { @@ -1434,7 +1434,7 @@ njs_js_http_destructor(njs_external_ptr_ http = host; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http destructor:%p", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch destructor:%p", http); if (http->ctx != NULL) { @@ -1549,7 +1549,7 @@ ngx_js_http_connect(ngx_js_http_t *http) addr = &http->addrs[http->naddr]; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js http connect %ui/%ui", http->naddr, http->naddrs); + "js fetch connect %ui/%ui", http->naddr, http->naddrs); http->peer.sockaddr = addr->sockaddr; http->peer.socklen = addr->socklen; @@ -1605,7 +1605,8 @@ ngx_js_http_ssl_init_connection(ngx_js_h c = http->peer.connection; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js http secure connect %ui/%ui", http->naddr, http->naddrs); + "js fetch secure connect %ui/%ui", http->naddr, + http->naddrs); if (ngx_ssl_create_connection(http->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) != NGX_OK) @@ -1663,15 +1664,14 @@ ngx_js_http_ssl_handshake(ngx_js_http_t if (rc != X509_V_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js http fetch SSL certificate verify " - "error: (%l:%s)", rc, - X509_verify_cert_error_string(rc)); + "js fetch SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); goto failed; } if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js http SSL certificate does not match \"%V\"", + "js fetch SSL certificate does not match \"%V\"", &http->tls_name); goto failed; } @@ -1728,7 +1728,7 @@ ngx_js_http_ssl_name(ngx_js_http_t *http name->data = p; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js http SSL server name: \"%s\"", name->data); + "js fetch SSL server name: \"%s\"", name->data); if (SSL_set_tlsext_host_name(http->peer.connection->ssl->connection, (char *) name->data) @@ -1751,7 +1751,7 @@ done: static void ngx_js_http_next(ngx_js_http_t *http) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http next"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch next addr"); if (++http->naddr >= http->naddrs) { ngx_js_http_error(http, 0, "connect failed"); @@ -1780,7 +1780,7 @@ ngx_js_http_write_handler(ngx_event_t *w c = wev->data; http = c->data; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "js http write handler"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "js fetch write handler"); if (wev->timedout) { ngx_js_http_error(http, NGX_ETIMEDOUT, "write timed out"); @@ -1862,7 +1862,7 @@ ngx_js_http_read_handler(ngx_event_t *re c = rev->data; http = c->data; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "js http read handler"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "js fetch read handler"); if (rev->timedout) { ngx_js_http_error(http, NGX_ETIMEDOUT, "read timed out"); @@ -2340,7 +2340,7 @@ ngx_js_http_process_status_line(ngx_js_h rc = ngx_js_http_parse_status_line(hp, http->buffer); if (rc == NGX_OK) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http status %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch status %ui", hp->code); http->response.code = hp->code; @@ -2373,7 +2373,7 @@ ngx_js_http_process_headers(ngx_js_http_ ngx_js_http_parse_t *hp; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js http process headers"); + "js fetch process headers"); hp = &http->http_parse; @@ -2403,7 +2403,7 @@ ngx_js_http_process_headers(ngx_js_http_ } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js http header \"%*s: %*s\"", + "js fetch header \"%*s: %*s\"", len, hp->header_name_start, vlen, hp->header_start); if (len == njs_strlen("Transfer-Encoding") @@ -2473,7 +2473,7 @@ ngx_js_http_process_body(ngx_js_http_t * ngx_buf_t *b; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, - "js http process body done:%ui", (ngx_uint_t) http->done); + "js fetch process body done:%ui", (ngx_uint_t) http->done); if (http->done) { size = njs_chb_size(&http->response.chain); @@ -3156,7 +3156,7 @@ ngx_js_http_parse_chunked(ngx_js_http_ch static void ngx_js_http_dummy_handler(ngx_event_t *ev) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "js http dummy handler"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "js fetch dummy handler"); } From xeioex at nginx.com Sat Sep 9 01:01:21 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 09 Sep 2023 01:01:21 +0000 Subject: [njs] Modules: improved debug log. Message-ID: details: https://hg.nginx.org/njs/rev/1ac5ae749ae1 branches: changeset: 2197:1ac5ae749ae1 user: Dmitry Volyntsev date: Fri Sep 08 17:13:19 2023 -0700 description: Modules: improved debug log. 1) Ensuring that consistent prefixes are used: "http js" in HTTP module and "stream js" in Stream module. 2) Added debug for every event/callback handler entrance. 3) Added debug with a method name for every JS call. diffstat: nginx/ngx_http_js_module.c | 12 ++++++++++++ nginx/ngx_stream_js_module.c | 16 +++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diffs (97 lines): diff -r 947f3b18dde4 -r 1ac5ae749ae1 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Fri Sep 08 17:52:07 2023 -0700 +++ b/nginx/ngx_http_js_module.c Fri Sep 08 17:13:19 2023 -0700 @@ -1075,6 +1075,9 @@ ngx_http_js_header_filter(ngx_http_reque return ngx_http_next_header_filter(r); } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js header filter"); + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR || rc == NGX_DECLINED) { @@ -1086,6 +1089,9 @@ ngx_http_js_header_filter(ngx_http_reque ctx->filter = 1; pending = njs_vm_pending(ctx->vm); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js header call \"%V\"", &jlcf->header_filter); + rc = ngx_js_call(ctx->vm, &jlcf->header_filter, r->connection->log, &ctx->request, 1); @@ -1127,6 +1133,9 @@ ngx_http_js_body_filter(ngx_http_request return ngx_http_next_body_filter(r, in); } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js body filter"); + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR || rc == NGX_DECLINED) { @@ -1183,6 +1192,9 @@ ngx_http_js_body_filter(ngx_http_request pending = njs_vm_pending(ctx->vm); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http js body call \"%V\"", &jlcf->body_filter); + rc = ngx_js_call(ctx->vm, &jlcf->body_filter, c->log, &arguments[0], 3); diff -r 947f3b18dde4 -r 1ac5ae749ae1 nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Fri Sep 08 17:52:07 2023 -0700 +++ b/nginx/ngx_stream_js_module.c Fri Sep 08 17:13:19 2023 -0700 @@ -722,6 +722,9 @@ ngx_stream_js_phase_handler(ngx_stream_s return NGX_DECLINED; } + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream js phase handler"); + rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); if (rc != NGX_OK) { return rc; @@ -729,9 +732,6 @@ ngx_stream_js_phase_handler(ngx_stream_s c = s->connection; - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, - "stream js phase call \"%V\"", name); - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (!ctx->in_progress) { @@ -742,6 +742,9 @@ ngx_stream_js_phase_handler(ngx_stream_s ctx->status = NGX_ERROR; + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream js phase call \"%V\"", name); + rc = ngx_js_call(ctx->vm, name, c->log, &ctx->args[0], 1); if (rc == NGX_ERROR) { @@ -816,6 +819,9 @@ ngx_stream_js_body_filter(ngx_stream_ses ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (!ctx->filter) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream js filter call \"%V\"" , &jscf->filter); + rc = ngx_js_call(ctx->vm, &jscf->filter, c->log, &ctx->args[0], 1); if (rc == NGX_ERROR) { @@ -1899,8 +1905,8 @@ ngx_stream_js_periodic_handler(ngx_event if (c != NULL) { ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js periodic \"%V\" is already running, killing previous " - "instance", &periodic->method); + "stream js periodic \"%V\" is already running, killing " + "previous instance", &periodic->method); ngx_stream_js_periodic_finalize(c->data, NGX_ERROR); } From mdounin at mdounin.ru Sun Sep 10 19:11:09 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sun, 10 Sep 2023 22:11:09 +0300 Subject: [PATCH] Allowed nesting arbitrary prefix "location" in regex "location" In-Reply-To: References: Message-ID: Hello! On Mon, Sep 04, 2023 at 10:11:47PM +0300, Valentin V. Bartenev wrote: > # HG changeset patch > # User Valentin Bartenev > # Date 1693854233 -10800 > # Mon Sep 04 22:03:53 2023 +0300 > # Node ID c706913db63c6862c13a0a540cdc37be0ccf0c81 > # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa > Allowed nesting arbitrary prefix "location" in regex "location". > > Previously, only prefix "location" blocks that literally matched the beginning > of the regular expression were allowed inside. This restriction makes no sense > because regular expressions have different matching semantics. > > 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 > @@ -3202,6 +3202,7 @@ ngx_http_core_location(ngx_conf_t *cf, n > > #if (NGX_PCRE) > if (clcf->regex == NULL > + && pclcf->regex == NULL > && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0) > #else > if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0) Thank you for your patch. Locations given by regular expressions are expected to be used as leaf locations to match a small subset of the requests. Further, to make sure the configuration can be read and modified it is recommended to isolate regular expressions inside prefix locations, see here: https://www.youtube.com/watch?v=YWRYbLKsS0I&t=630 The condition in question is indeed somewhat misleading, but the practical effect is that it prevents usage of prefix locations within regex locations, with the exception of regex locations misused as prefix ones, such as in: location ~ /foo/ { location /foo/bar { ... } } While trying to create arbitrary prefix locations within a regex location might work, it will certainly complicate things a lot, making reading and modifying such configurations much harder. Consider: location / { location ~ \.php$ { ... } } location /foo/ { location ~ \.php$ { ... } } location ~ \.php$ { location /foo/ { ... } } Which location is to be used for "/foo/file.php" request? Within the existing logic, the answer is more or less obvious: the most specific configuration wins, so "location /foo/ { location ~ \.php$ { ... }}" will be used. But with the "location ~ \.php$ { location /foo/ { ... }}", as allowed with the patch in question, things become more complicated. Both "location /foo/ { location ~ \.php$ { ... }}" and "location ~ \.php$ { location /foo/ { ... }}" are equivalent, and there is no obvious answer. Given the above, I tend to think that it would be better to preserve existing behaviour. -- Maxim Dounin http://mdounin.ru/ From pluknet at nginx.com Mon Sep 11 11:11:35 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 11 Sep 2023 15:11:35 +0400 Subject: keep a reference of ngx_conneciton_t to the ngx_quic_path_t? In-Reply-To: References: Message-ID: <01ADFEC8-E8BE-45FE-ACC4-EAFF1DCDFB81@nginx.com> > On 7 Sep 2023, at 12:49, Yu Zhu wrote: > > Hi > > Some function declarations contain ngx_connection_t and quic_quic_path_t: > • ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); > • ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) > • void ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path) > • static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path); > • static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path) > • static ngx_int_t ngx_quic_expire_path_mtu_delay(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); > • ... and others... > Is it possible to keep a reference of ngx_connection_t to the ngx_quic_path_t? > An active path is kept in qc->path. There may be other paths accessible through the qc->paths queue. That's why such functions have a distinct path argument. -- Sergey Kandaurov From arut at nginx.com Mon Sep 11 11:30:29 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Mon, 11 Sep 2023 15:30:29 +0400 Subject: [PATCH 0 of 3] QUIC module compatibility issues In-Reply-To: < References: < Message-ID: First patch reworked. Now it's 2 patches instead of 1. The approach implemented is similar to what was done for ngx_http_v2_connection_t and ngx_http_request_t. From arut at nginx.com Mon Sep 11 11:30:30 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Mon, 11 Sep 2023 15:30:30 +0400 Subject: [PATCH 1 of 3] QUIC: "handshake_timeout" configuration parameter In-Reply-To: References: Message-ID: <65574b876c4047495ac8.1694431830@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694415935 -14400 # Mon Sep 11 11:05:35 2023 +0400 # Node ID 65574b876c4047495ac8622c6161fc87c1b75913 # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa QUIC: "handshake_timeout" configuration parameter. Previously QUIC did not have such parameter and handshake duration was controlled by HTTP/3. However that required creating and storing HTTP/3 session while processing the first client datagram. Apparently there's no convenient way to store the session object before QUIC handshake. In the following patch session creation will be postponed until first stream creation. 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 @@ -213,6 +213,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_qu ngx_add_timer(c->read, qc->tp.max_idle_timeout); ngx_quic_connstate_dbg(c); + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); + c->read->handler = ngx_quic_input_handler; return; @@ -521,6 +523,10 @@ ngx_quic_close_connection(ngx_connection qc->error_app ? "app " : "", qc->error, qc->error_reason ? qc->error_reason : ""); + if (qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ctx = &qc->send_ctx[i]; 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 @@ -67,7 +67,8 @@ typedef struct { ngx_flag_t retry; ngx_flag_t gso_enabled; ngx_flag_t disable_active_migration; - ngx_msec_t timeout; + ngx_msec_t handshake_timeout; + ngx_msec_t idle_timeout; ngx_str_t host_key; size_t stream_buffer_size; ngx_uint_t max_concurrent_streams_bidi; 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 @@ -630,6 +630,10 @@ ngx_quic_do_init_streams(ngx_connection_ qc->streams.initialized = 1; + if (!qc->closing && qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + return NGX_OK; } 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 @@ -1985,7 +1985,7 @@ ngx_quic_init_transport_params(ngx_quic_ * tp->preferred_address = NULL */ - tp->max_idle_timeout = qcf->timeout; + tp->max_idle_timeout = qcf->idle_timeout; tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; 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 @@ -192,7 +192,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * * h3scf->quic.host_key = { 0, NULL } * h3scf->quic.stream_reject_code_uni = 0; * h3scf->quic.disable_active_migration = 0; - * h3scf->quic.timeout = 0; + * h3scf->quic.idle_timeout = 0; * h3scf->max_blocked_streams = 0; */ @@ -223,7 +223,8 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c ngx_http_v3_srv_conf_t *prev = parent; ngx_http_v3_srv_conf_t *conf = child; - ngx_http_ssl_srv_conf_t *sscf; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; ngx_conf_merge_value(conf->enable, prev->enable, 1); @@ -281,6 +282,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c return NGX_CONF_ERROR; } + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); + conf->quic.handshake_timeout = cscf->client_header_timeout; + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); conf->quic.ssl = &sscf->ssl; 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 @@ -58,18 +58,15 @@ static const struct { 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) { @@ -78,10 +75,7 @@ ngx_http_v3_init_stream(ngx_connection_t return; } - h3c = hc->v3_session; - ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); - - h3scf->quic.timeout = clcf->keepalive_timeout; + h3scf->quic.idle_timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; } From arut at nginx.com Mon Sep 11 11:30:31 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Mon, 11 Sep 2023 15:30:31 +0400 Subject: [PATCH 2 of 3] HTTP/3: eliminated v3_session field from ngx_http_connection_t In-Reply-To: References: Message-ID: <9ee4158b9d3fa41e647b.1694431831@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694431436 -14400 # Mon Sep 11 15:23:56 2023 +0400 # Node ID 9ee4158b9d3fa41e647b772e707c29b3e4cb77b5 # Parent 65574b876c4047495ac8622c6161fc87c1b75913 HTTP/3: eliminated v3_session field from ngx_http_connection_t. The field was under NGX_HTTP_V3 macro, which was a source of binary compatibility problems when nginx/module is build with/without HTTP/3 support. Now ngx_http_v3_session_t is assigned to c->data on streams initialization, while ngx_http_connection_t object is referenced by http_connection field of the session object, similar to ngx_http_v2_connection_t and ngx_http_request_t. 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 @@ -324,10 +324,6 @@ 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; 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,6 +30,8 @@ ngx_http_v3_init_session(ngx_connection_ goto failed; } + h3c->http_connection = hc; + ngx_queue_init(&h3c->blocked); h3c->keepalive.log = c->log; @@ -48,7 +50,7 @@ ngx_http_v3_init_session(ngx_connection_ cln->handler = ngx_http_v3_cleanup_session; cln->data = h3c; - hc->v3_session = h3c; + c->data = h3c; return NGX_OK; 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 @@ -78,11 +78,12 @@ #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 \ +#define ngx_http_v3_get_session(c) \ + ((ngx_http_v3_session_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_quic_get_connection(c) \ + (ngx_http_v3_get_session(c)->http_connection) #define ngx_http_v3_get_module_loc_conf(c, module) \ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ @@ -120,6 +121,8 @@ struct ngx_http_v3_parse_s { struct ngx_http_v3_session_s { + ngx_http_connection_t *http_connection; + ngx_http_v3_dynamic_table_t table; ngx_event_t keepalive; 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 @@ -70,11 +70,6 @@ ngx_http_v3_init_stream(ngx_connection_t 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; - } - h3scf->quic.idle_timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; @@ -112,6 +107,10 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + 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); From arut at nginx.com Mon Sep 11 11:30:32 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Mon, 11 Sep 2023 15:30:32 +0400 Subject: [PATCH 3 of 3] Modules compatibility: added QUIC to signature (ticket #2539) In-Reply-To: References: Message-ID: <766e9a5771e20cdb3ec4.1694431832@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694088909 -14400 # Thu Sep 07 16:15:09 2023 +0400 # Node ID 766e9a5771e20cdb3ec41b8001b065ee299ec7ea # Parent 9ee4158b9d3fa41e647b772e707c29b3e4cb77b5 Modules compatibility: added QUIC to signature (ticket #2539). Enabling QUIC changes ngx_connection_t layout, which is why it should be added to the signature. diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -191,12 +191,18 @@ #define NGX_MODULE_SIGNATURE_33 "0" #endif -#if (NGX_COMPAT) +#if (NGX_QUIC || NGX_COMPAT) #define NGX_MODULE_SIGNATURE_34 "1" #else #define NGX_MODULE_SIGNATURE_34 "0" #endif +#if (NGX_COMPAT) +#define NGX_MODULE_SIGNATURE_35 "1" +#else +#define NGX_MODULE_SIGNATURE_35 "0" +#endif + #define NGX_MODULE_SIGNATURE \ NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2 \ NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5 \ @@ -209,7 +215,7 @@ NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26 \ NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29 \ NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32 \ - NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 + NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 NGX_MODULE_SIGNATURE_35 #define NGX_MODULE_V1 \ From xeioex at nginx.com Tue Sep 12 03:35:40 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 12 Sep 2023 03:35:40 +0000 Subject: [njs] HTTP: avoid calling body filter when input chain is NULL. Message-ID: details: https://hg.nginx.org/njs/rev/6b3176692593 branches: changeset: 2198:6b3176692593 user: Dmitry Volyntsev date: Mon Sep 11 20:34:48 2023 -0700 description: HTTP: avoid calling body filter when input chain is NULL. This solves the problem of erroneous exception thrown in r.internalRedirect() after 05c7f0b31856 (0.8.0). diffstat: nginx/ngx_http_js_module.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 1ac5ae749ae1 -r 6b3176692593 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Fri Sep 08 17:13:19 2023 -0700 +++ b/nginx/ngx_http_js_module.c Mon Sep 11 20:34:48 2023 -0700 @@ -1129,7 +1129,7 @@ ngx_http_js_body_filter(ngx_http_request jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - if (jlcf->body_filter.len == 0) { + if (jlcf->body_filter.len == 0 || in == NULL) { return ngx_http_next_body_filter(r, in); } From pluknet at nginx.com Tue Sep 12 13:04:59 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 12 Sep 2023 17:04:59 +0400 Subject: [PATCH 1 of 3] QUIC: "handshake_timeout" configuration parameter In-Reply-To: <65574b876c4047495ac8.1694431830@arut-laptop> References: <65574b876c4047495ac8.1694431830@arut-laptop> Message-ID: <6AACECB4-3BB7-47AD-973A-08197985B568@nginx.com> > On 11 Sep 2023, at 15:30, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1694415935 -14400 > # Mon Sep 11 11:05:35 2023 +0400 > # Node ID 65574b876c4047495ac8622c6161fc87c1b75913 > # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa > QUIC: "handshake_timeout" configuration parameter. > > Previously QUIC did not have such parameter and handshake duration was > controlled by HTTP/3. However that required creating and storing HTTP/3 > session while processing the first client datagram. Apparently there's s/while/before ? > no convenient way to store the session object before QUIC handshake. In > the following patch session creation will be postponed until first stream > creation. > > 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 > @@ -213,6 +213,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_qu > ngx_add_timer(c->read, qc->tp.max_idle_timeout); > ngx_quic_connstate_dbg(c); > > + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); > + It makes sense to cap handshake_timeout to idle timeout. I.e. ngx_min(qc->conf->handshake_timeout, qc->tp.max_idle_timeout); Or additionally handle this in ngx_quic_close_connection(), by removing the timer if set for the NGX_DONE case. Otherwise the connection would prolong unnecessarily on idle timeout for no purpose, because close timer set prevents closing a connection. As a (somewhat degenerate) ready example, see h3_keepalive.t TODO with too long connection close after the patch applied. The test uses "keepalive_timeout 0;" in the configuration. Also I would move setting close timer before calling ngx_quic_connstate_dbg() to reflect it's set in the debug log. > c->read->handler = ngx_quic_input_handler; > > return; > @@ -521,6 +523,10 @@ ngx_quic_close_connection(ngx_connection > qc->error_app ? "app " : "", qc->error, > qc->error_reason ? qc->error_reason : ""); > > + if (qc->close.timer_set) { > + ngx_del_timer(&qc->close); > + } > + Removing timer there makes the check below for the timer set unnecessary: : if (rc == NGX_OK && !qc->close.timer_set) { > for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > ctx = &qc->send_ctx[i]; > > 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 > @@ -67,7 +67,8 @@ typedef struct { > ngx_flag_t retry; > ngx_flag_t gso_enabled; > ngx_flag_t disable_active_migration; > - ngx_msec_t timeout; > + ngx_msec_t handshake_timeout; > + ngx_msec_t idle_timeout; > ngx_str_t host_key; > size_t stream_buffer_size; > ngx_uint_t max_concurrent_streams_bidi; > 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 > @@ -630,6 +630,10 @@ ngx_quic_do_init_streams(ngx_connection_ > > qc->streams.initialized = 1; > > + if (!qc->closing && qc->close.timer_set) { > + ngx_del_timer(&qc->close); > + } > + > return NGX_OK; > } > > 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 > @@ -1985,7 +1985,7 @@ ngx_quic_init_transport_params(ngx_quic_ > * tp->preferred_address = NULL > */ > > - tp->max_idle_timeout = qcf->timeout; > + tp->max_idle_timeout = qcf->idle_timeout; > > tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; > > 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 > @@ -192,7 +192,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * > * h3scf->quic.host_key = { 0, NULL } > * h3scf->quic.stream_reject_code_uni = 0; > * h3scf->quic.disable_active_migration = 0; > - * h3scf->quic.timeout = 0; > + * h3scf->quic.idle_timeout = 0; > * h3scf->max_blocked_streams = 0; > */ > > @@ -223,7 +223,8 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > ngx_http_v3_srv_conf_t *prev = parent; > ngx_http_v3_srv_conf_t *conf = child; > > - ngx_http_ssl_srv_conf_t *sscf; > + ngx_http_ssl_srv_conf_t *sscf; > + ngx_http_core_srv_conf_t *cscf; > > ngx_conf_merge_value(conf->enable, prev->enable, 1); > > @@ -281,6 +282,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > return NGX_CONF_ERROR; > } > > + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); > + conf->quic.handshake_timeout = cscf->client_header_timeout; > + > sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); > conf->quic.ssl = &sscf->ssl; > > 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 > @@ -58,18 +58,15 @@ static const struct { > 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); Not directly related, h3scf initialization can be moved under the below condition where it is only used. > > if (c->quic == NULL) { > @@ -78,10 +75,7 @@ ngx_http_v3_init_stream(ngx_connection_t > return; > } > > - h3c = hc->v3_session; > - ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); > - > - h3scf->quic.timeout = clcf->keepalive_timeout; > + h3scf->quic.idle_timeout = clcf->keepalive_timeout; > ngx_quic_run(c, &h3scf->quic); > return; > } -- Sergey Kandaurov From pluknet at nginx.com Tue Sep 12 13:05:02 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 12 Sep 2023 17:05:02 +0400 Subject: [PATCH 2 of 3] HTTP/3: eliminated v3_session field from ngx_http_connection_t In-Reply-To: <9ee4158b9d3fa41e647b.1694431831@arut-laptop> References: <9ee4158b9d3fa41e647b.1694431831@arut-laptop> Message-ID: <6CE6B9BE-EE11-444C-A35E-7815289A1157@nginx.com> > On 11 Sep 2023, at 15:30, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1694431436 -14400 > # Mon Sep 11 15:23:56 2023 +0400 > # Node ID 9ee4158b9d3fa41e647b772e707c29b3e4cb77b5 > # Parent 65574b876c4047495ac8622c6161fc87c1b75913 > HTTP/3: eliminated v3_session field from ngx_http_connection_t. > > The field was under NGX_HTTP_V3 macro, which was a source of binary > compatibility problems when nginx/module is build with/without HTTP/3 support. > > Now ngx_http_v3_session_t is assigned to c->data on streams initialization, > while ngx_http_connection_t object is referenced by http_connection field of > the session object, similar to ngx_http_v2_connection_t and ngx_http_request_t. I'd write explicitly (taking from the 1st patch commit log) that session creation is now postponed, to make the description self contained, YMMV. Note that this change will trigger segfaults on graceful shutdown if there are connections with handshake not yet complete, due to now postponed session creation. In this case, http3 shutdown() callback is called, used to send GOAWAY and proceed with connection close, but h3c is not yet. Probably calling the callback should depend on c->ssl->handshaked, because it has little sense if handshake isn't complete. > > 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 > @@ -324,10 +324,6 @@ 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; > > 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,6 +30,8 @@ ngx_http_v3_init_session(ngx_connection_ > goto failed; > } > > + h3c->http_connection = hc; > + > ngx_queue_init(&h3c->blocked); > > h3c->keepalive.log = c->log; > @@ -48,7 +50,7 @@ ngx_http_v3_init_session(ngx_connection_ > cln->handler = ngx_http_v3_cleanup_session; > cln->data = h3c; > > - hc->v3_session = h3c; > + c->data = h3c; > > return NGX_OK; > > 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 > @@ -78,11 +78,12 @@ > #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 \ > +#define ngx_http_v3_get_session(c) \ > + ((ngx_http_v3_session_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_quic_get_connection(c) \ > + (ngx_http_v3_get_session(c)->http_connection) > > #define ngx_http_v3_get_module_loc_conf(c, module) \ > ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > @@ -120,6 +121,8 @@ struct ngx_http_v3_parse_s { > > > struct ngx_http_v3_session_s { > + ngx_http_connection_t *http_connection; > + > ngx_http_v3_dynamic_table_t table; > > ngx_event_t keepalive; > 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 > @@ -70,11 +70,6 @@ ngx_http_v3_init_stream(ngx_connection_t > 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; > - } > - > h3scf->quic.idle_timeout = clcf->keepalive_timeout; > ngx_quic_run(c, &h3scf->quic); > return; > @@ -112,6 +107,10 @@ ngx_http_v3_init(ngx_connection_t *c) > > ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); > > + if (ngx_http_v3_init_session(c) != NGX_OK) { > + return NGX_ERROR; > + } > + > 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); -- Sergey Kandaurov From pluknet at nginx.com Tue Sep 12 13:05:35 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 12 Sep 2023 17:05:35 +0400 Subject: [PATCH 3 of 3] Modules compatibility: added QUIC to signature (ticket #2539) In-Reply-To: <766e9a5771e20cdb3ec4.1694431832@arut-laptop> References: <766e9a5771e20cdb3ec4.1694431832@arut-laptop> Message-ID: <05651614-5AEE-47D8-A2F0-EE2D444BEC31@nginx.com> > On 11 Sep 2023, at 15:30, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1694088909 -14400 > # Thu Sep 07 16:15:09 2023 +0400 > # Node ID 766e9a5771e20cdb3ec41b8001b065ee299ec7ea > # Parent 9ee4158b9d3fa41e647b772e707c29b3e4cb77b5 > Modules compatibility: added QUIC to signature (ticket #2539). > > Enabling QUIC changes ngx_connection_t layout, which is why it should be > added to the signature. > There are several spare values, such as NGX_MODULE_SIGNATURE_9, unused since initial dynamic modules support in 85dea406e18f. They could be reused without introducing more signature points. > diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h > --- a/src/core/ngx_module.h > +++ b/src/core/ngx_module.h > @@ -191,12 +191,18 @@ > #define NGX_MODULE_SIGNATURE_33 "0" > #endif > > -#if (NGX_COMPAT) > +#if (NGX_QUIC || NGX_COMPAT) > #define NGX_MODULE_SIGNATURE_34 "1" > #else > #define NGX_MODULE_SIGNATURE_34 "0" > #endif > > +#if (NGX_COMPAT) > +#define NGX_MODULE_SIGNATURE_35 "1" > +#else > +#define NGX_MODULE_SIGNATURE_35 "0" > +#endif > + > #define NGX_MODULE_SIGNATURE \ > NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2 \ > NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5 \ > @@ -209,7 +215,7 @@ > NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26 \ > NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29 \ > NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32 \ > - NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 > + NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 NGX_MODULE_SIGNATURE_35 > > > #define NGX_MODULE_V1 \ -- Sergey Kandaurov From xeioex at nginx.com Tue Sep 12 18:56:47 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 12 Sep 2023 18:56:47 +0000 Subject: [njs] Types: added TS description for items() introduced in f5428bc87159. Message-ID: details: https://hg.nginx.org/njs/rev/586767a28478 branches: changeset: 2199:586767a28478 user: Dmitry Volyntsev date: Tue Sep 12 11:36:15 2023 -0700 description: Types: added TS description for items() introduced in f5428bc87159. diffstat: test/ts/test.ts | 5 +++++ ts/ngx_core.d.ts | 6 ++++++ 2 files changed, 11 insertions(+), 0 deletions(-) diffs (35 lines): diff -r 6b3176692593 -r 586767a28478 test/ts/test.ts --- a/test/ts/test.ts Mon Sep 11 20:34:48 2023 -0700 +++ b/test/ts/test.ts Tue Sep 12 11:36:15 2023 -0700 @@ -321,3 +321,8 @@ function ngx_object() { ngx.log(ngx.WARN, Buffer.from(ngx.error_log_path)); ngx.log(ngx.ERR, ngx.version); } + +function ngx_shared(dict: NgxSharedDict, numeric: NgxSharedDict) { + var s:NgxKeyValuePair = dict.items()[0]; + var v:number = numeric.incr('foo', 1); +} diff -r 6b3176692593 -r 586767a28478 ts/ngx_core.d.ts --- a/ts/ngx_core.d.ts Mon Sep 11 20:34:48 2023 -0700 +++ b/ts/ngx_core.d.ts Tue Sep 12 11:36:15 2023 -0700 @@ -250,6 +250,7 @@ interface NgxFetchOptions { declare class SharedMemoryError extends Error {} type NgxSharedDictValue = string | number; +type NgxKeyValuePair = { key: string, value: V }; /** * Interface of a dictionary shared among the working processes. @@ -315,6 +316,11 @@ interface NgxSharedDict number : never; /** + * @param maxCount The maximum number of pairs to retrieve (default is 1024). + * @returns An array of the key-value pairs. + */ + items(maxCount?: number): NgxKeyValuePair[]; + /** * @returns The free page size in bytes. * Note that even if the free page is zero the dictionary may still accept * new values if there is enough space in the occupied pages. From xeioex at nginx.com Tue Sep 12 18:56:49 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 12 Sep 2023 18:56:49 +0000 Subject: [njs] Version 0.8.1. Message-ID: details: https://hg.nginx.org/njs/rev/a52b49f9afcf branches: changeset: 2200:a52b49f9afcf user: Dmitry Volyntsev date: Tue Sep 12 11:54:22 2023 -0700 description: Version 0.8.1. diffstat: CHANGES | 20 ++++++++++++++++++++ 1 files changed, 20 insertions(+), 0 deletions(-) diffs (27 lines): diff -r 586767a28478 -r a52b49f9afcf CHANGES --- a/CHANGES Tue Sep 12 11:36:15 2023 -0700 +++ b/CHANGES Tue Sep 12 11:54:22 2023 -0700 @@ -1,3 +1,23 @@ +Changes with njs 0.8.1 12 Sep 2023 + + nginx modules: + + *) Feature: introduced js_periodic directive. + The directive specifies a JS handler to run at regular intervals. + + *) Feature: implemented items() method for a shared dictionary. + The method returns all the non-expired key-value pairs. + + *) Bugfix: fixed size() and keys() methods of a shared dictionary. + + *) Bugfix: fixed erroneous exception in r.internalRedirect() + introduced in 0.8.0. + + Core: + + *) Bugfix: fixed incorrect order of keys in + Object.getOwnPropertyNames(). + Changes with njs 0.8.0 6 Jul 2023 nginx modules: From xeioex at nginx.com Tue Sep 12 18:57:24 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 12 Sep 2023 18:57:24 +0000 Subject: [njs] Added tag 0.8.1 for changeset a52b49f9afcf Message-ID: details: https://hg.nginx.org/njs/rev/a387eed79b90 branches: changeset: 2201:a387eed79b90 user: Dmitry Volyntsev date: Tue Sep 12 11:57:00 2023 -0700 description: Added tag 0.8.1 for changeset a52b49f9afcf diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff -r a52b49f9afcf -r a387eed79b90 .hgtags --- a/.hgtags Tue Sep 12 11:54:22 2023 -0700 +++ b/.hgtags Tue Sep 12 11:57:00 2023 -0700 @@ -63,3 +63,4 @@ 3a1b46d51f040f5e7b9b81c3b2b312a2d272f0a3 26dd3824b9f343e2768609c1b673f788e3a5e154 0.7.11 a1faa64d4972020413fd168e2b542bcc150819c0 0.7.12 0ed1952588ab1e0e1c18425fe7923b2b76f38a65 0.8.0 +a52b49f9afcf410597dc6657ad39ae3dbbfeec56 0.8.1 From arut at nginx.com Wed Sep 13 15:54:29 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 13 Sep 2023 19:54:29 +0400 Subject: [PATCH 1 of 3] QUIC: "handshake_timeout" configuration parameter In-Reply-To: <6AACECB4-3BB7-47AD-973A-08197985B568@nginx.com> References: <65574b876c4047495ac8.1694431830@arut-laptop> <6AACECB4-3BB7-47AD-973A-08197985B568@nginx.com> Message-ID: <20230913155429.5udaeqkiqqi6zy5b@N00W24XTQX> On Tue, Sep 12, 2023 at 05:04:59PM +0400, Sergey Kandaurov wrote: > > > On 11 Sep 2023, at 15:30, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1694415935 -14400 > > # Mon Sep 11 11:05:35 2023 +0400 > > # Node ID 65574b876c4047495ac8622c6161fc87c1b75913 > > # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa > > QUIC: "handshake_timeout" configuration parameter. > > > > Previously QUIC did not have such parameter and handshake duration was > > controlled by HTTP/3. However that required creating and storing HTTP/3 > > session while processing the first client datagram. Apparently there's > > s/while/before ? I'd still keep the "while". However, since apparently it sounds confusing, I have rephrased it a little. > > no convenient way to store the session object before QUIC handshake. In > > the following patch session creation will be postponed until first stream > > creation. > > > > 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 > > @@ -213,6 +213,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_qu > > ngx_add_timer(c->read, qc->tp.max_idle_timeout); > > ngx_quic_connstate_dbg(c); > > > > + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); > > + > > It makes sense to cap handshake_timeout to idle timeout. > I.e. ngx_min(qc->conf->handshake_timeout, qc->tp.max_idle_timeout); > > Or additionally handle this in ngx_quic_close_connection(), > by removing the timer if set for the NGX_DONE case. I like this option better. In fact, we can remove it once for both cases. > Otherwise the connection would prolong unnecessarily on idle timeout > for no purpose, because close timer set prevents closing a connection. > As a (somewhat degenerate) ready example, see h3_keepalive.t TODO > with too long connection close after the patch applied. > The test uses "keepalive_timeout 0;" in the configuration. > > Also I would move setting close timer before calling > ngx_quic_connstate_dbg() to reflect it's set in the debug log. OK > > c->read->handler = ngx_quic_input_handler; > > > > return; > > @@ -521,6 +523,10 @@ ngx_quic_close_connection(ngx_connection > > qc->error_app ? "app " : "", qc->error, > > qc->error_reason ? qc->error_reason : ""); > > > > + if (qc->close.timer_set) { > > + ngx_del_timer(&qc->close); > > + } > > + > > Removing timer there makes the check below for the timer set unnecessary: > > : if (rc == NGX_OK && !qc->close.timer_set) { Now it seems to be we don't need this condition at all and it has nothing to do with the patch. Normally, we'd like to use a timeout for the lower level. However, the PTO() value is only different for the application level due to max_ack_delay and only after handshake completion. However, as discussed before, after handshake completion other levels are cleared. Also, resetting the timer for the same value has no performance implications. So we can remove the condition in a separate patch. > > for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > > ctx = &qc->send_ctx[i]; > > > > 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 > > @@ -67,7 +67,8 @@ typedef struct { > > ngx_flag_t retry; > > ngx_flag_t gso_enabled; > > ngx_flag_t disable_active_migration; > > - ngx_msec_t timeout; > > + ngx_msec_t handshake_timeout; > > + ngx_msec_t idle_timeout; > > ngx_str_t host_key; > > size_t stream_buffer_size; > > ngx_uint_t max_concurrent_streams_bidi; > > 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 > > @@ -630,6 +630,10 @@ ngx_quic_do_init_streams(ngx_connection_ > > > > qc->streams.initialized = 1; > > > > + if (!qc->closing && qc->close.timer_set) { > > + ngx_del_timer(&qc->close); > > + } > > + > > return NGX_OK; > > } > > > > 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 > > @@ -1985,7 +1985,7 @@ ngx_quic_init_transport_params(ngx_quic_ > > * tp->preferred_address = NULL > > */ > > > > - tp->max_idle_timeout = qcf->timeout; > > + tp->max_idle_timeout = qcf->idle_timeout; > > > > tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; > > > > 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 > > @@ -192,7 +192,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * > > * h3scf->quic.host_key = { 0, NULL } > > * h3scf->quic.stream_reject_code_uni = 0; > > * h3scf->quic.disable_active_migration = 0; > > - * h3scf->quic.timeout = 0; > > + * h3scf->quic.idle_timeout = 0; > > * h3scf->max_blocked_streams = 0; > > */ > > > > @@ -223,7 +223,8 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > > ngx_http_v3_srv_conf_t *prev = parent; > > ngx_http_v3_srv_conf_t *conf = child; > > > > - ngx_http_ssl_srv_conf_t *sscf; > > + ngx_http_ssl_srv_conf_t *sscf; > > + ngx_http_core_srv_conf_t *cscf; > > > > ngx_conf_merge_value(conf->enable, prev->enable, 1); > > > > @@ -281,6 +282,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c > > return NGX_CONF_ERROR; > > } > > > > + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); > > + conf->quic.handshake_timeout = cscf->client_header_timeout; > > + > > sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); > > conf->quic.ssl = &sscf->ssl; > > > > 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 > > @@ -58,18 +58,15 @@ static const struct { > > 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); > > Not directly related, h3scf initialization can be moved > under the below condition where it is only used. > > > > > if (c->quic == NULL) { > > @@ -78,10 +75,7 @@ ngx_http_v3_init_stream(ngx_connection_t > > return; > > } > > > > - h3c = hc->v3_session; > > - ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); > > - > > - h3scf->quic.timeout = clcf->keepalive_timeout; > > + h3scf->quic.idle_timeout = clcf->keepalive_timeout; > > ngx_quic_run(c, &h3scf->quic); > > return; > > } > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From arut at nginx.com Wed Sep 13 16:02:05 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 13 Sep 2023 20:02:05 +0400 Subject: [PATCH 2 of 3] HTTP/3: eliminated v3_session field from ngx_http_connection_t In-Reply-To: <6CE6B9BE-EE11-444C-A35E-7815289A1157@nginx.com> References: <9ee4158b9d3fa41e647b.1694431831@arut-laptop> <6CE6B9BE-EE11-444C-A35E-7815289A1157@nginx.com> Message-ID: <20230913160205.vnv32jt25nykouj7@N00W24XTQX> Hi, On Tue, Sep 12, 2023 at 05:05:02PM +0400, Sergey Kandaurov wrote: > > > On 11 Sep 2023, at 15:30, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1694431436 -14400 > > # Mon Sep 11 15:23:56 2023 +0400 > > # Node ID 9ee4158b9d3fa41e647b772e707c29b3e4cb77b5 > > # Parent 65574b876c4047495ac8622c6161fc87c1b75913 > > HTTP/3: eliminated v3_session field from ngx_http_connection_t. > > > > The field was under NGX_HTTP_V3 macro, which was a source of binary > > compatibility problems when nginx/module is build with/without HTTP/3 support. > > > > Now ngx_http_v3_session_t is assigned to c->data on streams initialization, > > while ngx_http_connection_t object is referenced by http_connection field of > > the session object, similar to ngx_http_v2_connection_t and ngx_http_request_t. > > I'd write explicitly (taking from the 1st patch commit log) that session > creation is now postponed, to make the description self contained, YMMV. > > Note that this change will trigger segfaults on graceful shutdown > if there are connections with handshake not yet complete, > due to now postponed session creation. > In this case, http3 shutdown() callback is called, used to send > GOAWAY and proceed with connection close, but h3c is not yet. > Probably calling the callback should depend on c->ssl->handshaked, > because it has little sense if handshake isn't complete. Thanks for noticing this. Indeed, hc is treated as h3c, which may lead to a crash. The solution is to postpone calling shutdown() until after init(). I will add a separate patch for this. An alternative solution could be to terminate the handshake since the worker is exiting anyway. However, as we try to be as graceful as possible while shutting down the old worker, it's better to finish the handshake and send GOAWAY after that. > > 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 > > @@ -324,10 +324,6 @@ 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; > > > > 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,6 +30,8 @@ ngx_http_v3_init_session(ngx_connection_ > > goto failed; > > } > > > > + h3c->http_connection = hc; > > + > > ngx_queue_init(&h3c->blocked); > > > > h3c->keepalive.log = c->log; > > @@ -48,7 +50,7 @@ ngx_http_v3_init_session(ngx_connection_ > > cln->handler = ngx_http_v3_cleanup_session; > > cln->data = h3c; > > > > - hc->v3_session = h3c; > > + c->data = h3c; > > > > return NGX_OK; > > > > 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 > > @@ -78,11 +78,12 @@ > > #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 \ > > +#define ngx_http_v3_get_session(c) \ > > + ((ngx_http_v3_session_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_quic_get_connection(c) \ > > + (ngx_http_v3_get_session(c)->http_connection) > > > > #define ngx_http_v3_get_module_loc_conf(c, module) \ > > ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ > > @@ -120,6 +121,8 @@ struct ngx_http_v3_parse_s { > > > > > > struct ngx_http_v3_session_s { > > + ngx_http_connection_t *http_connection; > > + > > ngx_http_v3_dynamic_table_t table; > > > > ngx_event_t keepalive; > > 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 > > @@ -70,11 +70,6 @@ ngx_http_v3_init_stream(ngx_connection_t > > 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; > > - } > > - > > h3scf->quic.idle_timeout = clcf->keepalive_timeout; > > ngx_quic_run(c, &h3scf->quic); > > return; > > @@ -112,6 +107,10 @@ ngx_http_v3_init(ngx_connection_t *c) > > > > ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); > > > > + if (ngx_http_v3_init_session(c) != NGX_OK) { > > + return NGX_ERROR; > > + } > > + > > 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); > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From arut at nginx.com Wed Sep 13 16:07:45 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 13 Sep 2023 20:07:45 +0400 Subject: [PATCH 3 of 3] Modules compatibility: added QUIC to signature (ticket #2539) In-Reply-To: <05651614-5AEE-47D8-A2F0-EE2D444BEC31@nginx.com> References: <766e9a5771e20cdb3ec4.1694431832@arut-laptop> <05651614-5AEE-47D8-A2F0-EE2D444BEC31@nginx.com> Message-ID: <20230913160745.rnjkw3fovrogtmwb@N00W24XTQX> Hi, On Tue, Sep 12, 2023 at 05:05:35PM +0400, Sergey Kandaurov wrote: > > > On 11 Sep 2023, at 15:30, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1694088909 -14400 > > # Thu Sep 07 16:15:09 2023 +0400 > > # Node ID 766e9a5771e20cdb3ec41b8001b065ee299ec7ea > > # Parent 9ee4158b9d3fa41e647b772e707c29b3e4cb77b5 > > Modules compatibility: added QUIC to signature (ticket #2539). > > > > Enabling QUIC changes ngx_connection_t layout, which is why it should be > > added to the signature. > > > > There are several spare values, such as NGX_MODULE_SIGNATURE_9, > unused since initial dynamic modules support in 85dea406e18f. > They could be reused without introducing more signature points. Thanks. I'm choosing #18 unused since 2016. > > diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h > > --- a/src/core/ngx_module.h > > +++ b/src/core/ngx_module.h > > @@ -191,12 +191,18 @@ > > #define NGX_MODULE_SIGNATURE_33 "0" > > #endif > > > > -#if (NGX_COMPAT) > > +#if (NGX_QUIC || NGX_COMPAT) > > #define NGX_MODULE_SIGNATURE_34 "1" > > #else > > #define NGX_MODULE_SIGNATURE_34 "0" > > #endif > > > > +#if (NGX_COMPAT) > > +#define NGX_MODULE_SIGNATURE_35 "1" > > +#else > > +#define NGX_MODULE_SIGNATURE_35 "0" > > +#endif > > + > > #define NGX_MODULE_SIGNATURE \ > > NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2 \ > > NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5 \ > > @@ -209,7 +215,7 @@ > > NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26 \ > > NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29 \ > > NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32 \ > > - NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 > > + NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34 NGX_MODULE_SIGNATURE_35 > > > > > > #define NGX_MODULE_V1 \ > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From arut at nginx.com Thu Sep 14 10:17:03 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:03 +0400 Subject: [PATCH 0 of 6] QUIC module compatibility issues In-Reply-To: References: Message-ID: Updated series. From arut at nginx.com Thu Sep 14 10:17:04 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:04 +0400 Subject: [PATCH 1 of 6] QUIC: "handshake_timeout" configuration parameter In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1694613577 -14400 # Wed Sep 13 17:59:37 2023 +0400 # Node ID ad3d34ddfdcc88a9e002c55db0ea7df970dfb786 # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa QUIC: "handshake_timeout" configuration parameter. Previously QUIC did not have such parameter and handshake duration was controlled by HTTP/3. However that required creating and storing HTTP/3 session on first client datagram. Apparently there's no convenient way to store the session object until QUIC handshake is complete. In the followup patches session creation will be postponed to init() callback. 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 @@ -211,6 +211,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_qu qc = ngx_quic_get_connection(c); ngx_add_timer(c->read, qc->tp.max_idle_timeout); + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); + ngx_quic_connstate_dbg(c); c->read->handler = ngx_quic_input_handler; @@ -485,6 +487,10 @@ ngx_quic_close_connection(ngx_connection ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } + if (qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + if (rc == NGX_DONE) { /* 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 @@ -67,7 +67,8 @@ typedef struct { ngx_flag_t retry; ngx_flag_t gso_enabled; ngx_flag_t disable_active_migration; - ngx_msec_t timeout; + ngx_msec_t handshake_timeout; + ngx_msec_t idle_timeout; ngx_str_t host_key; size_t stream_buffer_size; ngx_uint_t max_concurrent_streams_bidi; 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 @@ -630,6 +630,10 @@ ngx_quic_do_init_streams(ngx_connection_ qc->streams.initialized = 1; + if (!qc->closing && qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + return NGX_OK; } 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 @@ -1985,7 +1985,7 @@ ngx_quic_init_transport_params(ngx_quic_ * tp->preferred_address = NULL */ - tp->max_idle_timeout = qcf->timeout; + tp->max_idle_timeout = qcf->idle_timeout; tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; 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 @@ -192,7 +192,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * * h3scf->quic.host_key = { 0, NULL } * h3scf->quic.stream_reject_code_uni = 0; * h3scf->quic.disable_active_migration = 0; - * h3scf->quic.timeout = 0; + * h3scf->quic.idle_timeout = 0; * h3scf->max_blocked_streams = 0; */ @@ -223,7 +223,8 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c ngx_http_v3_srv_conf_t *prev = parent; ngx_http_v3_srv_conf_t *conf = child; - ngx_http_ssl_srv_conf_t *sscf; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; ngx_conf_merge_value(conf->enable, prev->enable, 1); @@ -281,6 +282,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c return NGX_CONF_ERROR; } + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); + conf->quic.handshake_timeout = cscf->client_header_timeout; + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); conf->quic.ssl = &sscf->ssl; 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 @@ -58,18 +58,15 @@ static const struct { 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) { @@ -78,10 +75,7 @@ ngx_http_v3_init_stream(ngx_connection_t return; } - h3c = hc->v3_session; - ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); - - h3scf->quic.timeout = clcf->keepalive_timeout; + h3scf->quic.idle_timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; } From arut at nginx.com Thu Sep 14 10:17:05 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:05 +0400 Subject: [PATCH 2 of 6] HTTP/3: moved variable initialization In-Reply-To: References: Message-ID: <6d3ca6f8db357a1db267.1694686625@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694613433 -14400 # Wed Sep 13 17:57:13 2023 +0400 # Node ID 6d3ca6f8db357a1db267978f730875e51e87c608 # Parent ad3d34ddfdcc88a9e002c55db0ea7df970dfb786 HTTP/3: moved variable initialization. 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 @@ -67,7 +67,6 @@ ngx_http_v3_init_stream(ngx_connection_t hc->ssl = 1; clcf = ngx_http_get_module_loc_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) { @@ -75,7 +74,9 @@ ngx_http_v3_init_stream(ngx_connection_t return; } + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); h3scf->quic.idle_timeout = clcf->keepalive_timeout; + ngx_quic_run(c, &h3scf->quic); return; } From arut at nginx.com Thu Sep 14 10:17:06 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:06 +0400 Subject: [PATCH 3 of 6] QUIC: call shutdown() callback only after handshake completion In-Reply-To: References: Message-ID: <51166a8f35ba880415dd.1694686626@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694613709 -14400 # Wed Sep 13 18:01:49 2023 +0400 # Node ID 51166a8f35ba880415ddc2bf2745012a8d4cea34 # Parent 6d3ca6f8db357a1db267978f730875e51e87c608 QUIC: call shutdown() callback only after handshake completion. Previously the callback could be called while QUIC handshake was in progress and, what's more important, before the init() callback. Now it's postponed after init(). This change is a preparation to postponing HTTP/3 session creation to init(). 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 @@ -427,7 +427,7 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - if (!qc->closing && qc->conf->shutdown) { + if (!qc->closing && qc->streams.initialized && qc->conf->shutdown) { qc->conf->shutdown(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 @@ -620,6 +620,10 @@ ngx_quic_do_init_streams(ngx_connection_ } } + if (ngx_exiting && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + for (q = ngx_queue_head(&qc->streams.uninitialized); q != ngx_queue_sentinel(&qc->streams.uninitialized); q = ngx_queue_next(q)) From arut at nginx.com Thu Sep 14 10:17:07 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:07 +0400 Subject: [PATCH 4 of 6] HTTP/3: postponed session creation to init() callback In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1694686423 -14400 # Thu Sep 14 14:13:43 2023 +0400 # Node ID f92cac470cd7cbd0fd6ff85d11ed6dfa6562a6f3 # Parent 51166a8f35ba880415ddc2bf2745012a8d4cea34 HTTP/3: postponed session creation to init() callback. Now the session object is assigned to c->data while ngx_http_connection_t object is referenced by its http_connection field, similar to ngx_http_v2_connection_t and ngx_http_request_t. The change allows to eliminate v3_session field from ngx_http_connection_t. The field was under NGX_HTTP_V3 macro, which was a source of binary compatibility problems when nginx/module is build with/without HTTP/3 support. Postponing is essential since c->data should retain the reference to ngx_http_connection_t object throughout QUIC handshake, because SSL callbacks ngx_http_ssl_servername() and ngx_http_ssl_alpn_select() rely on this. 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 @@ -324,10 +324,6 @@ 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; 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,6 +30,8 @@ ngx_http_v3_init_session(ngx_connection_ goto failed; } + h3c->http_connection = hc; + ngx_queue_init(&h3c->blocked); h3c->keepalive.log = c->log; @@ -48,7 +50,7 @@ ngx_http_v3_init_session(ngx_connection_ cln->handler = ngx_http_v3_cleanup_session; cln->data = h3c; - hc->v3_session = h3c; + c->data = h3c; return NGX_OK; 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 @@ -78,11 +78,12 @@ #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 \ +#define ngx_http_v3_get_session(c) \ + ((ngx_http_v3_session_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_quic_get_connection(c) \ + (ngx_http_v3_get_session(c)->http_connection) #define ngx_http_v3_get_module_loc_conf(c, module) \ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ @@ -120,6 +121,8 @@ struct ngx_http_v3_parse_s { struct ngx_http_v3_session_s { + ngx_http_connection_t *http_connection; + ngx_http_v3_dynamic_table_t table; ngx_event_t keepalive; 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 @@ -69,11 +69,6 @@ ngx_http_v3_init_stream(ngx_connection_t clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); if (c->quic == NULL) { - if (ngx_http_v3_init_session(c) != NGX_OK) { - ngx_http_close_connection(c); - return; - } - h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); h3scf->quic.idle_timeout = clcf->keepalive_timeout; @@ -113,6 +108,10 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + 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); From arut at nginx.com Thu Sep 14 10:17:08 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:08 +0400 Subject: [PATCH 5 of 6] QUIC: simplified setting close timer when closing connection In-Reply-To: References: Message-ID: <54829df980cc8d597528.1694686628@arut-laptop> # HG changeset patch # User Roman Arutyunyan # Date 1694686520 -14400 # Thu Sep 14 14:15:20 2023 +0400 # Node ID 54829df980cc8d5975280f355f2a8c1a9cff2e79 # Parent f92cac470cd7cbd0fd6ff85d11ed6dfa6562a6f3 QUIC: simplified setting close timer when closing connection. Previously, the timer was never reset due to an explicit check. The check was added in 36b59521a41c as part of connection close simplification. The reason was to retain the earliest timeout. However, the timeouts are all the same while QUIC handshake is in progress and resetting the timer for the same value has no performance implications. After handshake completion there's only application level. The change removes the check. 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 @@ -537,7 +537,7 @@ ngx_quic_close_connection(ngx_connection qc->error_level = ctx->level; (void) ngx_quic_send_cc(c); - if (rc == NGX_OK && !qc->close.timer_set) { + if (rc == NGX_OK) { ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); } } From arut at nginx.com Thu Sep 14 10:17:09 2023 From: arut at nginx.com (=?iso-8859-1?q?Roman_Arutyunyan?=) Date: Thu, 14 Sep 2023 14:17:09 +0400 Subject: [PATCH 6 of 6] Modules compatibility: added QUIC to signature (ticket #2539) In-Reply-To: References: Message-ID: # HG changeset patch # User Roman Arutyunyan # Date 1694612895 -14400 # Wed Sep 13 17:48:15 2023 +0400 # Node ID fa644e50bbba40db23047818a4fedc1cc9651385 # Parent 54829df980cc8d5975280f355f2a8c1a9cff2e79 Modules compatibility: added QUIC to signature (ticket #2539). Enabling QUIC changes ngx_connection_t layout, which is why it should be added to the signature. diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -107,7 +107,12 @@ #endif #define NGX_MODULE_SIGNATURE_17 "0" + +#if (NGX_QUIC || NGX_COMPAT) +#define NGX_MODULE_SIGNATURE_18 "1" +#else #define NGX_MODULE_SIGNATURE_18 "0" +#endif #if (NGX_HAVE_OPENAT) #define NGX_MODULE_SIGNATURE_19 "1" From arut at nginx.com Mon Sep 18 07:08:42 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 18 Sep 2023 11:08:42 +0400 Subject: [PATCH 2 of 8] QUIC: added check to prevent packet output with discarded keys In-Reply-To: <02c86eac80c907adb779.1694099634@enoparse.local> References: <02c86eac80c907adb779.1694099634@enoparse.local> Message-ID: <20230918070842.s4a6o6ti52xx4fcu@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:13:54PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1694041352 -14400 > # Thu Sep 07 03:02:32 2023 +0400 > # Node ID 02c86eac80c907adb7790c79ac6892afabcee5f4 > # Parent be1862a28fd8575a88475215ccfce995e392dfab > QUIC: added check to prevent packet output with discarded keys. > > In addition to triggering alert, it ensures that such packets won't be sent. > > With the previous change that marks server keys as discarded by zeroing the > key lengh, it is now an error to send packets with discarded keys. OpenSSL > based stacks tolerate such behaviour because key length isn't used in packet > protection, but BoringSSL will raise the UNSUPPORTED_KEY_SIZE cipher error. > It won't be possible to use discarded keys with reused crypto contexts. > > 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 > @@ -519,6 +519,21 @@ ngx_quic_output_packet(ngx_connection_t > > qc = ngx_quic_get_connection(c); > > + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { > + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "quic %s write keys discarded", > + ngx_quic_level_name(ctx->level)); > + > + 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); > + } > + > + return 0; > + } > + > ngx_quic_init_packet(c, ctx, &pkt, qc->path); > > min_payload = ngx_quic_payload_size(&pkt, min); > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel In ngx_quic_create_datagrams(), before calling ngx_quic_output_packet(), ngx_quic_generate_ack() generates ACK frames for the current level. Maybe it's better to add this block before generating ACKs? Otherwise we generate an ACK and then immediately remove it. This technically would require a similar block in ngx_quic_create_segments(), although the application level should normally never be in a discarded key situation. It's true however that the next patch partially solves the issue by not generating the last handshake ACK. That however does not seem to be a complete solution since there could be previous ACKs. In my opinion, this patch provides a better and more general solution than the next one. -- Roman Arutyunyan From pluknet at nginx.com Tue Sep 19 09:59:27 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 19 Sep 2023 13:59:27 +0400 Subject: [PATCH 3 of 6] QUIC: call shutdown() callback only after handshake completion In-Reply-To: <51166a8f35ba880415dd.1694686626@arut-laptop> References: <51166a8f35ba880415dd.1694686626@arut-laptop> Message-ID: <8697F5F7-57CD-476E-8D43-63FCC23BBA05@nginx.com> > On 14 Sep 2023, at 14:17, Roman Arutyunyan wrote: > > # HG changeset patch > # User Roman Arutyunyan > # Date 1694613709 -14400 > # Wed Sep 13 18:01:49 2023 +0400 > # Node ID 51166a8f35ba880415ddc2bf2745012a8d4cea34 > # Parent 6d3ca6f8db357a1db267978f730875e51e87c608 > QUIC: call shutdown() callback only after handshake completion. > > Previously the callback could be called while QUIC handshake was in progress > and, what's more important, before the init() callback. Now it's postponed > after init(). > > This change is a preparation to postponing HTTP/3 session creation to init(). > > 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 > @@ -427,7 +427,7 @@ ngx_quic_input_handler(ngx_event_t *rev) > return; > } > > - if (!qc->closing && qc->conf->shutdown) { > + if (!qc->closing && qc->streams.initialized && qc->conf->shutdown) { > qc->conf->shutdown(c); Adding condition here will now prevent doing anything on graceful shutdown, input handler will just return, connection will stuck for handshake_timeout. I'd rather move it above, to handle similar to closing reusable connections: if (!ngx_exiting || !qc->streams.initialized) { > } > > 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 > @@ -620,6 +620,10 @@ ngx_quic_do_init_streams(ngx_connection_ > } > } > > + if (ngx_exiting && qc->conf->shutdown) { > + qc->conf->shutdown(c); how this can be reached? > + } > + > for (q = ngx_queue_head(&qc->streams.uninitialized); > q != ngx_queue_sentinel(&qc->streams.uninitialized); > q = ngx_queue_next(q)) -- Sergey Kandaurov From nginx at bzzt.net Tue Sep 19 10:28:49 2023 From: nginx at bzzt.net (Arnout Engelen) Date: Tue, 19 Sep 2023 12:28:49 +0200 Subject: [PATCH] Mail: allow auth to the proxy without auth to the backend Message-ID: # HG changeset patch # User Arnout Engelen # Date 1695027670 -7200 # Mon Sep 18 11:01:10 2023 +0200 # Node ID 9606e589b9537495c0457383048ac6888be0e7b4 # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa Mail: allow auth to the proxy without auth to the backend Currently, when the client authenticates itself to the nginx mail proxy, the mail proxy also authenticates itself to the backend. I encountered a situation where I wanted the proxy to require authentication, and forward the mail to a (local/firewalled) mailserver that does not have authentication configured. I created the patch below to support that. I'm providing this patch primarily for feedback at this point: while it does work for my scenario and pass the nginx-tests, it likely needs additional cleanup and testing. I'd like your thoughs on whether this change makes sense in the first place, and whether this is generally a reasonable approach - if so I'll clean up the patch further. My approach is to allow the authentication server to return a 'Auth-Method: none' header, in which case the proxy will not attempt to authenticate to the backend but instead wait for the 'MAIL FROM' from the client. You'll notice I've added a 'proxy_auth_method'. The reason I didn't overwrite 'auth_method' is that 'auth_method' is also used to determine whether to confirm the authentication to the client. Is that acceptable from a binary compatibility perspective? Looking forward to hearing your thoughts! diff -r daf8f5ba23d8 -r 9606e589b953 src/mail/ngx_mail.h --- a/src/mail/ngx_mail.h Fri Sep 01 20:31:46 2023 +0400 +++ b/src/mail/ngx_mail.h Mon Sep 18 11:01:10 2023 +0200 @@ -212,6 +212,7 @@ unsigned starttls:1; unsigned esmtp:1; unsigned auth_method:3; + unsigned proxy_auth_method:3; unsigned auth_wait:1; ngx_str_t login; diff -r daf8f5ba23d8 -r 9606e589b953 src/mail/ngx_mail_auth_http_module.c --- a/src/mail/ngx_mail_auth_http_module.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/mail/ngx_mail_auth_http_module.c Mon Sep 18 11:01:10 2023 +0200 @@ -677,6 +677,23 @@ continue; } + if (len == sizeof("Auth-Method") - 1 + && ngx_strncasecmp(ctx->header_name_start, + (u_char *) "Auth-Method", + sizeof("Auth-Method") - 1) + == 0) + { + int value_len = ctx->header_end - ctx->header_start; + if (value_len == sizeof("none") - 1 + && ngx_strncasecmp(ctx->header_start, + (u_char *) "none", + sizeof("none") - 1) + == 0) + { + s->proxy_auth_method = NGX_MAIL_AUTH_NONE; + } + } + /* ignore other headers */ continue; @@ -883,6 +900,7 @@ s->mail_state = 0; s->auth_method = NGX_MAIL_AUTH_PLAIN; + s->proxy_auth_method = NGX_MAIL_AUTH_PLAIN; c->log->action = "in auth state"; diff -r daf8f5ba23d8 -r 9606e589b953 src/mail/ngx_mail_handler.c --- a/src/mail/ngx_mail_handler.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/mail/ngx_mail_handler.c Mon Sep 18 11:01:10 2023 +0200 @@ -714,6 +714,7 @@ "mail auth cram-md5: \"%V\" \"%V\"", &s->login, &s->passwd); s->auth_method = NGX_MAIL_AUTH_CRAM_MD5; + s->proxy_auth_method = NGX_MAIL_AUTH_CRAM_MD5; return NGX_DONE; } @@ -748,6 +749,7 @@ "mail auth external: \"%V\"", &s->login); s->auth_method = NGX_MAIL_AUTH_EXTERNAL; + s->proxy_auth_method = NGX_MAIL_AUTH_EXTERNAL; return NGX_DONE; } diff -r daf8f5ba23d8 -r 9606e589b953 src/mail/ngx_mail_pop3_handler.c --- a/src/mail/ngx_mail_pop3_handler.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/mail/ngx_mail_pop3_handler.c Mon Sep 18 11:01:10 2023 +0200 @@ -464,6 +464,7 @@ "pop3 apop: \"%V\" \"%V\"", &s->login, &s->passwd); s->auth_method = NGX_MAIL_AUTH_APOP; + s->proxy_auth_method = NGX_MAIL_AUTH_APOP; return NGX_DONE; } diff -r daf8f5ba23d8 -r 9606e589b953 src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/mail/ngx_mail_proxy_module.c Mon Sep 18 11:01:10 2023 +0200 @@ -605,8 +605,12 @@ if (pcf->xclient) { s->mail_state = ngx_smtp_helo_xclient; - } else if (s->auth_method == NGX_MAIL_AUTH_NONE) { - s->mail_state = ngx_smtp_helo_from; + } else if (s->proxy_auth_method == NGX_MAIL_AUTH_NONE) { + if (s->smtp_from.len) { + s->mail_state = ngx_smtp_helo_from; + } else { + s->mail_state = ngx_smtp_helo; + } } else if (pcf->smtp_auth) { s->mail_state = ngx_smtp_helo_auth; @@ -667,8 +671,12 @@ if (s->smtp_helo.len) { s->mail_state = ngx_smtp_xclient_helo; - } else if (s->auth_method == NGX_MAIL_AUTH_NONE) { - s->mail_state = ngx_smtp_xclient_from; + } else if (s->proxy_auth_method == NGX_MAIL_AUTH_NONE) { + if (s->smtp_from.len) { + s->mail_state = ngx_smtp_xclient_from; + } else { + s->mail_state = ngx_smtp_xclient_helo; + } } else if (pcf->smtp_auth) { s->mail_state = ngx_smtp_xclient_auth; @@ -700,8 +708,12 @@ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module); - if (s->auth_method == NGX_MAIL_AUTH_NONE) { - s->mail_state = ngx_smtp_helo_from; + if (s->proxy_auth_method == NGX_MAIL_AUTH_NONE) { + if (s->smtp_from.len) { + s->mail_state = ngx_smtp_helo_from; + } else { + s->mail_state = ngx_smtp_helo; + } } else if (pcf->smtp_auth) { s->mail_state = ngx_smtp_helo_auth; diff -r daf8f5ba23d8 -r 9606e589b953 src/mail/ngx_mail_smtp_handler.c --- a/src/mail/ngx_mail_smtp_handler.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/mail/ngx_mail_smtp_handler.c Mon Sep 18 11:01:10 2023 +0200 @@ -820,6 +820,7 @@ "smtp rcpt to:\"%V\"", &s->smtp_to); s->auth_method = NGX_MAIL_AUTH_NONE; + s->proxy_auth_method = NGX_MAIL_AUTH_NONE; return NGX_DONE; } From pluknet at nginx.com Tue Sep 19 10:33:04 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Tue, 19 Sep 2023 14:33:04 +0400 Subject: [PATCH 3 of 6] QUIC: call shutdown() callback only after handshake completion In-Reply-To: <8697F5F7-57CD-476E-8D43-63FCC23BBA05@nginx.com> References: <51166a8f35ba880415dd.1694686626@arut-laptop> <8697F5F7-57CD-476E-8D43-63FCC23BBA05@nginx.com> Message-ID: > On 19 Sep 2023, at 13:59, Sergey Kandaurov wrote: > >> >> On 14 Sep 2023, at 14:17, Roman Arutyunyan wrote: >> >> # HG changeset patch >> # User Roman Arutyunyan >> # Date 1694613709 -14400 >> # Wed Sep 13 18:01:49 2023 +0400 >> # Node ID 51166a8f35ba880415ddc2bf2745012a8d4cea34 >> # Parent 6d3ca6f8db357a1db267978f730875e51e87c608 >> QUIC: call shutdown() callback only after handshake completion. >> >> Previously the callback could be called while QUIC handshake was in progress >> and, what's more important, before the init() callback. Now it's postponed >> after init(). >> >> This change is a preparation to postponing HTTP/3 session creation to init(). >> >> 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 >> @@ -427,7 +427,7 @@ ngx_quic_input_handler(ngx_event_t *rev) >> return; >> } >> >> - if (!qc->closing && qc->conf->shutdown) { >> + if (!qc->closing && qc->streams.initialized && qc->conf->shutdown) { >> qc->conf->shutdown(c); > > Adding condition here will now prevent doing anything on graceful shutdown, > input handler will just return, connection will stuck for handshake_timeout. > I'd rather move it above, to handle similar to closing reusable connections: > > if (!ngx_exiting || !qc->streams.initialized) { > > >> } >> >> 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 >> @@ -620,6 +620,10 @@ ngx_quic_do_init_streams(ngx_connection_ >> } >> } >> >> + if (ngx_exiting && qc->conf->shutdown) { >> + qc->conf->shutdown(c); > > how this can be reached? More specifically, the check in ngx_quic_input_handler() seems to take care for all possible cases, it won't reach there. > >> + } >> + >> for (q = ngx_queue_head(&qc->streams.uninitialized); >> q != ngx_queue_sentinel(&qc->streams.uninitialized); >> q = ngx_queue_next(q)) > Other patches look good. -- Sergey Kandaurov From arut at nginx.com Tue Sep 19 13:53:43 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Tue, 19 Sep 2023 17:53:43 +0400 Subject: [PATCH 5 of 8] QUIC: reusing crypto contexts for packet protection In-Reply-To: <28f7491bc79771f9cfa8.1694099637@enoparse.local> References: <28f7491bc79771f9cfa8.1694099637@enoparse.local> Message-ID: <20230919135343.gyryhjjh5igd6xzl@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:13:57PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1694099424 -14400 > # Thu Sep 07 19:10:24 2023 +0400 > # Node ID 28f7491bc79771f9cfa882b1b5584fa48ea42e6b > # Parent 24e5d652ecc861f0c68607d20941abbf3726fdf1 > QUIC: reusing crypto contexts for packet protection. > > 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 > @@ -225,6 +225,7 @@ ngx_quic_new_connection(ngx_connection_t > { > ngx_uint_t i; > ngx_quic_tp_t *ctp; > + ngx_pool_cleanup_t *cln; > ngx_quic_connection_t *qc; > > qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); > @@ -237,6 +238,14 @@ ngx_quic_new_connection(ngx_connection_t > return NULL; > } > > + cln = ngx_pool_cleanup_add(c->pool, 0); > + if (cln == NULL) { > + return NULL; > + } > + > + cln->handler = ngx_quic_keys_cleanup; > + cln->data = qc->keys; I think it's better to cleanup keys in ngx_quic_close_connection(). We do the same with sockets by calling ngx_quic_close_sockets(). We just have to carefully handle the errors later in this function and cleanup keys when ngx_quic_open_sockets() fails. > qc->version = pkt->version; > > ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, > 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 > @@ -54,9 +54,10 @@ struct ngx_quic_compat_s { > > > 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, > +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, > 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 void ngx_quic_compat_cleanup_encryption_secret(void *data); > 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); > @@ -214,14 +215,14 @@ ngx_quic_compat_keylog_callback(const SS > com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); > com->read_record = 0; > > - (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level, > + (void) ngx_quic_compat_set_encryption_secret(c, &com->keys, level, > cipher, secret, n); > } > } > > > static ngx_int_t > -ngx_quic_compat_set_encryption_secret(ngx_log_t *log, > +ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, > ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, > const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) > { > @@ -231,6 +232,7 @@ ngx_quic_compat_set_encryption_secret(ng > ngx_quic_hkdf_t seq[2]; > ngx_quic_secret_t *peer_secret; > ngx_quic_ciphers_t ciphers; > + ngx_pool_cleanup_t *cln; > > peer_secret = &keys->secret; > > @@ -239,12 +241,12 @@ ngx_quic_compat_set_encryption_secret(ng > key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); > > if (key_len == NGX_ERROR) { > - ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); > + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); > return NGX_ERROR; > } > > if (sizeof(peer_secret->secret.data) < secret_len) { > - ngx_log_error(NGX_LOG_ALERT, log, 0, > + ngx_log_error(NGX_LOG_ALERT, c->log, 0, > "unexpected secret len: %uz", secret_len); > return NGX_ERROR; > } > @@ -262,15 +264,42 @@ ngx_quic_compat_set_encryption_secret(ng > 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) { > + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { > return NGX_ERROR; > } > } > > + ngx_quic_crypto_cleanup(peer_secret); > + > + if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) { > + return NGX_ERROR; > + } > + > + /* register cleanup handler once */ > + > + if (level == ssl_encryption_handshake) { Does not look perfect, but I don't see a simpler and better solution. > + cln = ngx_pool_cleanup_add(c->pool, 0); > + if (cln == NULL) { Cleanup peer_secret here? Alternatively, move this block up. > + return NGX_ERROR; > + } > + > + cln->handler = ngx_quic_compat_cleanup_encryption_secret; > + cln->data = peer_secret; > + } > + > return NGX_OK; > } > > > +static void > +ngx_quic_compat_cleanup_encryption_secret(void *data) > +{ > + ngx_quic_secret_t *secret = data; > + > + ngx_quic_crypto_cleanup(secret); > +} > + > + > 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, > @@ -568,8 +597,7 @@ ngx_quic_compat_create_record(ngx_quic_c > ngx_memcpy(nonce, secret->iv.data, secret->iv.len); > ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); > > - if (ngx_quic_crypto_seal(ciphers.c, secret, &out, > - nonce, &rec->payload, &ad, rec->log) > + if (ngx_quic_crypto_seal(secret, &out, nonce, &rec->payload, &ad, rec->log) > != NGX_OK) > { > return NGX_ERROR; > diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -26,9 +26,8 @@ static ngx_int_t ngx_hkdf_extract(u_char > 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_crypto_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_crypto_open(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_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, > ngx_quic_secret_t *s, u_char *out, u_char *in); > > @@ -108,13 +107,14 @@ 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; > + 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; > + ngx_quic_ciphers_t ciphers; > > static const uint8_t salt[20] = > "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" > @@ -180,6 +180,18 @@ ngx_quic_keys_set_initial_secret(ngx_qui > } > } > > + if (ngx_quic_ciphers(0, &ciphers, ssl_encryption_initial) == NGX_ERROR) { > + return NGX_ERROR; > + } > + > + if (ngx_quic_crypto_init(ciphers.c, client, 0, log) == NGX_ERROR) { > + return NGX_ERROR; > + } > + > + if (ngx_quic_crypto_init(ciphers.c, server, 1, log) == NGX_ERROR) { Call ngx_quic_crypto_cleanup() for client here? This function is called from ngx_quic_send_early_cc(), which has no keys cleanup handler (and I propose we remove it from regular QUIC connections as well). > + return NGX_ERROR; > + } > + > return NGX_OK; > } > > @@ -343,9 +355,9 @@ failed: > } > > > -static ngx_int_t > -ngx_quic_crypto_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) > +ngx_int_t > +ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, > + ngx_int_t enc, ngx_log_t *log) > { > > #ifdef OPENSSL_IS_BORINGSSL > @@ -357,19 +369,7 @@ ngx_quic_crypto_open(const ngx_quic_ciph > 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; > EVP_CIPHER_CTX *ctx; > > ctx = EVP_CIPHER_CTX_new(); > @@ -378,114 +378,9 @@ ngx_quic_crypto_open(const ngx_quic_ciph > 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; > - } > - > - in->len -= NGX_QUIC_TAG_LEN; > - > - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, > - in->data + in->len) > - == 0) > - { > - EVP_CIPHER_CTX_free(ctx); > - ngx_ssl_error(NGX_LOG_INFO, log, 0, > - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); > - return NGX_ERROR; > - } > - > - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_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_AEAD_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_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE > - && EVP_DecryptUpdate(ctx, NULL, &len, NULL, in->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, 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) != 1) { > + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc) != 1) { > EVP_CIPHER_CTX_free(ctx); > - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); > - return NGX_ERROR; > - } > - > - out->len = len; > - > - if (EVP_DecryptFinal_ex(ctx, out->data + out->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_crypto_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"); > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); > return NGX_ERROR; > } > > @@ -509,28 +404,121 @@ ngx_quic_crypto_seal(const ngx_quic_ciph > return NGX_ERROR; > } > > - if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { > + if (EVP_CipherInit_ex(ctx, NULL, NULL, s->key.data, NULL, enc) != 1) { > EVP_CIPHER_CTX_free(ctx); > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); > + return NGX_ERROR; > + } > +#endif > + > + s->ctx = ctx; > + return NGX_OK; > +} > + > + > +static ngx_int_t > +ngx_quic_crypto_open(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_quic_crypto_ctx_t *ctx; > + > + ctx = s->ctx; > + > +#ifdef OPENSSL_IS_BORINGSSL > + 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) > + { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); > + return NGX_ERROR; > + } > +#else > + int len; > + > + if (EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, nonce) != 1) { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); > + return NGX_ERROR; > + } > + > + in->len -= NGX_QUIC_TAG_LEN; > + > + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, > + in->data + in->len) > + == 0) > + { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, > + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); > + return NGX_ERROR; > + } > + > + if (EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(ctx)) == EVP_CIPH_CCM_MODE > + && EVP_DecryptUpdate(ctx, NULL, &len, NULL, in->len) != 1) > + { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); > + return NGX_ERROR; > + } > + > + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { > + 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) != 1) { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); > + return NGX_ERROR; > + } > + > + out->len = len; > + > + if (EVP_DecryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); > + return NGX_ERROR; > + } > + > + out->len += len; > +#endif > + > + return NGX_OK; > +} > + > + > +ngx_int_t > +ngx_quic_crypto_seal(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_quic_crypto_ctx_t *ctx; > + > + ctx = s->ctx; > + > +#ifdef OPENSSL_IS_BORINGSSL > + 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) > + { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); > + return NGX_ERROR; > + } > +#else > + int len; > + > + if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, nonce) != 1) { > ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); > return NGX_ERROR; > } > > - if (EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE > + if (EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(ctx)) == EVP_CIPH_CCM_MODE > && EVP_EncryptUpdate(ctx, NULL, &len, NULL, in->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, 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; > } > @@ -538,7 +526,6 @@ ngx_quic_crypto_seal(const ngx_quic_ciph > 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; > } > @@ -549,21 +536,30 @@ ngx_quic_crypto_seal(const ngx_quic_ciph > out->data + out->len) > == 0) > { > - EVP_CIPHER_CTX_free(ctx); > ngx_ssl_error(NGX_LOG_INFO, log, 0, > "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_GET_TAG) failed"); > return NGX_ERROR; > } > > out->len += NGX_QUIC_TAG_LEN; > - > - EVP_CIPHER_CTX_free(ctx); > #endif > > return NGX_OK; > } Now that we have universal ngx_quic_crypto_open() which receives "enc", it's tempting more than ever to combine ngx_quic_crypto_seal() and ngx_quic_crypto_open() in a single function with "enc". Not a part of this work though. > +void > +ngx_quic_crypto_cleanup(ngx_quic_secret_t *s) > +{ Although we know these functions ignore a NULL argument, I think the code would still look better under "if (s->ctx) {}". > +#ifdef OPENSSL_IS_BORINGSSL > + EVP_AEAD_CTX_free(s->ctx); > +#else > + EVP_CIPHER_CTX_free(s->ctx); > +#endif > + s->ctx = NULL; > +} > + > + > static ngx_int_t > ngx_quic_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, > ngx_quic_secret_t *s, u_char *out, u_char *in) > @@ -666,6 +662,12 @@ ngx_quic_keys_set_encryption_secret(ngx_ > } > } > > + if (ngx_quic_crypto_init(ciphers.c, peer_secret, is_write, log) > + == NGX_ERROR) > + { > + return NGX_ERROR; > + } > + > return NGX_OK; > } > > @@ -675,10 +677,10 @@ ngx_quic_keys_available(ngx_quic_keys_t > enum ssl_encryption_level_t level, ngx_uint_t is_write) > { > if (is_write == 0) { > - return keys->secrets[level].client.key.len != 0; > + return keys->secrets[level].client.ctx != NULL; > } > > - return keys->secrets[level].server.key.len != 0; > + return keys->secrets[level].server.ctx != 0; Maybe != NULL ? > } > > > @@ -686,8 +688,13 @@ void > ngx_quic_keys_discard(ngx_quic_keys_t *keys, > enum ssl_encryption_level_t level) > { > - keys->secrets[level].client.key.len = 0; > - keys->secrets[level].server.key.len = 0; > + ngx_quic_secret_t *client, *server; > + > + client = &keys->secrets[level].client; > + server = &keys->secrets[level].server; > + > + ngx_quic_crypto_cleanup(client); > + ngx_quic_crypto_cleanup(server); > } > > > @@ -699,6 +706,9 @@ ngx_quic_keys_switch(ngx_connection_t *c > current = &keys->secrets[ssl_encryption_application]; > next = &keys->next_key; > > + ngx_quic_crypto_cleanup(¤t->client); > + ngx_quic_crypto_cleanup(¤t->server); > + > tmp = *current; > *current = *next; > *next = tmp; > @@ -762,6 +772,16 @@ ngx_quic_keys_update(ngx_event_t *ev) > } > } > > + if (ngx_quic_crypto_init(ciphers.c, &next->client, 0, c->log) == NGX_ERROR) > + { > + goto failed; > + } > + > + if (ngx_quic_crypto_init(ciphers.c, &next->server, 1, c->log) == NGX_ERROR) > + { > + goto failed; > + } > + > return; > > failed: > @@ -770,6 +790,26 @@ failed: > } > > > +void > +ngx_quic_keys_cleanup(void *data) > +{ > + ngx_quic_keys_t *keys = data; > + > + size_t i; > + ngx_quic_secrets_t *secrets; > + > + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { > + secrets = &keys->secrets[i]; > + ngx_quic_crypto_cleanup(&secrets->client); > + ngx_quic_crypto_cleanup(&secrets->server); > + } > + > + secrets = &keys->next_key; > + ngx_quic_crypto_cleanup(&secrets->client); > + ngx_quic_crypto_cleanup(&secrets->server); > +} > + > + > static ngx_int_t > ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) > { > @@ -801,8 +841,7 @@ ngx_quic_create_packet(ngx_quic_header_t > ngx_memcpy(nonce, secret->iv.data, secret->iv.len); > ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); > > - if (ngx_quic_crypto_seal(ciphers.c, secret, &out, > - nonce, &pkt->payload, &ad, pkt->log) > + if (ngx_quic_crypto_seal(secret, &out, nonce, &pkt->payload, &ad, pkt->log) > != NGX_OK) > { > return NGX_ERROR; > @@ -862,13 +901,18 @@ ngx_quic_create_retry_packet(ngx_quic_he > ngx_memcpy(secret.key.data, key, sizeof(key)); > secret.iv.len = NGX_QUIC_IV_LEN; > > - if (ngx_quic_crypto_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, > - pkt->log) > + if (ngx_quic_crypto_init(ciphers.c, &secret, 1, pkt->log) == NGX_ERROR) { > + return NGX_ERROR; > + } > + > + if (ngx_quic_crypto_seal(&secret, &itag, nonce, &in, &ad, pkt->log) > != NGX_OK) > { Need to call ngx_quic_crypto_cleanup() here. > return NGX_ERROR; > } > > + ngx_quic_crypto_cleanup(&secret); > + > res->len = itag.data + itag.len - start; > res->data = start; > > @@ -999,7 +1043,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, > u_char *p, *sample; > size_t len; > uint64_t pn, lpn; > - ngx_int_t pnl, rc; > + ngx_int_t pnl; > ngx_str_t in, ad; > ngx_uint_t key_phase; > ngx_quic_secret_t *secret; > @@ -1088,9 +1132,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, > pkt->payload.len = in.len - NGX_QUIC_TAG_LEN; > pkt->payload.data = pkt->plaintext + ad.len; > > - rc = ngx_quic_crypto_open(ciphers.c, secret, &pkt->payload, > - nonce, &in, &ad, pkt->log); > - if (rc != NGX_OK) { > + if (ngx_quic_crypto_open(secret, &pkt->payload, nonce, &in, &ad, pkt->log) > + != NGX_OK) > + { > return NGX_DECLINED; > } > > diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h > --- a/src/event/quic/ngx_event_quic_protection.h > +++ b/src/event/quic/ngx_event_quic_protection.h > @@ -26,8 +26,10 @@ > > #ifdef OPENSSL_IS_BORINGSSL > #define ngx_quic_cipher_t EVP_AEAD > +#define ngx_quic_crypto_ctx_t EVP_AEAD_CTX > #else > #define ngx_quic_cipher_t EVP_CIPHER > +#define ngx_quic_crypto_ctx_t EVP_CIPHER_CTX > #endif > > > @@ -48,6 +50,7 @@ typedef struct { > ngx_quic_md_t key; > ngx_quic_iv_t iv; > ngx_quic_md_t hp; > + ngx_quic_crypto_ctx_t *ctx; > } ngx_quic_secret_t; > > > @@ -100,14 +103,17 @@ void ngx_quic_keys_discard(ngx_quic_keys > enum ssl_encryption_level_t level); > void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); > void ngx_quic_keys_update(ngx_event_t *ev); > +void ngx_quic_keys_cleanup(void *data); > 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_crypto_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_crypto_init(const ngx_quic_cipher_t *cipher, > + ngx_quic_secret_t *s, ngx_int_t enc, ngx_log_t *log); > +ngx_int_t ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, > + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); > +void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s); > ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, > ngx_log_t *log); > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From mdounin at mdounin.ru Tue Sep 19 16:34:45 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 19 Sep 2023 19:34:45 +0300 Subject: [PATCH] Mail: allow auth to the proxy without auth to the backend In-Reply-To: References: Message-ID: Hello! On Tue, Sep 19, 2023 at 12:28:49PM +0200, Arnout Engelen wrote: > # HG changeset patch > # User Arnout Engelen > # Date 1695027670 -7200 > # Mon Sep 18 11:01:10 2023 +0200 > # Node ID 9606e589b9537495c0457383048ac6888be0e7b4 > # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa > Mail: allow auth to the proxy without auth to the backend > > Currently, when the client authenticates itself to the nginx > mail proxy, the mail proxy also authenticates itself to the > backend. > > I encountered a situation where I wanted the proxy to require > authentication, and forward the mail to a (local/firewalled) > mailserver that does not have authentication configured. I > created the patch below to support that. > > I'm providing this patch primarily for feedback at this point: > while it does work for my scenario and pass the nginx-tests, > it likely needs additional cleanup and testing. I'd like your > thoughs on whether this change makes sense in the first place, > and whether this is generally a reasonable approach - if so I'll > clean up the patch further. > > My approach is to allow the authentication server to return a > 'Auth-Method: none' header, in which case the proxy will not > attempt to authenticate to the backend but instead wait for > the 'MAIL FROM' from the client. > > You'll notice I've added a 'proxy_auth_method'. The reason I didn't > overwrite 'auth_method' is that 'auth_method' is also used to determine > whether to confirm the authentication to the client. Is that acceptable > from a binary compatibility perspective? > > Looking forward to hearing your thoughts! >From the description it is not clear why "proxy_smtp_auth off;" (which is the default and implies that nginx won't try to authenticate against SMTP backends) does not work for you. Could you please elaborate? [...] -- Maxim Dounin http://mdounin.ru/ From nginx at bzzt.net Tue Sep 19 16:59:23 2023 From: nginx at bzzt.net (Arnout Engelen) Date: Tue, 19 Sep 2023 18:59:23 +0200 Subject: [PATCH] Mail: allow auth to the proxy without auth to the backend In-Reply-To: References: Message-ID: <52339066-5caf-4957-bc42-082fbbaf3878@app.fastmail.com> On Tue, Sep 19, 2023, at 18:34, Maxim Dounin wrote: > On Tue, Sep 19, 2023 at 12:28:49PM +0200, Arnout Engelen wrote: > > # HG changeset patch > > # User Arnout Engelen > > # Date 1695027670 -7200 > > # Mon Sep 18 11:01:10 2023 +0200 > > # Node ID 9606e589b9537495c0457383048ac6888be0e7b4 > > # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa > > Mail: allow auth to the proxy without auth to the backend > > > > Currently, when the client authenticates itself to the nginx > > mail proxy, the mail proxy also authenticates itself to the > > backend. > > > > I encountered a situation where I wanted the proxy to require > > authentication, and forward the mail to a (local/firewalled) > > mailserver that does not have authentication configured. I > > created the patch below to support that. > > > > I'm providing this patch primarily for feedback at this point: > > while it does work for my scenario and pass the nginx-tests, > > it likely needs additional cleanup and testing. I'd like your > > thoughs on whether this change makes sense in the first place, > > and whether this is generally a reasonable approach - if so I'll > > clean up the patch further. > > > > My approach is to allow the authentication server to return a > > 'Auth-Method: none' header, in which case the proxy will not > > attempt to authenticate to the backend but instead wait for > > the 'MAIL FROM' from the client. > > > > You'll notice I've added a 'proxy_auth_method'. The reason I didn't > > overwrite 'auth_method' is that 'auth_method' is also used to determine > > whether to confirm the authentication to the client. Is that acceptable > > from a binary compatibility perspective? > > > > Looking forward to hearing your thoughts! > > From the description it is not clear why "proxy_smtp_auth off;" > (which is the default and implies that nginx won't try to > authenticate against SMTP backends) does not work for you. Could > you please elaborate? Ah, indeed I didn't describe that: I have two different backends, and the HTTP authentication server decides based on the account whether to proxy the mail to one (which requires authentication) or the other (which doesn't allow authentication). This does make me wonder whether adding that 'proxy_auth_method' field was really necessary, though, as you'd expect the "proxy_smtp_auth off" code path should need something similar. I'll look into that. Kind regards, Arnout -------------- next part -------------- An HTML attachment was scrubbed... URL: From arut at nginx.com Wed Sep 20 12:12:16 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 20 Sep 2023 16:12:16 +0400 Subject: [PATCH 6 of 8] QUIC: reusing crypto contexts for header protection In-Reply-To: References: Message-ID: <20230920121216.kpwwd2qo2xdzhmf4@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:13:58PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1694099424 -14400 > # Thu Sep 07 19:10:24 2023 +0400 > # Node ID cdc5b59309dbdc234c71e53fca142502884e6177 > # Parent 28f7491bc79771f9cfa882b1b5584fa48ea42e6b > QUIC: reusing crypto contexts for header protection. > > diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -28,8 +28,12 @@ static uint64_t ngx_quic_parse_pn(u_char > > static ngx_int_t ngx_quic_crypto_open(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_crypto_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_crypto_hp_init(const EVP_CIPHER *cipher, > + ngx_quic_secret_t *s, ngx_log_t *log); > +static ngx_int_t ngx_quic_crypto_hp(ngx_quic_secret_t *s, > + u_char *out, u_char *in, ngx_log_t *log); > +static void ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s); > > static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, > ngx_str_t *res); > @@ -192,6 +196,14 @@ ngx_quic_keys_set_initial_secret(ngx_qui > return NGX_ERROR; > } > > + if (ngx_quic_crypto_hp_init(ciphers.hp, client, log) == NGX_ERROR) { > + return NGX_ERROR; > + } > + > + if (ngx_quic_crypto_hp_init(ciphers.hp, server, log) == NGX_ERROR) { > + return NGX_ERROR; > + } Again, as before, in case of errors all ctx's created here should be freed, since we don't always have a cleanup handler for them, see ngx_quic_send_early_cc(). Also, in ngx_quic_send_early_cc() there's no cleanup at all, and ctx's will leak in case of successful creation. > return NGX_OK; > } > > @@ -561,53 +573,88 @@ ngx_quic_crypto_cleanup(ngx_quic_secret_ > > > static ngx_int_t > -ngx_quic_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, > - ngx_quic_secret_t *s, u_char *out, u_char *in) > +ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, ngx_quic_secret_t *s, > + ngx_log_t *log) > { > - 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); > + if (cipher == (EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { > + /* some bogus value to distinguish ChaCha20 cipher */ > + s->hp_ctx = (EVP_CIPHER_CTX *) cipher; What if we use NULL as the special value? > return NGX_OK; > } > #endif > > 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, s->hp.data, NULL) != 1) { > + EVP_CIPHER_CTX_free(ctx); > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); > return NGX_ERROR; > } > > - if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { > + s->hp_ctx = ctx; > + return NGX_OK; > +} > + > + > +static ngx_int_t > +ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, > + ngx_log_t *log) > +{ > + int outlen; > + EVP_CIPHER_CTX *ctx; > + u_char zero[NGX_QUIC_HP_LEN] = {0}; > + > + ctx = s->hp_ctx; > + > +#ifdef OPENSSL_IS_BORINGSSL > + uint32_t cnt; > + > + if (ctx == (EVP_CIPHER_CTX *) EVP_aead_chacha20_poly1305()) { > + ngx_memcpy(&cnt, in, sizeof(uint32_t)); > + CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); > + return NGX_OK; > + } > +#endif > + > + if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, in) != 1) { > ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); > - goto failed; > + return NGX_ERROR; > } > > if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { > ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); > - goto failed; > + return NGX_ERROR; > } > > 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; > + return NGX_ERROR; > } > > - EVP_CIPHER_CTX_free(ctx); > - > return NGX_OK; > +} > > -failed: > + > +static void > +ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s) > +{ > > - EVP_CIPHER_CTX_free(ctx); > +#ifdef OPENSSL_IS_BORINGSSL > + if (s->hp_ctx == (EVP_CIPHER_CTX *) EVP_aead_chacha20_poly1305()) { > + s->hp_ctx = NULL; > + return; > + } > +#endif > > - return NGX_ERROR; > + EVP_CIPHER_CTX_free(s->hp_ctx); > + s->hp_ctx = NULL; > } > > > @@ -668,6 +715,10 @@ ngx_quic_keys_set_encryption_secret(ngx_ > return NGX_ERROR; > } > > + if (ngx_quic_crypto_hp_init(ciphers.hp, peer_secret, log) == NGX_ERROR) { > + return NGX_ERROR; > + } > + > return NGX_OK; > } > > @@ -695,6 +746,9 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k > > ngx_quic_crypto_cleanup(client); > ngx_quic_crypto_cleanup(server); > + > + ngx_quic_crypto_hp_cleanup(client); > + ngx_quic_crypto_hp_cleanup(server); > } > > > @@ -747,11 +801,13 @@ ngx_quic_keys_update(ngx_event_t *ev) > next->client.key.len = current->client.key.len; > next->client.iv.len = NGX_QUIC_IV_LEN; > next->client.hp = current->client.hp; > + next->client.hp_ctx = current->client.hp_ctx; > > 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; > + next->server.hp_ctx = current->server.hp_ctx; > > ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", > &next->client.secret, ¤t->client.secret); > @@ -802,6 +858,9 @@ ngx_quic_keys_cleanup(void *data) > secrets = &keys->secrets[i]; > ngx_quic_crypto_cleanup(&secrets->client); > ngx_quic_crypto_cleanup(&secrets->server); > + > + ngx_quic_crypto_hp_cleanup(&secrets->client); > + ngx_quic_crypto_hp_cleanup(&secrets->server); > } > > secrets = &keys->next_key; > @@ -848,9 +907,7 @@ ngx_quic_create_packet(ngx_quic_header_t > } > > sample = &out.data[4 - pkt->num_len]; > - if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) > - != NGX_OK) > - { > + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { > return NGX_ERROR; > } > > @@ -1077,9 +1134,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, > > /* header protection */ > > - if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) > - != NGX_OK) > - { > + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { > return NGX_DECLINED; > } > > diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h > --- a/src/event/quic/ngx_event_quic_protection.h > +++ b/src/event/quic/ngx_event_quic_protection.h > @@ -51,6 +51,7 @@ typedef struct { > ngx_quic_iv_t iv; > ngx_quic_md_t hp; > ngx_quic_crypto_ctx_t *ctx; > + EVP_CIPHER_CTX *hp_ctx; > } ngx_quic_secret_t; > > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From arut at nginx.com Wed Sep 20 12:27:27 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Wed, 20 Sep 2023 16:27:27 +0400 Subject: [PATCH 7 of 8] QUIC: cleaned up now unused ngx_quic_ciphers() calls In-Reply-To: <8bd0104b7e6b658a1696.1694099639@enoparse.local> References: <8bd0104b7e6b658a1696.1694099639@enoparse.local> Message-ID: <20230920122727.oayt6lw2j4osggc7@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:13:59PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1694099425 -14400 > # Thu Sep 07 19:10:25 2023 +0400 > # Node ID 8bd0104b7e6b658a1696fe7f3e2f1868ac2ae1f9 > # Parent cdc5b59309dbdc234c71e53fca142502884e6177 > QUIC: cleaned up now unused ngx_quic_ciphers() calls. > > 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 > @@ -571,10 +571,9 @@ ngx_quic_compat_create_header(ngx_quic_c > 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]; > + ngx_str_t ad, out; > + ngx_quic_secret_t *secret; > + u_char nonce[NGX_QUIC_IV_LEN]; > > ad.data = res->data; > ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); > @@ -587,11 +586,6 @@ ngx_quic_compat_create_record(ngx_quic_c > "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); > diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -872,12 +872,11 @@ ngx_quic_keys_cleanup(void *data) > 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]; > + u_char *pnp, *sample; > + ngx_str_t ad, out; > + ngx_uint_t i; > + ngx_quic_secret_t *secret; > + 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); > @@ -890,11 +889,6 @@ ngx_quic_create_packet(ngx_quic_header_t > "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); > @@ -1097,20 +1091,14 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, > 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; > - 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; > - } > + u_char *p, *sample; > + size_t len; > + uint64_t pn, lpn; > + ngx_int_t pnl; > + ngx_str_t in, ad; > + ngx_uint_t key_phase; > + ngx_quic_secret_t *secret; > + uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; > > secret = &pkt->keys->secrets[pkt->level].client; > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks ok From arut at nginx.com Thu Sep 21 13:29:30 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Thu, 21 Sep 2023 17:29:30 +0400 Subject: [PATCH 8 of 8] QUIC: explicitly zero out unused keying material In-Reply-To: <813128cee322830435a9.1694099640@enoparse.local> References: <813128cee322830435a9.1694099640@enoparse.local> Message-ID: <20230921132930.6m2dmffrzywhe5ba@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:14:00PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1694099425 -14400 > # Thu Sep 07 19:10:25 2023 +0400 > # Node ID 813128cee322830435a95903993b17fb24683da7 > # Parent 8bd0104b7e6b658a1696fe7f3e2f1868ac2ae1f9 > QUIC: explicitly zero out unused keying material. > > 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 > @@ -245,15 +245,6 @@ ngx_quic_compat_set_encryption_secret(ng > return NGX_ERROR; > } > > - if (sizeof(peer_secret->secret.data) < secret_len) { > - ngx_log_error(NGX_LOG_ALERT, c->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; > > @@ -275,6 +266,9 @@ ngx_quic_compat_set_encryption_secret(ng > return NGX_ERROR; > } > > + ngx_explicit_memzero(secret_str.data, secret_str.len); > + ngx_explicit_memzero(peer_secret->key.data, peer_secret->key.len); > + > /* register cleanup handler once */ > > if (level == ssl_encryption_handshake) { > diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -719,6 +719,8 @@ ngx_quic_keys_set_encryption_secret(ngx_ > return NGX_ERROR; > } > > + ngx_explicit_memzero(peer_secret->key.data, peer_secret->key.len); > + > return NGX_OK; > } > > @@ -749,6 +751,12 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k > > ngx_quic_crypto_hp_cleanup(client); > ngx_quic_crypto_hp_cleanup(server); > + > + ngx_explicit_memzero(client->secret.data, client->secret.len); > + ngx_explicit_memzero(client->key.data, client->key.len); > + > + ngx_explicit_memzero(server->secret.data, server->secret.len); > + ngx_explicit_memzero(server->key.data, server->key.len); > } > > > @@ -838,6 +846,14 @@ ngx_quic_keys_update(ngx_event_t *ev) > goto failed; > } > > + ngx_explicit_memzero(current->client.secret.data, > + current->client.secret.len); > + ngx_explicit_memzero(current->server.secret.data, > + current->server.secret.len); > + > + ngx_explicit_memzero(next->client.key.data, next->client.key.len); > + ngx_explicit_memzero(next->server.key.data, next->server.key.len); > + > return; > > failed: > @@ -866,6 +882,12 @@ ngx_quic_keys_cleanup(void *data) > secrets = &keys->next_key; > ngx_quic_crypto_cleanup(&secrets->client); > ngx_quic_crypto_cleanup(&secrets->server); > + > + ngx_explicit_memzero(secrets->client.secret.data, > + secrets->client.secret.len); > + > + ngx_explicit_memzero(secrets->server.secret.data, > + secrets->server.secret.len); > } > > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Maybe also we need to zero out the secret in ngx_quic_compat_keylog_callback()? Also, this patch made me think about removing key and hp from ngx_quic_secret_t. Since we have ctx/hp_ctx now, we only need them when creating these contexts. This will reduce the amount of sensitive data we permanently store in memory. Attached is my effort towards making key and hp local. -- Roman Arutyunyan -------------- next part -------------- # HG changeset patch # User Roman Arutyunyan # Date 1695302414 -14400 # Thu Sep 21 17:20:14 2023 +0400 # Node ID bcc06b925738982fc6a4bae0c40c9ed7719ff86f # Parent 5458263534926aede9253751017a3537e18d56f0 [mq]: c8-up 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 @@ -229,6 +229,7 @@ ngx_quic_compat_set_encryption_secret(ng ngx_int_t key_len; ngx_str_t secret_str; ngx_uint_t i; + ngx_quic_md_t key; ngx_quic_hkdf_t seq[2]; ngx_quic_secret_t *peer_secret; ngx_quic_ciphers_t ciphers; @@ -245,13 +246,14 @@ ngx_quic_compat_set_encryption_secret(ng return NGX_ERROR; } - peer_secret->key.len = key_len; + 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[0], "tls13 key", &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++) { @@ -262,12 +264,14 @@ ngx_quic_compat_set_encryption_secret(ng ngx_quic_crypto_cleanup(peer_secret); - if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) { + if (ngx_quic_crypto_init(ciphers.c, peer_secret, &key, 1, c->log) + == NGX_ERROR) + { return NGX_ERROR; } ngx_explicit_memzero(secret_str.data, secret_str.len); - ngx_explicit_memzero(peer_secret->key.data, peer_secret->key.len); + ngx_explicit_memzero(key.data, key.len); /* register cleanup handler once */ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -30,7 +30,7 @@ static ngx_int_t ngx_quic_crypto_open(ng u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); static ngx_int_t ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, ngx_log_t *log); + ngx_quic_secret_t *s, ngx_quic_md_t *hp, ngx_log_t *log); static ngx_int_t ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, ngx_log_t *log); static void ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s); @@ -116,6 +116,8 @@ ngx_quic_keys_set_initial_secret(ngx_qui ngx_str_t iss; ngx_uint_t i; const EVP_MD *digest; + ngx_quic_md_t client_key, server_key; + ngx_quic_md_t client_hp, server_hp; ngx_quic_hkdf_t seq[8]; ngx_quic_secret_t *client, *server; ngx_quic_ciphers_t ciphers; @@ -159,24 +161,24 @@ ngx_quic_keys_set_initial_secret(ngx_qui 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_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_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[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[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[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); + 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) { @@ -188,20 +190,28 @@ ngx_quic_keys_set_initial_secret(ngx_qui return NGX_ERROR; } - if (ngx_quic_crypto_init(ciphers.c, client, 0, log) == NGX_ERROR) { + if (ngx_quic_crypto_init(ciphers.c, client, &client_key, 0, log) + == NGX_ERROR) + { return NGX_ERROR; } - if (ngx_quic_crypto_init(ciphers.c, server, 1, log) == NGX_ERROR) { + if (ngx_quic_crypto_init(ciphers.c, server, &server_key, 1, log) + == NGX_ERROR) + { ngx_quic_crypto_cleanup(client); return NGX_ERROR; } - if (ngx_quic_crypto_hp_init(ciphers.hp, client, log) == NGX_ERROR) { + if (ngx_quic_crypto_hp_init(ciphers.hp, client, &client_hp, log) + == NGX_ERROR) + { return NGX_ERROR; } - if (ngx_quic_crypto_hp_init(ciphers.hp, server, log) == NGX_ERROR) { + if (ngx_quic_crypto_hp_init(ciphers.hp, server, &server_hp, log) + == NGX_ERROR) + { return NGX_ERROR; } @@ -370,13 +380,13 @@ failed: ngx_int_t ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, - ngx_int_t enc, ngx_log_t *log) + ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log) { #ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *ctx; - ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + ctx = EVP_AEAD_CTX_new(cipher, key->data, key->len, EVP_AEAD_DEFAULT_TAG_LENGTH); if (ctx == NULL) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); @@ -408,7 +418,7 @@ ngx_quic_crypto_init(const ngx_quic_ciph return NGX_ERROR; } - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, s->iv.len, NULL) + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, NGX_QUIC_IV_LEN, NULL) == 0) { EVP_CIPHER_CTX_free(ctx); @@ -417,7 +427,7 @@ ngx_quic_crypto_init(const ngx_quic_ciph return NGX_ERROR; } - if (EVP_CipherInit_ex(ctx, NULL, NULL, s->key.data, NULL, enc) != 1) { + if (EVP_CipherInit_ex(ctx, NULL, NULL, key->data, NULL, enc) != 1) { EVP_CIPHER_CTX_free(ctx); ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); return NGX_ERROR; @@ -438,8 +448,8 @@ ngx_quic_crypto_open(ngx_quic_secret_t * ctx = s->ctx; #ifdef OPENSSL_IS_BORINGSSL - if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, - in->data, in->len, ad->data, ad->len) + if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, + NGX_QUIC_IV_LEN, in->data, in->len, ad->data, ad->len) != 1) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); @@ -504,8 +514,8 @@ ngx_quic_crypto_seal(ngx_quic_secret_t * ctx = s->ctx; #ifdef OPENSSL_IS_BORINGSSL - if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, - in->data, in->len, ad->data, ad->len) + if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, + NGX_QUIC_IV_LEN, in->data, in->len, ad->data, ad->len) != 1) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); @@ -577,7 +587,7 @@ ngx_quic_crypto_cleanup(ngx_quic_secret_ static ngx_int_t ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, ngx_quic_secret_t *s, - ngx_log_t *log) + ngx_quic_md_t *hp, ngx_log_t *log) { EVP_CIPHER_CTX *ctx; @@ -595,7 +605,7 @@ ngx_quic_crypto_hp_init(const EVP_CIPHER return NGX_ERROR; } - if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, NULL) != 1) { + if (EVP_EncryptInit_ex(ctx, cipher, NULL, hp->data, NULL) != 1) { EVP_CIPHER_CTX_free(ctx); ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); return NGX_ERROR; @@ -669,6 +679,7 @@ ngx_quic_keys_set_encryption_secret(ngx_ ngx_int_t key_len; ngx_str_t secret_str; ngx_uint_t i; + ngx_quic_md_t key, hp; ngx_quic_hkdf_t seq[3]; ngx_quic_secret_t *peer_secret; ngx_quic_ciphers_t ciphers; @@ -693,18 +704,17 @@ ngx_quic_keys_set_encryption_secret(ngx_ peer_secret->secret.len = secret_len; ngx_memcpy(peer_secret->secret.data, secret, secret_len); + peer_secret->iv.len = NGX_QUIC_IV_LEN; - peer_secret->key.len = key_len; - peer_secret->iv.len = NGX_QUIC_IV_LEN; - peer_secret->hp.len = key_len; + key.len = key_len; + 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[0], "tls13 quic key", &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); + ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &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) { @@ -712,17 +722,19 @@ ngx_quic_keys_set_encryption_secret(ngx_ } } - if (ngx_quic_crypto_init(ciphers.c, peer_secret, is_write, log) + if (ngx_quic_crypto_init(ciphers.c, peer_secret, &key, is_write, log) == NGX_ERROR) { return NGX_ERROR; } - if (ngx_quic_crypto_hp_init(ciphers.hp, peer_secret, log) == NGX_ERROR) { + if (ngx_quic_crypto_hp_init(ciphers.hp, peer_secret, &hp, log) == NGX_ERROR) + { return NGX_ERROR; } - ngx_explicit_memzero(peer_secret->key.data, peer_secret->key.len); + ngx_explicit_memzero(key.data, key.len); + ngx_explicit_memzero(hp.data, hp.len); return NGX_OK; } @@ -756,10 +768,7 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k ngx_quic_crypto_hp_cleanup(server); ngx_explicit_memzero(client->secret.data, client->secret.len); - ngx_explicit_memzero(client->key.data, client->key.len); - ngx_explicit_memzero(server->secret.data, server->secret.len); - ngx_explicit_memzero(server->key.data, server->key.len); } @@ -783,7 +792,9 @@ ngx_quic_keys_switch(ngx_connection_t *c void ngx_quic_keys_update(ngx_event_t *ev) { + ngx_int_t key_len; ngx_uint_t i; + ngx_quic_md_t client_key, server_key; ngx_quic_hkdf_t seq[6]; ngx_quic_keys_t *keys; ngx_connection_t *c; @@ -802,34 +813,34 @@ ngx_quic_keys_update(ngx_event_t *ev) c->log->action = "updating keys"; - if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) - == NGX_ERROR) - { + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, + ssl_encryption_application); + + if (key_len == NGX_ERROR) { goto failed; } + client_key.len = key_len; + server_key.len = key_len; + 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->client.hp_ctx = current->client.hp_ctx; 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; next->server.hp_ctx = current->server.hp_ctx; 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); + &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); + &server_key, &next->server.secret); ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", &next->server.iv, &next->server.secret); @@ -839,12 +850,14 @@ ngx_quic_keys_update(ngx_event_t *ev) } } - if (ngx_quic_crypto_init(ciphers.c, &next->client, 0, c->log) == NGX_ERROR) + if (ngx_quic_crypto_init(ciphers.c, &next->client, &client_key, 0, c->log) + == NGX_ERROR) { goto failed; } - if (ngx_quic_crypto_init(ciphers.c, &next->server, 1, c->log) == NGX_ERROR) + if (ngx_quic_crypto_init(ciphers.c, &next->server, &server_key, 1, c->log) + == NGX_ERROR) { goto failed; } @@ -854,8 +867,8 @@ ngx_quic_keys_update(ngx_event_t *ev) ngx_explicit_memzero(current->server.secret.data, current->server.secret.len); - ngx_explicit_memzero(next->client.key.data, next->client.key.len); - ngx_explicit_memzero(next->server.key.data, next->server.key.len); + ngx_explicit_memzero(client_key.data, client_key.len); + ngx_explicit_memzero(server_key.data, server_key.len); return; @@ -950,11 +963,12 @@ ngx_quic_create_retry_packet(ngx_quic_he { u_char *start; ngx_str_t ad, itag; + ngx_quic_md_t key; ngx_quic_secret_t secret; ngx_quic_ciphers_t ciphers; /* 5.8. Retry Packet Integrity */ - static u_char key[16] = + static u_char key_str[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"; @@ -975,11 +989,13 @@ ngx_quic_create_retry_packet(ngx_quic_he return NGX_ERROR; } - secret.key.len = sizeof(key); - ngx_memcpy(secret.key.data, key, sizeof(key)); + key.len = sizeof(key_str); + ngx_memcpy(key.data, key_str, sizeof(key_str)); secret.iv.len = NGX_QUIC_IV_LEN; - if (ngx_quic_crypto_init(ciphers.c, &secret, 1, pkt->log) == NGX_ERROR) { + if (ngx_quic_crypto_init(ciphers.c, &secret, &key, 1, pkt->log) + == NGX_ERROR) + { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -47,9 +47,7 @@ typedef struct { typedef struct { ngx_quic_md_t secret; - ngx_quic_md_t key; ngx_quic_iv_t iv; - ngx_quic_md_t hp; ngx_quic_crypto_ctx_t *ctx; EVP_CIPHER_CTX *hp_ctx; } ngx_quic_secret_t; @@ -111,7 +109,7 @@ void ngx_quic_compute_nonce(u_char *nonc 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_crypto_init(const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_int_t enc, ngx_log_t *log); + ngx_quic_secret_t *s, ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log); ngx_int_t ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s); From arut at nginx.com Thu Sep 21 13:29:55 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Thu, 21 Sep 2023 17:29:55 +0400 Subject: [PATCH 1 of 8] QUIC: split keys availability checks to read and write sides In-Reply-To: References: Message-ID: <20230921132955.vcpdivwd2z7yw5zf@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:13:53PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1693497250 -14400 > # Thu Aug 31 19:54:10 2023 +0400 > # Node ID be1862a28fd8575a88475215ccfce995e392dfab > # Parent daf8f5ba23d8e9955b22782d945f9c065f4b6baa > QUIC: split keys availability checks to read and write sides. > > Keys may be released by TLS stack in different time, so it makes sense > to check this independently as well. This allows to fine-tune what key > direction is used when checking keys availability. > > When discarding, server keys are now marked in addition to client keys. > > 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 > @@ -524,7 +524,7 @@ ngx_quic_close_connection(ngx_connection > for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { > ctx = &qc->send_ctx[i]; > > - if (!ngx_quic_keys_available(qc->keys, ctx->level)) { > + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { > continue; > } > > @@ -953,7 +953,7 @@ ngx_quic_handle_payload(ngx_connection_t > > c->log->action = "decrypting packet"; > > - if (!ngx_quic_keys_available(qc->keys, pkt->level)) { > + if (!ngx_quic_keys_available(qc->keys, pkt->level, 0)) { > ngx_log_error(NGX_LOG_INFO, c->log, 0, > "quic no %s keys, ignoring packet", > ngx_quic_level_name(pkt->level)); > @@ -1076,7 +1076,9 @@ ngx_quic_discard_ctx(ngx_connection_t *c > > qc = ngx_quic_get_connection(c); > > - if (!ngx_quic_keys_available(qc->keys, level)) { > + if (!ngx_quic_keys_available(qc->keys, level, 0) > + && !ngx_quic_keys_available(qc->keys, level, 1)) > + { > return; > } > > diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -672,9 +672,13 @@ ngx_quic_keys_set_encryption_secret(ngx_ > > ngx_uint_t > ngx_quic_keys_available(ngx_quic_keys_t *keys, > - enum ssl_encryption_level_t level) > + enum ssl_encryption_level_t level, ngx_uint_t is_write) > { > - return keys->secrets[level].client.key.len != 0; > + if (is_write == 0) { > + return keys->secrets[level].client.key.len != 0; > + } > + > + return keys->secrets[level].server.key.len != 0; > } > > > @@ -683,6 +687,7 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k > enum ssl_encryption_level_t level) > { > keys->secrets[level].client.key.len = 0; > + keys->secrets[level].server.key.len = 0; > } > > > diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h > --- a/src/event/quic/ngx_event_quic_protection.h > +++ b/src/event/quic/ngx_event_quic_protection.h > @@ -95,7 +95,7 @@ ngx_int_t ngx_quic_keys_set_encryption_s > 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); > + enum ssl_encryption_level_t level, ngx_uint_t is_write); > 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); > 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 > @@ -434,7 +434,7 @@ ngx_quic_crypto_input(ngx_connection_t * > } > > if (n <= 0 || SSL_in_init(ssl_conn)) { > - if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data) > + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data, 0) > && qc->client_tp_done) > { > if (ngx_quic_init_streams(c) != NGX_OK) { > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks ok From arut at nginx.com Thu Sep 21 13:30:47 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Thu, 21 Sep 2023 17:30:47 +0400 Subject: [PATCH 4 of 8] QUIC: renamed protection functions In-Reply-To: <24e5d652ecc861f0c686.1694099636@enoparse.local> References: <24e5d652ecc861f0c686.1694099636@enoparse.local> Message-ID: <20230921133047.qcxztlumk42zjy2n@N00W24XTQX> Hi, On Thu, Sep 07, 2023 at 07:13:56PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1694099421 -14400 > # Thu Sep 07 19:10:21 2023 +0400 > # Node ID 24e5d652ecc861f0c68607d20941abbf3726fdf1 > # Parent b05feba278a8b766cddd4cc35d73ff43e8d77092 > QUIC: renamed protection functions. > > Now these functions have names ngx_quic_crypto_XXX(): > > - ngx_quic_tls_open() -> ngx_quic_crypto_open() > - ngx_quic_tls_seal() -> ngx_quic_crypto_seal() > - ngx_quic_tls_hp() -> ngx_quic_crypto_hp() > > 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 > @@ -568,8 +568,8 @@ ngx_quic_compat_create_record(ngx_quic_c > 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) > + if (ngx_quic_crypto_seal(ciphers.c, secret, &out, > + nonce, &rec->payload, &ad, rec->log) > != NGX_OK) > { > return NGX_ERROR; > diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -26,10 +26,10 @@ static ngx_int_t ngx_hkdf_extract(u_char > 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, > +static ngx_int_t ngx_quic_crypto_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, > +static ngx_int_t ngx_quic_crypto_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, > @@ -344,7 +344,7 @@ failed: > > > static ngx_int_t > -ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, > +ngx_quic_crypto_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) > { > > @@ -449,7 +449,7 @@ ngx_quic_tls_open(const ngx_quic_cipher_ > > > ngx_int_t > -ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, > +ngx_quic_crypto_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) > { > > @@ -565,7 +565,7 @@ ngx_quic_tls_seal(const ngx_quic_cipher_ > > > static ngx_int_t > -ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, > +ngx_quic_crypto_hp(ngx_log_t *log, const EVP_CIPHER *cipher, > ngx_quic_secret_t *s, u_char *out, u_char *in) > { > int outlen; > @@ -801,15 +801,15 @@ ngx_quic_create_packet(ngx_quic_header_t > 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) > + if (ngx_quic_crypto_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) > + if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) > != NGX_OK) > { > return NGX_ERROR; > @@ -862,7 +862,8 @@ ngx_quic_create_retry_packet(ngx_quic_he > 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) > + if (ngx_quic_crypto_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, > + pkt->log) > != NGX_OK) > { > return NGX_ERROR; > @@ -1032,7 +1033,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, > > /* header protection */ > > - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) > + if (ngx_quic_crypto_hp(pkt->log, ciphers.hp, secret, mask, sample) > != NGX_OK) > { > return NGX_DECLINED; > @@ -1087,8 +1088,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, > pkt->payload.len = in.len - NGX_QUIC_TAG_LEN; > pkt->payload.data = pkt->plaintext + ad.len; > > - rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, > - nonce, &in, &ad, pkt->log); > + rc = ngx_quic_crypto_open(ciphers.c, secret, &pkt->payload, > + nonce, &in, &ad, pkt->log); > if (rc != NGX_OK) { > return NGX_DECLINED; > } > diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h > --- a/src/event/quic/ngx_event_quic_protection.h > +++ b/src/event/quic/ngx_event_quic_protection.h > @@ -105,7 +105,7 @@ ngx_int_t ngx_quic_decrypt(ngx_quic_head > 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_int_t ngx_quic_crypto_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, > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Looks ok From arut at nginx.com Thu Sep 21 15:40:32 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Thu, 21 Sep 2023 19:40:32 +0400 Subject: [PATCH 3 of 6] QUIC: call shutdown() callback only after handshake completion In-Reply-To: <8697F5F7-57CD-476E-8D43-63FCC23BBA05@nginx.com> References: <51166a8f35ba880415dd.1694686626@arut-laptop> <8697F5F7-57CD-476E-8D43-63FCC23BBA05@nginx.com> Message-ID: <20230921154032.mkfs2ndxgv7fcosd@N00W24XTQX> On Tue, Sep 19, 2023 at 01:59:27PM +0400, Sergey Kandaurov wrote: > > > On 14 Sep 2023, at 14:17, Roman Arutyunyan wrote: > > > > # HG changeset patch > > # User Roman Arutyunyan > > # Date 1694613709 -14400 > > # Wed Sep 13 18:01:49 2023 +0400 > > # Node ID 51166a8f35ba880415ddc2bf2745012a8d4cea34 > > # Parent 6d3ca6f8db357a1db267978f730875e51e87c608 > > QUIC: call shutdown() callback only after handshake completion. > > > > Previously the callback could be called while QUIC handshake was in progress > > and, what's more important, before the init() callback. Now it's postponed > > after init(). > > > > This change is a preparation to postponing HTTP/3 session creation to init(). > > > > 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 > > @@ -427,7 +427,7 @@ ngx_quic_input_handler(ngx_event_t *rev) > > return; > > } > > > > - if (!qc->closing && qc->conf->shutdown) { > > + if (!qc->closing && qc->streams.initialized && qc->conf->shutdown) { > > qc->conf->shutdown(c); > > Adding condition here will now prevent doing anything on graceful shutdown, > input handler will just return, connection will stuck for handshake_timeout. > I'd rather move it above, to handle similar to closing reusable connections: Nothing will be done if handshake is in progress, in which case the shutdown() handler will be called after init(), see the change below. > if (!ngx_exiting || !qc->streams.initialized) { > > > > } > > > > 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 > > @@ -620,6 +620,10 @@ ngx_quic_do_init_streams(ngx_connection_ > > } > > } > > > > + if (ngx_exiting && qc->conf->shutdown) { > > + qc->conf->shutdown(c); > > how this can be reached? > > > + } > > + > > for (q = ngx_queue_head(&qc->streams.uninitialized); > > q != ngx_queue_sentinel(&qc->streams.uninitialized); > > q = ngx_queue_next(q)) While the above change tries to be as graceful as possible, there can be another solution that's less complex. We can just terminate the connection with handshake in progress instead of waiting for its completion and sending GOAWAY. -- Roman Arutyunyan -------------- next part -------------- # HG changeset patch # User Roman Arutyunyan # Date 1695310358 -14400 # Thu Sep 21 19:32:38 2023 +0400 # Node ID ed9c0b01d341ac0cc13ab55b06d5a2cc3572d3fa # Parent 2e657cae3de01aa55079cc2e4d43215aeeccb324 QUIC: do not call shutdown() when handshake is in progress. Instead, when worker is shutting down and handshake is not yet completed, connection is terminated immediately. Previously the callback could be called while QUIC handshake was in progress and, what's more important, before the init() callback. Now it's postponed after init(). This change is a preparation to postponing HTTP/3 session creation to init(). 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 @@ -421,7 +421,7 @@ ngx_quic_input_handler(ngx_event_t *rev) if (c->close) { c->close = 0; - if (!ngx_exiting) { + if (!ngx_exiting || !qc->streams.initialized) { qc->error = NGX_QUIC_ERR_NO_ERROR; qc->error_reason = "graceful shutdown"; ngx_quic_close_connection(c, NGX_ERROR); From nojima at ynojima.com Fri Sep 22 06:58:41 2023 From: nojima at ynojima.com (Yusuke Nojima) Date: Fri, 22 Sep 2023 15:58:41 +0900 Subject: [PATCH] Improve performance when starting nginx with a lot of locations Message-ID: # HG changeset patch # User Yusuke Nojima # Date 1679555707 -32400 # Thu Mar 23 16:15:07 2023 +0900 # Node ID 6aac98fb135e47ca9cf7ad7d780cf4a10e9aa55c # Parent 8771d35d55d0a2b1cefaab04401d6f837f5a05a2 Improve performance when starting nginx with a lot of locations Our team has a configuration file with a very large number of locations, and we found that starting nginx with this file takes an unacceptable amount of time. After investigating the issue, we discovered that the root cause of the long startup time is the sorting of the location list. Currently, the sorting algorithm used in nginx is insertion sort, which requires O(n^2) time for n locations. We have modified the sorting algorithm to use merge sort instead, which has a time complexity of O(n log n). We have tested the modified code using micro-benchmarks and confirmed that the new algorithm improves nginx startup time significantly (shown below). We believe that this change would be valuable for other users who are experiencing similar issues. Table: nginx startup time in seconds n current patched 2000 0.033 0.018 4000 0.047 0.028 6000 0.062 0.038 8000 0.079 0.050 10000 0.091 0.065 12000 0.367 0.081 14000 0.683 0.086 16000 0.899 0.097 18000 1.145 0.110 20000 1.449 0.122 22000 1.650 0.137 24000 2.155 0.151 26000 3.096 0.155 28000 3.711 0.168 30000 3.539 0.184 32000 3.980 0.193 34000 4.543 0.208 36000 4.349 0.217 38000 5.021 0.229 40000 4.918 0.245 42000 4.835 0.256 44000 5.159 0.262 46000 5.802 0.331 48000 6.205 0.295 50000 5.701 0.308 52000 5.992 0.335 54000 6.561 0.323 56000 6.856 0.333 58000 6.515 0.347 60000 7.051 0.359 62000 6.956 0.377 64000 7.376 0.376 66000 7.506 0.404 68000 7.292 0.407 70000 7.422 0.461 72000 10.090 0.443 74000 18.505 0.463 76000 11.857 0.462 78000 9.752 0.470 80000 12.485 0.481 82000 11.027 0.498 84000 9.804 0.523 86000 8.482 0.515 88000 9.838 0.560 90000 12.341 0.546 92000 13.881 0.648 94000 8.309 0.635 96000 8.854 0.650 98000 12.871 0.674 100000 8.261 0.698 diff -r 8771d35d55d0 -r 6aac98fb135e src/core/ngx_queue.c --- a/src/core/ngx_queue.c Fri Mar 10 07:43:50 2023 +0300 +++ b/src/core/ngx_queue.c Thu Mar 23 16:15:07 2023 +0900 @@ -45,36 +45,103 @@ } -/* the stable insertion sort */ +/* merge queue2 into queue1. queue2 becomes empty after merge. */ + +static void +ngx_queue_merge(ngx_queue_t *queue1, ngx_queue_t *queue2, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) +{ + ngx_queue_t *p1, *p2; + + p1 = ngx_queue_head(queue1); + p2 = ngx_queue_head(queue2); + + while (p1 != ngx_queue_sentinel(queue1) + && p2 != ngx_queue_sentinel(queue2)) { + + if (cmp(p1, p2) > 0) { + ngx_queue_t *next, *prev; + + next = ngx_queue_next(p2); + ngx_queue_remove(p2); + prev = ngx_queue_prev(p1); + ngx_queue_insert_after(prev, p2); + p2 = next; + } else { + p1 = ngx_queue_next(p1); + } + } + if (p2 != ngx_queue_sentinel(queue2)) { + ngx_queue_add(queue1, queue2); + ngx_queue_init(queue2); + } +} + + +/* move all elements from src to dest. dest should be empty before call. */ + +static void +ngx_queue_move(ngx_queue_t *dest, ngx_queue_t *src) +{ + *dest = *src; + ngx_queue_init(src); + + if (dest->next == src) { + dest->next = dest; + } else { + dest->next->prev = dest; + } + if (dest->prev == src) { + dest->prev = dest; + } else { + dest->prev->next = dest; + } +} + + +/* the stable merge sort */ void ngx_queue_sort(ngx_queue_t *queue, ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) { - ngx_queue_t *q, *prev, *next; + ngx_queue_t merged[64], *p, *last; - q = ngx_queue_head(queue); - - if (q == ngx_queue_last(queue)) { + if (ngx_queue_head(queue) == ngx_queue_last(queue)) { return; } - for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) { + last = merged; - prev = ngx_queue_prev(q); - next = ngx_queue_next(q); + while (!ngx_queue_empty(queue)) { + /* + * Loop invariant: + * merged[i] must have exactly 0 or 2^i elements in sorted order. + * For each iteration, we take one element from the given queue and + * insert it into merged without violating the invariant condition. + */ - ngx_queue_remove(q); + ngx_queue_t carry, *h; + + h = ngx_queue_head(queue); + ngx_queue_remove(h); + ngx_queue_init(&carry); + ngx_queue_insert_head(&carry, h); - do { - if (cmp(prev, q) <= 0) { - break; - } + for (p = merged; p != last && !ngx_queue_empty(p); p++) { + ngx_queue_merge(p, &carry, cmp); + ngx_queue_move(&carry, p); + } + if (p == last) { + ngx_queue_init(last); + last++; + } + ngx_queue_move(p, &carry); + } - prev = ngx_queue_prev(prev); - - } while (prev != ngx_queue_sentinel(queue)); - - ngx_queue_insert_after(prev, q); + /* Merge all queues into one queue */ + for (p = merged + 1; p != last; p++) { + ngx_queue_merge(p, p-1, cmp); } + ngx_queue_move(queue, last-1); } From pluknet at nginx.com Fri Sep 22 11:23:22 2023 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 22 Sep 2023 15:23:22 +0400 Subject: [PATCH 3 of 6] QUIC: call shutdown() callback only after handshake completion In-Reply-To: <20230921154032.mkfs2ndxgv7fcosd@N00W24XTQX> References: <51166a8f35ba880415dd.1694686626@arut-laptop> <8697F5F7-57CD-476E-8D43-63FCC23BBA05@nginx.com> <20230921154032.mkfs2ndxgv7fcosd@N00W24XTQX> Message-ID: <20230922112322.hwy4uzt2e6mqkrxg@Y9MQ9X2QVV> On Thu, Sep 21, 2023 at 07:40:32PM +0400, Roman Arutyunyan wrote: > On Tue, Sep 19, 2023 at 01:59:27PM +0400, Sergey Kandaurov wrote: > > > > > On 14 Sep 2023, at 14:17, Roman Arutyunyan wrote: > > > > > > # HG changeset patch > > > # User Roman Arutyunyan > > > # Date 1694613709 -14400 > > > # Wed Sep 13 18:01:49 2023 +0400 > > > # Node ID 51166a8f35ba880415ddc2bf2745012a8d4cea34 > > > # Parent 6d3ca6f8db357a1db267978f730875e51e87c608 > > > QUIC: call shutdown() callback only after handshake completion. > > > > > > Previously the callback could be called while QUIC handshake was in progress > > > and, what's more important, before the init() callback. Now it's postponed > > > after init(). > > > > > > This change is a preparation to postponing HTTP/3 session creation to init(). > > > > > > 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 > > > @@ -427,7 +427,7 @@ ngx_quic_input_handler(ngx_event_t *rev) > > > return; > > > } > > > > > > - if (!qc->closing && qc->conf->shutdown) { > > > + if (!qc->closing && qc->streams.initialized && qc->conf->shutdown) { > > > qc->conf->shutdown(c); > > > > Adding condition here will now prevent doing anything on graceful shutdown, > > input handler will just return, connection will stuck for handshake_timeout. > > I'd rather move it above, to handle similar to closing reusable connections: > > Nothing will be done if handshake is in progress, in which case the shutdown() > handler will be called after init(), see the change below. > > > if (!ngx_exiting || !qc->streams.initialized) { > > > > > > > } > > > > > > 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 > > > @@ -620,6 +620,10 @@ ngx_quic_do_init_streams(ngx_connection_ > > > } > > > } > > > > > > + if (ngx_exiting && qc->conf->shutdown) { > > > + qc->conf->shutdown(c); > > > > how this can be reached? > > > > > + } > > > + > > > for (q = ngx_queue_head(&qc->streams.uninitialized); > > > q != ngx_queue_sentinel(&qc->streams.uninitialized); > > > q = ngx_queue_next(q)) > > While the above change tries to be as graceful as possible, there can be > another solution that's less complex. We can just terminate the connection > with handshake in progress instead of waiting for its completion and sending > GOAWAY. > > -- > Roman Arutyunyan > # HG changeset patch > # User Roman Arutyunyan > # Date 1695310358 -14400 > # Thu Sep 21 19:32:38 2023 +0400 > # Node ID ed9c0b01d341ac0cc13ab55b06d5a2cc3572d3fa > # Parent 2e657cae3de01aa55079cc2e4d43215aeeccb324 > QUIC: do not call shutdown() when handshake is in progress. > > Instead, when worker is shutting down and handshake is not yet completed, > connection is terminated immediately. > > Previously the callback could be called while QUIC handshake was in progress > and, what's more important, before the init() callback. Now it's postponed > after init(). > > This change is a preparation to postponing HTTP/3 session creation to init(). > > 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 > @@ -421,7 +421,7 @@ ngx_quic_input_handler(ngx_event_t *rev) > if (c->close) { > c->close = 0; > > - if (!ngx_exiting) { > + if (!ngx_exiting || !qc->streams.initialized) { > qc->error = NGX_QUIC_ERR_NO_ERROR; > qc->error_reason = "graceful shutdown"; > ngx_quic_close_connection(c, NGX_ERROR); Although this approach may look more abrupt to close connection for seemingly no reason, I think it is more safe, in terms of how long a client can control the lifetime of a shutting down worker process. Looks good for me. From vl at inspert.ru Fri Sep 22 12:44:08 2023 From: vl at inspert.ru (Vladimir Homutov) Date: Fri, 22 Sep 2023 15:44:08 +0300 Subject: [PATCH] QUIC openssl compat mode error handling Message-ID: # HG changeset patch # User Vladimir Khomutov # Date 1695386443 -10800 # Fri Sep 22 15:40:43 2023 +0300 # Node ID 974ba23e68909ba708616410aa77074213d4d1e5 # Parent 5741eddf82e826766cd0f5ec7c6fe383145ca581 QUIC: handle add_handhshake_data() callback errors in compat. The error may be triggered by incorrect transport parameter sent by client. The expected behaviour in this case is to close connection complaining about incorrect parameter. Currently the connection just times out. 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 @@ -408,7 +408,10 @@ ngx_quic_compat_message_callback(int wri "quic compat tx %s len:%uz ", ngx_quic_level_name(level), len); - (void) com->method->add_handshake_data(ssl, level, buf, len); + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { + ngx_post_event(&qc->close, &ngx_posted_events); + return; + } break; From arut at nginx.com Fri Sep 22 15:30:50 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 19:30:50 +0400 Subject: [PATCH] QUIC openssl compat mode error handling In-Reply-To: References: Message-ID: <20230922152711.amodnb2asarxhmev@N00W24XTQX> Hi Vladimir, On Fri, Sep 22, 2023 at 03:44:08PM +0300, Vladimir Homutov via nginx-devel wrote: > # HG changeset patch > # User Vladimir Khomutov > # Date 1695386443 -10800 > # Fri Sep 22 15:40:43 2023 +0300 > # Node ID 974ba23e68909ba708616410aa77074213d4d1e5 > # Parent 5741eddf82e826766cd0f5ec7c6fe383145ca581 > QUIC: handle add_handhshake_data() callback errors in compat. > > The error may be triggered by incorrect transport parameter sent by client. > The expected behaviour in this case is to close connection complaining > about incorrect parameter. Currently the connection just times out. > > 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 > @@ -408,7 +408,10 @@ ngx_quic_compat_message_callback(int wri > "quic compat tx %s len:%uz ", > ngx_quic_level_name(level), len); > > - (void) com->method->add_handshake_data(ssl, level, buf, len); > + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { > + ngx_post_event(&qc->close, &ngx_posted_events); > + return; > + } > > break; Thanks for the patch. Indeed, it's a simple way to handle errors in callbacks. I'd also handle the error in send_alert(), even though we don't generate any errors in it now. -- Roman Arutyunyan -------------- next part -------------- # HG changeset patch # User Vladimir Khomutov # Date 1695396237 -14400 # Fri Sep 22 19:23:57 2023 +0400 # Node ID 3db945fda515014d220151046d02f3960bcfca0a # Parent 32b5aaebcca51854de6e1f8a40798edb13662edb QUIC: handle callback errors in compat. The error may be triggered in add_handhshake_data() by incorrect transport parameter sent by client. The expected behaviour in this case is to close connection complaining about incorrect parameter. Currently the connection just times out. 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 @@ -408,7 +408,9 @@ ngx_quic_compat_message_callback(int wri "quic compat tx %s len:%uz ", ngx_quic_level_name(level), len); - (void) com->method->add_handshake_data(ssl, level, buf, len); + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { + goto failed; + } break; @@ -420,11 +422,19 @@ ngx_quic_compat_message_callback(int wri "quic compat %s alert:%ui len:%uz ", ngx_quic_level_name(level), alert, len); - (void) com->method->send_alert(ssl, level, alert); + if (com->method->send_alert(ssl, level, alert) != 1) { + goto failed; + } } break; } + + return; + +failed: + + ngx_post_event(&qc->close, &ngx_posted_events); } From arut at nginx.com Fri Sep 22 15:36:25 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 15:36:25 +0000 Subject: [nginx] QUIC: "handshake_timeout" configuration parameter. Message-ID: details: https://hg.nginx.org/nginx/rev/ad3d34ddfdcc branches: changeset: 9158:ad3d34ddfdcc user: Roman Arutyunyan date: Wed Sep 13 17:59:37 2023 +0400 description: QUIC: "handshake_timeout" configuration parameter. Previously QUIC did not have such parameter and handshake duration was controlled by HTTP/3. However that required creating and storing HTTP/3 session on first client datagram. Apparently there's no convenient way to store the session object until QUIC handshake is complete. In the followup patches session creation will be postponed to init() callback. diffstat: src/event/quic/ngx_event_quic.c | 6 ++++++ src/event/quic/ngx_event_quic.h | 3 ++- src/event/quic/ngx_event_quic_streams.c | 4 ++++ src/event/quic/ngx_event_quic_transport.c | 2 +- src/http/v3/ngx_http_v3_module.c | 8 ++++++-- src/http/v3/ngx_http_v3_request.c | 8 +------- 6 files changed, 20 insertions(+), 11 deletions(-) diffs (128 lines): diff -r daf8f5ba23d8 -r ad3d34ddfdcc src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic.c Wed Sep 13 17:59:37 2023 +0400 @@ -211,6 +211,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_qu qc = ngx_quic_get_connection(c); ngx_add_timer(c->read, qc->tp.max_idle_timeout); + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); + ngx_quic_connstate_dbg(c); c->read->handler = ngx_quic_input_handler; @@ -485,6 +487,10 @@ ngx_quic_close_connection(ngx_connection ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } + if (qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + if (rc == NGX_DONE) { /* diff -r daf8f5ba23d8 -r ad3d34ddfdcc src/event/quic/ngx_event_quic.h --- a/src/event/quic/ngx_event_quic.h Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic.h Wed Sep 13 17:59:37 2023 +0400 @@ -67,7 +67,8 @@ typedef struct { ngx_flag_t retry; ngx_flag_t gso_enabled; ngx_flag_t disable_active_migration; - ngx_msec_t timeout; + ngx_msec_t handshake_timeout; + ngx_msec_t idle_timeout; ngx_str_t host_key; size_t stream_buffer_size; ngx_uint_t max_concurrent_streams_bidi; diff -r daf8f5ba23d8 -r ad3d34ddfdcc src/event/quic/ngx_event_quic_streams.c --- a/src/event/quic/ngx_event_quic_streams.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic_streams.c Wed Sep 13 17:59:37 2023 +0400 @@ -630,6 +630,10 @@ ngx_quic_do_init_streams(ngx_connection_ qc->streams.initialized = 1; + if (!qc->closing && qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + return NGX_OK; } diff -r daf8f5ba23d8 -r ad3d34ddfdcc src/event/quic/ngx_event_quic_transport.c --- a/src/event/quic/ngx_event_quic_transport.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/event/quic/ngx_event_quic_transport.c Wed Sep 13 17:59:37 2023 +0400 @@ -1985,7 +1985,7 @@ ngx_quic_init_transport_params(ngx_quic_ * tp->preferred_address = NULL */ - tp->max_idle_timeout = qcf->timeout; + tp->max_idle_timeout = qcf->idle_timeout; tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; diff -r daf8f5ba23d8 -r ad3d34ddfdcc src/http/v3/ngx_http_v3_module.c --- a/src/http/v3/ngx_http_v3_module.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/http/v3/ngx_http_v3_module.c Wed Sep 13 17:59:37 2023 +0400 @@ -192,7 +192,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t * * h3scf->quic.host_key = { 0, NULL } * h3scf->quic.stream_reject_code_uni = 0; * h3scf->quic.disable_active_migration = 0; - * h3scf->quic.timeout = 0; + * h3scf->quic.idle_timeout = 0; * h3scf->max_blocked_streams = 0; */ @@ -223,7 +223,8 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c ngx_http_v3_srv_conf_t *prev = parent; ngx_http_v3_srv_conf_t *conf = child; - ngx_http_ssl_srv_conf_t *sscf; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; ngx_conf_merge_value(conf->enable, prev->enable, 1); @@ -281,6 +282,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c return NGX_CONF_ERROR; } + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); + conf->quic.handshake_timeout = cscf->client_header_timeout; + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); conf->quic.ssl = &sscf->ssl; diff -r daf8f5ba23d8 -r ad3d34ddfdcc src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c Fri Sep 01 20:31:46 2023 +0400 +++ b/src/http/v3/ngx_http_v3_request.c Wed Sep 13 17:59:37 2023 +0400 @@ -58,18 +58,15 @@ static const struct { 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) { @@ -78,10 +75,7 @@ ngx_http_v3_init_stream(ngx_connection_t return; } - h3c = hc->v3_session; - ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); - - h3scf->quic.timeout = clcf->keepalive_timeout; + h3scf->quic.idle_timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; } From arut at nginx.com Fri Sep 22 15:36:28 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 15:36:28 +0000 Subject: [nginx] HTTP/3: moved variable initialization. Message-ID: details: https://hg.nginx.org/nginx/rev/6d3ca6f8db35 branches: changeset: 9159:6d3ca6f8db35 user: Roman Arutyunyan date: Wed Sep 13 17:57:13 2023 +0400 description: HTTP/3: moved variable initialization. diffstat: src/http/v3/ngx_http_v3_request.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diffs (21 lines): diff -r ad3d34ddfdcc -r 6d3ca6f8db35 src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c Wed Sep 13 17:59:37 2023 +0400 +++ b/src/http/v3/ngx_http_v3_request.c Wed Sep 13 17:57:13 2023 +0400 @@ -67,7 +67,6 @@ ngx_http_v3_init_stream(ngx_connection_t hc->ssl = 1; clcf = ngx_http_get_module_loc_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) { @@ -75,7 +74,9 @@ ngx_http_v3_init_stream(ngx_connection_t return; } + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); h3scf->quic.idle_timeout = clcf->keepalive_timeout; + ngx_quic_run(c, &h3scf->quic); return; } From arut at nginx.com Fri Sep 22 15:36:31 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 15:36:31 +0000 Subject: [nginx] QUIC: do not call shutdown() when handshake is in progress. Message-ID: details: https://hg.nginx.org/nginx/rev/dd5fd5719027 branches: changeset: 9160:dd5fd5719027 user: Roman Arutyunyan date: Thu Sep 21 19:32:38 2023 +0400 description: QUIC: do not call shutdown() when handshake is in progress. Instead, when worker is shutting down and handshake is not yet completed, connection is terminated immediately. Previously the callback could be called while QUIC handshake was in progress and, what's more important, before the init() callback. Now it's postponed after init(). This change is a preparation to postponing HTTP/3 session creation to init(). diffstat: src/event/quic/ngx_event_quic.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 6d3ca6f8db35 -r dd5fd5719027 src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Wed Sep 13 17:57:13 2023 +0400 +++ b/src/event/quic/ngx_event_quic.c Thu Sep 21 19:32:38 2023 +0400 @@ -420,7 +420,7 @@ ngx_quic_input_handler(ngx_event_t *rev) if (c->close) { c->close = 0; - if (!ngx_exiting) { + if (!ngx_exiting || !qc->streams.initialized) { qc->error = NGX_QUIC_ERR_NO_ERROR; qc->error_reason = "graceful shutdown"; ngx_quic_close_connection(c, NGX_ERROR); From arut at nginx.com Fri Sep 22 15:36:34 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 15:36:34 +0000 Subject: [nginx] HTTP/3: postponed session creation to init() callback. Message-ID: details: https://hg.nginx.org/nginx/rev/4939fd04737f branches: changeset: 9161:4939fd04737f user: Roman Arutyunyan date: Thu Sep 14 14:13:43 2023 +0400 description: HTTP/3: postponed session creation to init() callback. Now the session object is assigned to c->data while ngx_http_connection_t object is referenced by its http_connection field, similar to ngx_http_v2_connection_t and ngx_http_request_t. The change allows to eliminate v3_session field from ngx_http_connection_t. The field was under NGX_HTTP_V3 macro, which was a source of binary compatibility problems when nginx/module is build with/without HTTP/3 support. Postponing is essential since c->data should retain the reference to ngx_http_connection_t object throughout QUIC handshake, because SSL callbacks ngx_http_ssl_servername() and ngx_http_ssl_alpn_select() rely on this. diffstat: src/http/ngx_http_request.h | 4 ---- src/http/v3/ngx_http_v3.c | 4 +++- src/http/v3/ngx_http_v3.h | 9 ++++++--- src/http/v3/ngx_http_v3_request.c | 9 ++++----- 4 files changed, 13 insertions(+), 13 deletions(-) diffs (89 lines): diff -r dd5fd5719027 -r 4939fd04737f src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h Thu Sep 21 19:32:38 2023 +0400 +++ b/src/http/ngx_http_request.h Thu Sep 14 14:13:43 2023 +0400 @@ -324,10 +324,6 @@ 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; diff -r dd5fd5719027 -r 4939fd04737f src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c Thu Sep 21 19:32:38 2023 +0400 +++ b/src/http/v3/ngx_http_v3.c Thu Sep 14 14:13:43 2023 +0400 @@ -30,6 +30,8 @@ ngx_http_v3_init_session(ngx_connection_ goto failed; } + h3c->http_connection = hc; + ngx_queue_init(&h3c->blocked); h3c->keepalive.log = c->log; @@ -48,7 +50,7 @@ ngx_http_v3_init_session(ngx_connection_ cln->handler = ngx_http_v3_cleanup_session; cln->data = h3c; - hc->v3_session = h3c; + c->data = h3c; return NGX_OK; diff -r dd5fd5719027 -r 4939fd04737f src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h Thu Sep 21 19:32:38 2023 +0400 +++ b/src/http/v3/ngx_http_v3.h Thu Sep 14 14:13:43 2023 +0400 @@ -78,11 +78,12 @@ #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 \ +#define ngx_http_v3_get_session(c) \ + ((ngx_http_v3_session_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_quic_get_connection(c) \ + (ngx_http_v3_get_session(c)->http_connection) #define ngx_http_v3_get_module_loc_conf(c, module) \ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ @@ -120,6 +121,8 @@ struct ngx_http_v3_parse_s { struct ngx_http_v3_session_s { + ngx_http_connection_t *http_connection; + ngx_http_v3_dynamic_table_t table; ngx_event_t keepalive; diff -r dd5fd5719027 -r 4939fd04737f src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c Thu Sep 21 19:32:38 2023 +0400 +++ b/src/http/v3/ngx_http_v3_request.c Thu Sep 14 14:13:43 2023 +0400 @@ -69,11 +69,6 @@ ngx_http_v3_init_stream(ngx_connection_t clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); if (c->quic == NULL) { - if (ngx_http_v3_init_session(c) != NGX_OK) { - ngx_http_close_connection(c); - return; - } - h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); h3scf->quic.idle_timeout = clcf->keepalive_timeout; @@ -113,6 +108,10 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + 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); From arut at nginx.com Fri Sep 22 15:36:37 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 15:36:37 +0000 Subject: [nginx] QUIC: simplified setting close timer when closing connection. Message-ID: details: https://hg.nginx.org/nginx/rev/f9845e4b5c14 branches: changeset: 9162:f9845e4b5c14 user: Roman Arutyunyan date: Thu Sep 14 14:15:20 2023 +0400 description: QUIC: simplified setting close timer when closing connection. Previously, the timer was never reset due to an explicit check. The check was added in 36b59521a41c as part of connection close simplification. The reason was to retain the earliest timeout. However, the timeouts are all the same while QUIC handshake is in progress and resetting the timer for the same value has no performance implications. After handshake completion there's only application level. The change removes the check. diffstat: src/event/quic/ngx_event_quic.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 4939fd04737f -r f9845e4b5c14 src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Thu Sep 14 14:13:43 2023 +0400 +++ b/src/event/quic/ngx_event_quic.c Thu Sep 14 14:15:20 2023 +0400 @@ -537,7 +537,7 @@ ngx_quic_close_connection(ngx_connection qc->error_level = ctx->level; (void) ngx_quic_send_cc(c); - if (rc == NGX_OK && !qc->close.timer_set) { + if (rc == NGX_OK) { ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); } } From arut at nginx.com Fri Sep 22 15:36:40 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 22 Sep 2023 15:36:40 +0000 Subject: [nginx] Modules compatibility: added QUIC to signature (ticket #2539). Message-ID: details: https://hg.nginx.org/nginx/rev/32b5aaebcca5 branches: changeset: 9163:32b5aaebcca5 user: Roman Arutyunyan date: Wed Sep 13 17:48:15 2023 +0400 description: Modules compatibility: added QUIC to signature (ticket #2539). Enabling QUIC changes ngx_connection_t layout, which is why it should be added to the signature. diffstat: src/core/ngx_module.h | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diffs (16 lines): diff -r f9845e4b5c14 -r 32b5aaebcca5 src/core/ngx_module.h --- a/src/core/ngx_module.h Thu Sep 14 14:15:20 2023 +0400 +++ b/src/core/ngx_module.h Wed Sep 13 17:48:15 2023 +0400 @@ -107,7 +107,12 @@ #endif #define NGX_MODULE_SIGNATURE_17 "0" + +#if (NGX_QUIC || NGX_COMPAT) +#define NGX_MODULE_SIGNATURE_18 "1" +#else #define NGX_MODULE_SIGNATURE_18 "0" +#endif #if (NGX_HAVE_OPENAT) #define NGX_MODULE_SIGNATURE_19 "1" From vl at inspert.ru Fri Sep 22 15:58:20 2023 From: vl at inspert.ru (Vladimir Homutov) Date: Fri, 22 Sep 2023 18:58:20 +0300 Subject: [PATCH] QUIC openssl compat mode error handling In-Reply-To: <20230922152711.amodnb2asarxhmev@N00W24XTQX> References: <20230922152711.amodnb2asarxhmev@N00W24XTQX> Message-ID: On Fri, Sep 22, 2023 at 07:30:50PM +0400, Roman Arutyunyan wrote: > Hi Vladimir, > > On Fri, Sep 22, 2023 at 03:44:08PM +0300, Vladimir Homutov via nginx-devel wrote: > > # HG changeset patch > > # User Vladimir Khomutov > > # Date 1695386443 -10800 > > # Fri Sep 22 15:40:43 2023 +0300 > > # Node ID 974ba23e68909ba708616410aa77074213d4d1e5 > > # Parent 5741eddf82e826766cd0f5ec7c6fe383145ca581 > > QUIC: handle add_handhshake_data() callback errors in compat. > > > > The error may be triggered by incorrect transport parameter sent by client. > > The expected behaviour in this case is to close connection complaining > > about incorrect parameter. Currently the connection just times out. > > > > 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 > > @@ -408,7 +408,10 @@ ngx_quic_compat_message_callback(int wri > > "quic compat tx %s len:%uz ", > > ngx_quic_level_name(level), len); > > > > - (void) com->method->add_handshake_data(ssl, level, buf, len); > > + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { > > + ngx_post_event(&qc->close, &ngx_posted_events); > > + return; > > + } > > > > break; > > Thanks for the patch. Indeed, it's a simple way to handle errors in callbacks. > I'd also handle the error in send_alert(), even though we don't generate any > errors in it now. Yes, although I was not sure if we need to close connection if we failed to send alert (but probably if we are sending it, everything is already bad enough). In either case, handling both cases similarly looks as a way to go. > > -- > Roman Arutyunyan > # HG changeset patch > # User Vladimir Khomutov > # Date 1695396237 -14400 > # Fri Sep 22 19:23:57 2023 +0400 > # Node ID 3db945fda515014d220151046d02f3960bcfca0a > # Parent 32b5aaebcca51854de6e1f8a40798edb13662edb > QUIC: handle callback errors in compat. > > The error may be triggered in add_handhshake_data() by incorrect transport > parameter sent by client. The expected behaviour in this case is to close > connection complaining about incorrect parameter. Currently the connection > just times out. > > 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 > @@ -408,7 +408,9 @@ ngx_quic_compat_message_callback(int wri > "quic compat tx %s len:%uz ", > ngx_quic_level_name(level), len); > > - (void) com->method->add_handshake_data(ssl, level, buf, len); > + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { > + goto failed; > + } > > break; > > @@ -420,11 +422,19 @@ ngx_quic_compat_message_callback(int wri > "quic compat %s alert:%ui len:%uz ", > ngx_quic_level_name(level), alert, len); > > - (void) com->method->send_alert(ssl, level, alert); > + if (com->method->send_alert(ssl, level, alert) != 1) { > + goto failed; > + } > } > > break; > } > + > + return; > + > +failed: > + > + ngx_post_event(&qc->close, &ngx_posted_events); > } > Looks good! From xeioex at nginx.com Fri Sep 22 19:59:38 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 22 Sep 2023 19:59:38 +0000 Subject: [njs] Version bump. Message-ID: details: https://hg.nginx.org/njs/rev/385f3bbbdca1 branches: changeset: 2202:385f3bbbdca1 user: Dmitry Volyntsev date: Thu Sep 21 16:11:20 2023 -0700 description: Version bump. diffstat: src/njs.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r a387eed79b90 -r 385f3bbbdca1 src/njs.h --- a/src/njs.h Tue Sep 12 11:57:00 2023 -0700 +++ b/src/njs.h Thu Sep 21 16:11:20 2023 -0700 @@ -11,8 +11,8 @@ #include -#define NJS_VERSION "0.8.1" -#define NJS_VERSION_NUMBER 0x000801 +#define NJS_VERSION "0.8.2" +#define NJS_VERSION_NUMBER 0x000802 #include From xeioex at nginx.com Fri Sep 22 19:59:40 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 22 Sep 2023 19:59:40 +0000 Subject: [njs] Shell: improved shell_test. Message-ID: details: https://hg.nginx.org/njs/rev/6a94953b4053 branches: changeset: 2203:6a94953b4053 user: Dmitry Volyntsev date: Wed Sep 20 19:19:58 2023 -0700 description: Shell: improved shell_test. Setting more appropriate env variables for libedit since we moved to libedit as a default readline library in 0.8.0. diffstat: auto/expect | 5 ++--- 1 files changed, 2 insertions(+), 3 deletions(-) diffs (15 lines): diff -r 385f3bbbdca1 -r 6a94953b4053 auto/expect --- a/auto/expect Thu Sep 21 16:11:20 2023 -0700 +++ b/auto/expect Wed Sep 20 19:19:58 2023 -0700 @@ -21,9 +21,8 @@ if [ $njs_found = yes -a $NJS_HAVE_READL cat << END >> $NJS_MAKEFILE shell_test: njs test/shell_test.exp - INPUTRC=test/inputrc PATH=$NJS_BUILD_DIR:\$(PATH) \ - LANG=en_US.UTF-8 TERM= \ - expect -f test/shell_test.exp + PATH=$NJS_BUILD_DIR:\$(PATH) LANG=C.UTF-8 TERM=screen \ + expect -f test/shell_test.exp END else From xeioex at nginx.com Fri Sep 22 19:59:41 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 22 Sep 2023 19:59:41 +0000 Subject: [njs] Shell: introduced error, info and warn methods for console object. Message-ID: details: https://hg.nginx.org/njs/rev/49f6f5c81a11 branches: changeset: 2204:49f6f5c81a11 user: Dmitry Volyntsev date: Wed Sep 20 21:48:41 2023 -0700 description: Shell: introduced error, info and warn methods for console object. diffstat: external/njs_shell.c | 74 ++++++++++++++++++++++++++++++++++++++++----------- test/shell_test.exp | 22 +++++++++----- 2 files changed, 72 insertions(+), 24 deletions(-) diffs (196 lines): diff -r 6a94953b4053 -r 49f6f5c81a11 external/njs_shell.c --- a/external/njs_shell.c Wed Sep 20 19:19:58 2023 -0700 +++ b/external/njs_shell.c Wed Sep 20 21:48:41 2023 -0700 @@ -122,7 +122,7 @@ static char *njs_completion_generator(co #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); + njs_uint_t nargs, njs_index_t magic, njs_value_t *retval); static njs_int_t njs_ext_console_time(njs_vm_t *vm, njs_value_t *args, 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, @@ -156,7 +156,33 @@ static njs_external_t njs_ext_console[] .enumerable = 1, .u.method = { .native = njs_ext_console_log, - .magic8 = 1, +#define NJS_LOG_DUMP 16 +#define NJS_LOG_MASK 15 + .magic8 = NJS_LOG_LEVEL_INFO | NJS_LOG_DUMP, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("error"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_log, + .magic8 = NJS_LOG_LEVEL_ERROR, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("info"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_log, + .magic8 = NJS_LOG_LEVEL_INFO, } }, @@ -168,6 +194,7 @@ static njs_external_t njs_ext_console[] .enumerable = 1, .u.method = { .native = njs_ext_console_log, + .magic8 = NJS_LOG_LEVEL_INFO, } }, @@ -201,6 +228,18 @@ static njs_external_t njs_ext_console[] } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("warn"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_ext_console_log, + .magic8 = NJS_LOG_LEVEL_WARN, + } + }, + }; @@ -1337,30 +1376,28 @@ next: 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) + njs_index_t magic, njs_value_t *retval) { - njs_str_t msg; - njs_uint_t n; + njs_str_t msg; + njs_uint_t n; + njs_log_level_t level; n = 1; + level = (njs_log_level_t) magic & NJS_LOG_MASK; while (n < nargs) { - if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1, indent) + if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1, + !!(magic & NJS_LOG_DUMP)) == NJS_ERROR) { return NJS_ERROR; } - njs_vm_log(vm, "%s", (n != 1) ? " " : ""); - njs_vm_log(vm, "%*s", msg.length, msg.start); + njs_vm_logger(vm, level, "%*s\n", msg.length, msg.start); n++; } - if (nargs > 1) { - njs_vm_log(vm, "\n"); - } - njs_value_undefined_set(retval); return NJS_OK; @@ -1604,11 +1641,16 @@ 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) { - if (level == NJS_LOG_LEVEL_ERROR) { - njs_stderror("%*s", length, start); - - } else { + switch (level) { + case NJS_LOG_LEVEL_INFO: njs_printf("%*s", length, start); + break; + case NJS_LOG_LEVEL_WARN: + njs_printf("W: %*s", length, start); + break; + case NJS_LOG_LEVEL_ERROR: + njs_printf("E: %*s", length, start); + break; } } diff -r 6a94953b4053 -r 49f6f5c81a11 test/shell_test.exp --- a/test/shell_test.exp Wed Sep 20 19:19:58 2023 -0700 +++ b/test/shell_test.exp Wed Sep 20 21:48:41 2023 -0700 @@ -252,9 +252,9 @@ njs_test { {"console.log(1)\r\n" "console.log(1)\r\n1\r\nundefined\r\n>> "} {"console.log(1, 'a')\r\n" - "console.log(1, 'a')\r\n1 a\r\nundefined\r\n>> "} + "console.log(1, 'a')\r\n1\r\na\r\nundefined\r\n>> "} {"print(1, 'a')\r\n" - "print(1, 'a')\r\n1 a\r\nundefined\r\n>> "} + "print(1, 'a')\r\n1\r\na\r\nundefined\r\n>> "} {"console.log('\\tабв\\nгд')\r\n" "console.log('\\\\tабв\\\\nгд')\r\n\tабв\r\nгд\r\nundefined\r\n>> "} {"console.dump()\r\n" @@ -262,7 +262,13 @@ njs_test { {"console.dump(1)\r\n" "console.dump(1)\r\n1\r\nundefined\r\n>> "} {"console.dump(1, 'a')\r\n" - "console.dump(1, 'a')\r\n1 a\r\nundefined\r\n>> "} + "console.dump(1, 'a')\r\n1\r\na\r\nundefined\r\n>> "} + {"console.error(42)\r\n" + "console.error(42)\r\nE: 42\r\nundefined\r\n>> "} + {"console.info(23)\r\n" + "console.info(23)\r\n23\r\nundefined\r\n>> "} + {"console.warn(37)\r\n" + "console.warn(37)\r\nW: 37\r\nundefined\r\n>> "} } # console.time* functions @@ -307,9 +313,9 @@ njs_test { njs_test { {"var print = console.log.bind(console); print(1, 'a', [1, 2])\r\n" - "1 a \\\[1,2]\r\nundefined\r\n>> "} + "1\r\na\r\n\\\[1,2]\r\nundefined\r\n>> "} {"var print = console.dump.bind(console); print(1, 'a', [1, 2])\r\n" - "1 a \\\[\r\n 1,\r\n 2\r\n]\r\nundefined\r\n>> "} + "1\r\na\r\n\\\[\r\n 1,\r\n 2\r\n]\r\nundefined\r\n>> "} {"var print = console.log.bind(console); print(console.a.a)\r\n" "TypeError: cannot get property \"a\" of undefined"} {"print(console.a.a)\r\n" @@ -318,8 +324,8 @@ njs_test { # Backtraces for external objects njs_test { - {"console.log(console.a.a)\r\n" - "console.log(console.a.a)\r\nThrown:\r\nTypeError:*at console.log (native)"} + {"console.info(console.a.a)\r\n" + "console.info(console.a.a)\r\nThrown:\r\nTypeError:*at console.info (native)"} } # dumper @@ -400,7 +406,7 @@ njs_test { njs_test { {"var t = setImmediate(console.log, 'a', 'aa')\r\n" - "undefined\r\na aa"} + "undefined\r\na\r\naa"} } njs_test { From xeioex at nginx.com Fri Sep 22 19:59:44 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 22 Sep 2023 19:59:44 +0000 Subject: [njs] Shell: simplified console.time()/timeEnd() pair. Message-ID: details: https://hg.nginx.org/njs/rev/fc98c27d3fc0 branches: changeset: 2205:fc98c27d3fc0 user: Dmitry Volyntsev date: Thu Sep 21 12:40:53 2023 -0700 description: Shell: simplified console.time()/timeEnd() pair. Using a queue instead of a hash here because we assume that the number of profile timers will be low. diffstat: external/njs_shell.c | 158 +++++++++++++++++++------------------------------- test/shell_test.exp | 2 + 2 files changed, 63 insertions(+), 97 deletions(-) diffs (256 lines): diff -r 49f6f5c81a11 -r fc98c27d3fc0 external/njs_shell.c --- a/external/njs_shell.c Wed Sep 20 21:48:41 2023 -0700 +++ b/external/njs_shell.c Thu Sep 21 12:40:53 2023 -0700 @@ -80,8 +80,9 @@ typedef struct { typedef struct { - njs_opaque_value_t name; + njs_str_t name; uint64_t time; + njs_queue_link_t link; } njs_timelabel_t; @@ -91,7 +92,7 @@ typedef struct { njs_lvlhsh_t events; /* njs_ev_t * */ njs_queue_t posted_events; - njs_lvlhsh_t labels; /* njs_timelabel_t */ + njs_queue_t labels; njs_completion_t completion; } njs_console_t; @@ -136,8 +137,6 @@ static void njs_console_clear_timer(njs_ 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); @@ -275,14 +274,6 @@ static const njs_lvlhsh_proto_t lvlhsh_ }; -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, @@ -840,8 +831,7 @@ njs_console_init(njs_vm_t *vm, njs_conso njs_lvlhsh_init(&console->events); njs_queue_init(&console->posted_events); - - njs_lvlhsh_init(&console->labels); + njs_queue_init(&console->labels); console->completion.completions = njs_vm_completions(vm, NULL); if (console->completion.completions == NULL) { @@ -1408,12 +1398,13 @@ 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) { - njs_int_t ret; - njs_str_t name; - njs_value_t *value; - njs_console_t *console; - njs_timelabel_t *label; - njs_lvlhsh_query_t lhq; + njs_int_t ret; + njs_str_t name; + njs_queue_t *labels; + njs_value_t *value; + njs_console_t *console; + njs_timelabel_t *label; + njs_queue_link_t *link; static const njs_str_t default_label = njs_str("default"); @@ -1441,40 +1432,31 @@ njs_ext_console_time(njs_vm_t *vm, njs_v njs_value_string_get(value, &name); } + labels = &console->labels; + link = njs_queue_first(labels); + + while (link != njs_queue_tail(labels)) { + label = njs_queue_link_data(link, njs_timelabel_t, link); + + if (njs_strstr_eq(&name, &label->name)) { + njs_vm_log(vm, "Timer \"%V\" already exists.\n", &name); + njs_value_undefined_set(retval); + return NJS_OK; + } + + link = njs_queue_next(link); + } + label = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_timelabel_t)); if (njs_slow_path(label == NULL)) { njs_vm_memory_error(vm); return NJS_ERROR; } - lhq.replace = 0; - lhq.key = name; - lhq.key_hash = njs_djb_hash(name.start, name.length); - lhq.value = label; - lhq.pool = njs_vm_memory_pool(vm); - lhq.proto = &njs_timelabel_hash_proto; - - ret = njs_lvlhsh_insert(&console->labels, &lhq); - - if (njs_fast_path(ret == NJS_OK)) { - (void) njs_vm_value_string_set(vm, njs_value_arg(&label->name), - name.start, name.length); + label->name = name; + label->time = njs_time(); - } else { - njs_mp_free(njs_vm_memory_pool(vm), label); - - if (njs_slow_path(ret == NJS_ERROR)) { - njs_vm_error(vm, "lvlhsh insert failed"); - - return NJS_ERROR; - } - - njs_vm_log(vm, "Timer \"%V\" already exists.\n", &name); - - label = lhq.value; - } - - label->time = njs_time(); + njs_queue_insert_tail(&console->labels, &label->link); njs_value_undefined_set(retval); @@ -1486,13 +1468,14 @@ 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) { - uint64_t ns, ms; - njs_int_t ret; - njs_str_t name; - njs_value_t *value; - njs_console_t *console; - njs_timelabel_t *label; - njs_lvlhsh_query_t lhq; + uint64_t ns, ms; + njs_int_t ret; + njs_str_t name; + njs_queue_t *labels; + njs_value_t *value; + njs_console_t *console; + njs_timelabel_t *label; + njs_queue_link_t *link; static const njs_str_t default_label = njs_str("default"); @@ -1522,37 +1505,35 @@ njs_ext_console_time_end(njs_vm_t *vm, n njs_value_string_get(value, &name); } - lhq.key = name; - lhq.key_hash = njs_djb_hash(name.start, name.length); - lhq.pool = njs_vm_memory_pool(vm); - lhq.proto = &njs_timelabel_hash_proto; - - ret = njs_lvlhsh_delete(&console->labels, &lhq); - - if (njs_fast_path(ret == NJS_OK)) { - - label = lhq.value; - - ns = ns - label->time; + labels = &console->labels; + link = njs_queue_first(labels); - ms = ns / 1000000; - ns = ns % 1000000; - - njs_vm_log(vm, "%V: %uL.%06uLms\n", &name, ms, ns); - - /* GC: release. */ - njs_mp_free(njs_vm_memory_pool(vm), label); - - } else { - if (ret == NJS_ERROR) { - njs_vm_error(vm, "lvlhsh delete failed"); - - return NJS_ERROR; + for ( ;; ) { + if (link == njs_queue_tail(labels)) { + njs_vm_log(vm, "Timer \"%V\" doesn’t exist.\n", &name); + njs_value_undefined_set(retval); + return NJS_OK; } - njs_vm_log(vm, "Timer \"%V\" doesn’t exist.\n", &name); + label = njs_queue_link_data(link, njs_timelabel_t, link); + + if (njs_strstr_eq(&name, &label->name)) { + njs_queue_remove(&label->link); + break; + } + + link = njs_queue_next(link); } + ns = ns - label->time; + + ms = ns / 1000000; + ns = ns % 1000000; + + njs_vm_log(vm, "%V: %uL.%06uLms\n", &name, ms, ns); + + njs_mp_free(njs_vm_memory_pool(vm), label); + njs_value_undefined_set(retval); return NJS_OK; @@ -1656,23 +1637,6 @@ njs_console_log(njs_vm_t *vm, njs_extern static njs_int_t -njs_timelabel_hash_test(njs_lvlhsh_query_t *lhq, void *data) -{ - njs_str_t str; - njs_timelabel_t *label; - - label = data; - njs_value_string_get(njs_value_arg(&label->name), &str); - - if (njs_strstr_eq(&lhq->key, &str)) { - return NJS_OK; - } - - return NJS_DECLINED; -} - - -static njs_int_t lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data) { njs_ev_t *ev; diff -r 49f6f5c81a11 -r fc98c27d3fc0 test/shell_test.exp --- a/test/shell_test.exp Wed Sep 20 21:48:41 2023 -0700 +++ b/test/shell_test.exp Thu Sep 21 12:40:53 2023 -0700 @@ -299,6 +299,8 @@ njs_test { "console.timeEnd()\r\nTimer \"default\" doesn’t exist."} {"console.timeEnd('abc')\r\n" "console.timeEnd('abc')\r\nTimer \"abc\" doesn’t exist."} + {"console.time('abc')\r\n" + "console.time('abc')\r\nundefined\r\n>> "} } njs_test { From thresh at nginx.com Fri Sep 22 22:12:23 2023 From: thresh at nginx.com (=?iso-8859-1?q?Konstantin_Pavlov?=) Date: Fri, 22 Sep 2023 15:12:23 -0700 Subject: [PATCH] Linux packages: removed Ubuntu 22.10 'kinetic' due to EOL Message-ID: <1ad61bfc7630adf1d646.1695420743@qgcd7xg9r9.olympus.f5net.com> # HG changeset patch # User Konstantin Pavlov # Date 1695420683 25200 # Fri Sep 22 15:11:23 2023 -0700 # Node ID 1ad61bfc7630adf1d6460cf84cec484de4017326 # Parent ac4191d05fdf12dbc977a3a26dfde2799d301283 Linux packages: removed Ubuntu 22.10 'kinetic' due to EOL. diff -r ac4191d05fdf -r 1ad61bfc7630 xml/en/linux_packages.xml --- a/xml/en/linux_packages.xml Thu Sep 14 21:20:14 2023 +0100 +++ b/xml/en/linux_packages.xml Fri Sep 22 15:11:23 2023 -0700 @@ -7,7 +7,7 @@
+ rev="90">
@@ -88,11 +88,6 @@ versions: -22.10 “kinetic” -x86_64, aarch64/arm64 - - - 23.04 “lunar” x86_64, aarch64/arm64 diff -r ac4191d05fdf -r 1ad61bfc7630 xml/ru/linux_packages.xml --- a/xml/ru/linux_packages.xml Thu Sep 14 21:20:14 2023 +0100 +++ b/xml/ru/linux_packages.xml Fri Sep 22 15:11:23 2023 -0700 @@ -7,7 +7,7 @@
+ rev="90">
@@ -88,11 +88,6 @@ -22.10 “kinetic” -x86_64, aarch64/arm64 - - - 23.04 “lunar” x86_64, aarch64/arm64 From mdounin at mdounin.ru Fri Sep 22 22:17:00 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 23 Sep 2023 01:17:00 +0300 Subject: [PATCH] Linux packages: removed Ubuntu 22.10 'kinetic' due to EOL In-Reply-To: <1ad61bfc7630adf1d646.1695420743@qgcd7xg9r9.olympus.f5net.com> References: <1ad61bfc7630adf1d646.1695420743@qgcd7xg9r9.olympus.f5net.com> Message-ID: Hello! On Fri, Sep 22, 2023 at 03:12:23PM -0700, Konstantin Pavlov wrote: > # HG changeset patch > # User Konstantin Pavlov > # Date 1695420683 25200 > # Fri Sep 22 15:11:23 2023 -0700 > # Node ID 1ad61bfc7630adf1d6460cf84cec484de4017326 > # Parent ac4191d05fdf12dbc977a3a26dfde2799d301283 > Linux packages: removed Ubuntu 22.10 'kinetic' due to EOL. > > diff -r ac4191d05fdf -r 1ad61bfc7630 xml/en/linux_packages.xml > --- a/xml/en/linux_packages.xml Thu Sep 14 21:20:14 2023 +0100 > +++ b/xml/en/linux_packages.xml Fri Sep 22 15:11:23 2023 -0700 > @@ -7,7 +7,7 @@ >
link="/en/linux_packages.html" > lang="en" > - rev="89"> > + rev="90"> > >
> > @@ -88,11 +88,6 @@ versions: > > > > -22.10 “kinetic” > -x86_64, aarch64/arm64 > - > - > - > 23.04 “lunar” > x86_64, aarch64/arm64 > > diff -r ac4191d05fdf -r 1ad61bfc7630 xml/ru/linux_packages.xml > --- a/xml/ru/linux_packages.xml Thu Sep 14 21:20:14 2023 +0100 > +++ b/xml/ru/linux_packages.xml Fri Sep 22 15:11:23 2023 -0700 > @@ -7,7 +7,7 @@ >
link="/ru/linux_packages.html" > lang="ru" > - rev="89"> > + rev="90"> > >
> > @@ -88,11 +88,6 @@ > > > > -22.10 “kinetic” > -x86_64, aarch64/arm64 > - > - > - > 23.04 “lunar” > x86_64, aarch64/arm64 > Looks fine. -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Sat Sep 23 00:39:18 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 23 Sep 2023 00:39:18 +0000 Subject: [njs] Modules: added debug log for vm cloning and destroying. Message-ID: details: https://hg.nginx.org/njs/rev/dbb011e433b2 branches: changeset: 2206:dbb011e433b2 user: Dmitry Volyntsev date: Fri Sep 22 13:00:04 2023 -0700 description: Modules: added debug log for vm cloning and destroying. diffstat: nginx/ngx_http_js_module.c | 6 ++++++ nginx/ngx_stream_js_module.c | 6 ++++++ 2 files changed, 12 insertions(+), 0 deletions(-) diffs (46 lines): diff -r fc98c27d3fc0 -r dbb011e433b2 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Thu Sep 21 12:40:53 2023 -0700 +++ b/nginx/ngx_http_js_module.c Fri Sep 22 13:00:04 2023 -0700 @@ -1366,6 +1366,9 @@ ngx_http_js_init_vm(ngx_http_request_t * return NGX_ERROR; } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js vm clone: %p from: %p", ctx->vm, jlcf->vm); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_ERROR; @@ -1425,6 +1428,9 @@ ngx_http_js_cleanup_ctx(void *data) ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http js vm destroy: %p", + ctx->vm); + njs_vm_destroy(ctx->vm); } diff -r fc98c27d3fc0 -r dbb011e433b2 nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Thu Sep 21 12:40:53 2023 -0700 +++ b/nginx/ngx_stream_js_module.c Fri Sep 22 13:00:04 2023 -0700 @@ -1030,6 +1030,9 @@ ngx_stream_js_init_vm(ngx_stream_session return NGX_ERROR; } + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream js vm clone: %p from: %p", ctx->vm, jscf->vm); + cln = ngx_pool_cleanup_add(s->connection->pool, 0); if (cln == NULL) { return NGX_ERROR; @@ -1107,6 +1110,9 @@ ngx_stream_js_cleanup(void *data) ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "pending events"); } + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream js vm destroy: %p", ctx->vm); + njs_vm_destroy(ctx->vm); } From xeioex at nginx.com Sat Sep 23 00:39:20 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 23 Sep 2023 00:39:20 +0000 Subject: [njs] Introduced API to work with external value pointers. Message-ID: details: https://hg.nginx.org/njs/rev/cf7e8f006bd8 branches: changeset: 2207:cf7e8f006bd8 user: Dmitry Volyntsev date: Fri Sep 22 13:00:05 2023 -0700 description: Introduced API to work with external value pointers. This allows to allocate the necessary context for external code on demand. diffstat: src/njs.h | 5 + src/njs_value.c | 25 ++++++++ src/test/njs_externals_test.c | 117 ++++++++++++++++++++++++++++++++++++++++++ src/test/njs_unit_test.c | 16 +++++- 4 files changed, 162 insertions(+), 1 deletions(-) diffs (265 lines): diff -r dbb011e433b2 -r cf7e8f006bd8 src/njs.h --- a/src/njs.h Fri Sep 22 13:00:04 2023 -0700 +++ b/src/njs.h Fri Sep 22 13:00:05 2023 -0700 @@ -532,12 +532,15 @@ NJS_EXPORT void njs_value_boolean_set(nj NJS_EXPORT void njs_value_number_set(njs_value_t *value, double num); NJS_EXPORT void njs_value_function_set(njs_value_t *value, njs_function_t *function); +NJS_EXPORT void njs_value_external_set(njs_value_t *value, + njs_external_ptr_t external); 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_external_ptr_t njs_value_external(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); @@ -555,6 +558,8 @@ NJS_EXPORT njs_int_t njs_value_is_valid_ NJS_EXPORT njs_int_t njs_value_is_string(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_object(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_error(const njs_value_t *value); +NJS_EXPORT njs_int_t njs_value_is_external(const njs_value_t *value, + njs_int_t proto_id); NJS_EXPORT njs_int_t njs_value_is_array(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_function(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_buffer(const njs_value_t *value); diff -r dbb011e433b2 -r cf7e8f006bd8 src/njs_value.c --- a/src/njs_value.c Fri Sep 22 13:00:04 2023 -0700 +++ b/src/njs_value.c Fri Sep 22 13:00:05 2023 -0700 @@ -466,6 +466,15 @@ njs_value_function_set(njs_value_t *valu } +void +njs_value_external_set(njs_value_t *value, njs_external_ptr_t external) +{ + njs_assert(njs_value_is_external(value, NJS_PROTO_ID_ANY)); + + njs_object_data(value) = external; +} + + uint8_t njs_value_bool(const njs_value_t *value) { @@ -504,6 +513,15 @@ njs_value_native_function(const njs_valu } +njs_external_ptr_t +njs_value_external(const njs_value_t *value) +{ + njs_assert(njs_value_is_external(value, NJS_PROTO_ID_ANY)); + + return njs_object_data(value); +} + + njs_int_t njs_value_is_null(const njs_value_t *value) { @@ -577,6 +595,13 @@ njs_value_is_error(const njs_value_t *va njs_int_t +njs_value_is_external(const njs_value_t *value, njs_int_t proto_id) +{ + return njs_is_object_data(value, njs_make_tag(proto_id)); +} + + +njs_int_t njs_value_is_array(const njs_value_t *value) { return njs_is_array(value); diff -r dbb011e433b2 -r cf7e8f006bd8 src/test/njs_externals_test.c --- a/src/test/njs_externals_test.c Fri Sep 22 13:00:04 2023 -0700 +++ b/src/test/njs_externals_test.c Fri Sep 22 13:00:05 2023 -0700 @@ -33,6 +33,7 @@ njs_int_t njs_array_buffer_detach(njs_vm static njs_int_t njs_external_r_proto_id; +static njs_int_t njs_external_null_proto_id; static njs_int_t njs_external_error_ctor_id; @@ -556,6 +557,67 @@ njs_unit_test_r_bind(njs_vm_t *vm, njs_v static njs_int_t +njs_unit_test_null_get(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) +{ + double *d; + njs_value_t *this; + + this = njs_argument(args, 0); + + if (!njs_value_is_external(this, njs_external_null_proto_id)) { + njs_type_error(vm, "\"this\" is not a null external"); + return NJS_ERROR; + } + + d = njs_value_external(this); + + if (d == NULL) { + njs_value_undefined_set(retval); + + } else { + njs_value_number_set(retval, *d); + } + + return NJS_OK; +} + + +static njs_int_t +njs_unit_test_null_set(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) +{ + double *d; + njs_value_t *this; + + this = njs_argument(args, 0); + + if (!njs_value_is_external(this, njs_external_null_proto_id)) { + njs_type_error(vm, "\"this\" is not a null external"); + return NJS_ERROR; + } + + d = njs_value_external(this); + + if (d == NULL) { + d = njs_mp_alloc(vm->mem_pool, sizeof(double)); + if (d == NULL) { + njs_memory_error(vm); + return NJS_ERROR; + } + } + + *d = njs_value_number(njs_arg(args, nargs, 1)); + + njs_value_external_set(this, d); + + njs_value_undefined_set(retval); + + return NJS_OK; +} + + +static njs_int_t njs_unit_test_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { @@ -1043,6 +1105,40 @@ static njs_external_t njs_unit_test_r_e }; +static njs_external_t njs_unit_test_null_external[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "Null", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("get"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_unit_test_null_get, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("set"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_unit_test_null_set, + } + }, + +}; + static njs_external_t njs_unit_test_ctor_props[] = { @@ -1170,6 +1266,7 @@ njs_externals_init_internal(njs_vm_t *vm njs_unit_test_prop_t *prop; static const njs_str_t external_ctor = njs_str("ExternalConstructor"); + static const njs_str_t external_null = njs_str("ExternalNull"); static const njs_str_t external_error = njs_str("ExternalError"); if (shared) { @@ -1195,6 +1292,26 @@ njs_externals_init_internal(njs_vm_t *vm return NJS_ERROR; } + njs_external_null_proto_id = njs_vm_external_prototype(vm, + njs_unit_test_null_external, + njs_nitems(njs_unit_test_null_external)); + if (njs_slow_path(njs_external_null_proto_id < 0)) { + njs_printf("njs_vm_external_prototype() failed\n"); + return NJS_ERROR; + } + + ret = njs_vm_external_create(vm, njs_value_arg(&value), + njs_external_null_proto_id, NULL, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_vm_bind(vm, &external_null, njs_value_arg(&value), 1); + if (njs_slow_path(ret != NJS_OK)) { + njs_printf("njs_vm_bind() failed\n"); + return NJS_ERROR; + } + njs_external_error_ctor_id = njs_vm_external_constructor(vm, &external_error, njs_error_constructor, njs_unit_test_ctor_props, diff -r dbb011e433b2 -r cf7e8f006bd8 src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri Sep 22 13:00:04 2023 -0700 +++ b/src/test/njs_unit_test.c Fri Sep 22 13:00:05 2023 -0700 @@ -22637,7 +22637,8 @@ static njs_unit_test_t njs_externals_te #endif { njs_str("Object.keys(this).sort()"), - njs_str("$262,$r,$r2,$r3,$shared,ExternalConstructor,ExternalError," NCRYPTO "global,njs,process") }, + njs_str("$262,$r,$r2,$r3,$shared,ExternalConstructor,ExternalError," + "ExternalNull," NCRYPTO "global,njs,process") }, { njs_str("Object.getOwnPropertySymbols($r2)[0] == Symbol.toStringTag"), njs_str("true") }, @@ -22924,6 +22925,19 @@ static njs_unit_test_t njs_shared_test[ { njs_str("var sum = new Function('a, b', 'return a + b');" "sum(2, 4);"), njs_str("6") }, + + { njs_str("ExternalNull.get()"), + njs_str("undefined") }, + + { njs_str("ExternalNull.set(37); ExternalNull.get()"), + njs_str("37") }, + + { njs_str("ExternalNull.set(23); ExternalNull.set(37); ExternalNull.get()"), + njs_str("37") }, + + { njs_str("var v = Math.round(Math.random() * 1000); ExternalNull.set(v);" + "ExternalNull.get() == v"), + njs_str("true") }, }; From xeioex at nginx.com Sat Sep 23 00:39:22 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 23 Sep 2023 00:39:22 +0000 Subject: [njs] Modules: introduced console object. Message-ID: details: https://hg.nginx.org/njs/rev/ed935fa4805b branches: changeset: 2208:ed935fa4805b user: Dmitry Volyntsev date: Fri Sep 22 13:00:06 2023 -0700 description: Modules: introduced console object. The following methods were added: dump(), error(), info(), log(), time(), timeEnd(), warn(). diffstat: nginx/ngx_js.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++++-- nginx/t/js_console.t | 149 ++++++++++++++++++++ 2 files changed, 499 insertions(+), 15 deletions(-) diffs (592 lines): diff -r cf7e8f006bd8 -r ed935fa4805b nginx/ngx_js.c --- a/nginx/ngx_js.c Fri Sep 22 13:00:05 2023 -0700 +++ b/nginx/ngx_js.c Fri Sep 22 13:00:06 2023 -0700 @@ -11,6 +11,18 @@ #include "ngx_js.h" +typedef struct { + ngx_queue_t labels; +} ngx_js_console_t; + + +typedef struct { + njs_str_t name; + uint64_t time; + ngx_queue_t queue; +} ngx_js_timelabel_t; + + 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, @@ -27,9 +39,14 @@ static njs_int_t ngx_js_ext_version(njs_ njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t ngx_js_ext_worker_id(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_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 ngx_js_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static void ngx_js_cleanup_vm(void *data); static njs_int_t ngx_js_core_init(njs_vm_t *vm); +static uint64_t ngx_js_monotonic_time(void); static njs_external_t ngx_js_ext_global_shared[] = { @@ -197,6 +214,103 @@ static njs_external_t ngx_js_ext_core[] }; +static njs_external_t ngx_js_ext_console[] = { + + { + .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("dump"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_log, +#define NGX_JS_LOG_DUMP 16 +#define NGX_JS_LOG_MASK 15 + .magic8 = NGX_LOG_INFO | NGX_JS_LOG_DUMP, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("error"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_log, + .magic8 = NGX_LOG_ERR, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("info"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_log, + .magic8 = NGX_LOG_INFO, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("log"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_log, + .magic8 = NGX_LOG_INFO, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("time"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_console_time, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("timeEnd"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_console_time_end, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("warn"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = ngx_js_ext_log, + .magic8 = NGX_LOG_WARN, + } + }, + +}; + + njs_module_t ngx_js_ngx_module = { .name = njs_str("ngx"), .preinit = NULL, @@ -210,6 +324,9 @@ njs_module_t *njs_js_addon_modules_share }; +static njs_int_t ngx_js_console_proto_id; + + ngx_int_t ngx_js_call(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, njs_opaque_value_t *args, njs_uint_t nargs) @@ -345,6 +462,26 @@ ngx_js_core_init(njs_vm_t *vm) return NJS_ERROR; } + ngx_js_console_proto_id = njs_vm_external_prototype(vm, ngx_js_ext_console, + njs_nitems(ngx_js_ext_console)); + if (ngx_js_console_proto_id < 0) { + return NJS_ERROR; + } + + ret = njs_vm_external_create(vm, njs_value_arg(&value), + ngx_js_console_proto_id, NULL, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + name.length = 7; + name.start = (u_char *) "console"; + + ret = njs_vm_bind(vm, &name, njs_value_arg(&value), 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + return NJS_OK; } @@ -509,14 +646,13 @@ ngx_js_ext_worker_id(njs_vm_t *vm, njs_o 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) + njs_index_t magic, njs_value_t *retval) { - char *p; - ngx_int_t lvl; - njs_str_t msg; - njs_value_t *value; - ngx_connection_t *c; - ngx_log_handler_pt handler; + char *p; + ngx_int_t lvl; + njs_str_t msg; + njs_uint_t n; + njs_log_level_t level; p = njs_vm_external(vm, NJS_PROTO_ID_ANY, njs_argument(args, 0)); if (p == NULL) { @@ -524,7 +660,7 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t return NJS_ERROR; } - value = njs_arg(args, nargs, (level != 0) ? 1 : 2); + level = (njs_log_level_t) magic & NGX_JS_LOG_MASK; if (level == 0) { if (ngx_js_integer(vm, njs_arg(args, nargs, 1), &lvl) != NGX_OK) { @@ -532,20 +668,196 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t } level = lvl; + n = 2; + + } else { + n = 1; + } + + for (; n < nargs; n++) { + if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1, + !!(magic & NGX_JS_LOG_DUMP)) + == NJS_ERROR) + { + return NJS_ERROR; + } + + ngx_js_logger(vm, p, level, msg.start, msg.length); } - if (ngx_js_string(vm, value, &msg) != NGX_OK) { + njs_value_undefined_set(retval); + + return NJS_OK; +} + + +static njs_int_t +ngx_js_ext_console_time(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 name; + ngx_queue_t *labels, *q; + njs_value_t *value, *this; + ngx_js_console_t *console; + ngx_js_timelabel_t *label; + + static const njs_str_t default_label = njs_str("default"); + + this = njs_argument(args, 0); + + if (njs_slow_path(!njs_value_is_external(this, ngx_js_console_proto_id))) { + njs_vm_type_error(vm, "\"this\" is not a console external"); return NJS_ERROR; } - c = ngx_external_connection(vm, p); - handler = c->log->handler; - c->log->handler = NULL; + name = default_label; + + value = njs_arg(args, nargs, 1); + + if (njs_slow_path(!njs_value_is_string(value))) { + if (!njs_value_is_undefined(value)) { + ret = njs_value_to_string(vm, value, value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_value_string_get(value, &name); + } + + } else { + njs_value_string_get(value, &name); + } + + console = njs_value_external(this); + + if (console == NULL) { + console = njs_mp_alloc(njs_vm_memory_pool(vm), + sizeof(ngx_js_console_t)); + if (console == NULL) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + ngx_queue_init(&console->labels); + + njs_value_external_set(this, console); + } + + labels = &console->labels; + + for (q = ngx_queue_head(labels); + q != ngx_queue_sentinel(labels); + q = ngx_queue_next(q)) + { + label = ngx_queue_data(q, ngx_js_timelabel_t, queue); + + if (njs_strstr_eq(&name, &label->name)) { + njs_vm_log(vm, "Timer \"%V\" already exists.\n", &name); + njs_value_undefined_set(retval); + return NJS_OK; + } + } + + label = njs_mp_alloc(njs_vm_memory_pool(vm), + sizeof(ngx_js_timelabel_t) + name.length); + if (njs_slow_path(label == NULL)) { + njs_vm_memory_error(vm); + return NJS_ERROR; + } + + label->name.length = name.length; + label->name.start = (u_char *) label + sizeof(ngx_js_timelabel_t); + memcpy(label->name.start, name.start, name.length); + + label->time = ngx_js_monotonic_time(); + + ngx_queue_insert_tail(&console->labels, &label->queue); + + njs_value_undefined_set(retval); + + return NJS_OK; +} + - ngx_log_error((ngx_uint_t) level, c->log, 0, "js: %*s", - msg.length, msg.start); +static njs_int_t +ngx_js_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + uint64_t ns, ms; + njs_int_t ret; + njs_str_t name; + ngx_queue_t *labels, *q; + njs_value_t *value, *this; + ngx_js_console_t *console; + ngx_js_timelabel_t *label; + + static const njs_str_t default_label = njs_str("default"); + + ns = ngx_js_monotonic_time(); + + this = njs_argument(args, 0); + + if (njs_slow_path(!njs_value_is_external(this, ngx_js_console_proto_id))) { + njs_vm_type_error(vm, "\"this\" is not a console external"); + return NJS_ERROR; + } + + name = default_label; + + value = njs_arg(args, nargs, 1); + + if (njs_slow_path(!njs_value_is_string(value))) { + if (!njs_value_is_undefined(value)) { + ret = njs_value_to_string(vm, value, value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_value_string_get(value, &name); + } - c->log->handler = handler; + } else { + njs_value_string_get(value, &name); + } + + console = njs_value_external(this); + if (njs_slow_path(console == NULL)) { + goto not_found; + } + + labels = &console->labels; + q = ngx_queue_head(labels); + + for ( ;; ) { + if (q == ngx_queue_sentinel(labels)) { + goto not_found; + } + + label = ngx_queue_data(q, ngx_js_timelabel_t, queue); + + if (njs_strstr_eq(&name, &label->name)) { + ngx_queue_remove(&label->queue); + break; + } + + q = ngx_queue_next(q); + } + + ns = ns - label->time; + + ms = ns / 1000000; + ns = ns % 1000000; + + njs_vm_log(vm, "%V: %uL.%06uLms\n", &name, ms, ns); + + njs_value_undefined_set(retval); + + return NJS_OK; + +not_found: + + njs_vm_log(vm, "Timer \"%V\" doesn't exist.\n", &name); njs_value_undefined_set(retval); @@ -1308,3 +1620,26 @@ ngx_js_merge_conf(ngx_conf_t *cf, void * return NGX_CONF_OK; #endif } + + +static uint64_t +ngx_js_monotonic_time(void) +{ +#if (NGX_HAVE_CLOCK_MONOTONIC) + struct timespec ts; + +#if defined(CLOCK_MONOTONIC_FAST) + clock_gettime(CLOCK_MONOTONIC_FAST, &ts); +#else + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif + + return (uint64_t) ts.tv_sec * 1000000000 + ts.tv_nsec; +#else + struct timeval tv; + + gettimeofday(&tv, NULL); + + return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000; +#endif +} diff -r cf7e8f006bd8 -r ed935fa4805b nginx/t/js_console.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nginx/t/js_console.t Fri Sep 22 13:00:06 2023 -0700 @@ -0,0 +1,149 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, console object. + +############################################################################### + +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/) + ->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 /dump { + js_content test.dump; + } + + location /error { + js_content test.error; + } + + location /info { + js_content test.info; + } + + location /log { + js_content test.log; + } + + location /time { + js_content test.time; + } + + location /time_test { + js_content test.time_test; + } + + location /warn { + js_content test.warn; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs console')->plan(7); + +############################################################################### + +http_get('/dump?data=eyJhIjpbMiwzXX0'); +http_get('/error?data=IldBS0Ei'); +http_get('/info?data=IkJBUiI'); +http_get('/log?data=eyJhIjpbIkIiLCJDIl19'); +http_get('/time?delay=7&timer=foo'); +http_get('/time_test'); +http_get('/warn?data=IkZPTyI'); + +$t->stop(); + +like($t->read_file('error.log'), qr/\[error\].*js: WAKA/, 'console.error'); +like($t->read_file('error.log'), qr/\[info\].*js: BAR/, 'console.info'); +like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/, + 'console.log with object'); +like($t->read_file('error.log'), qr/\[warn\].*js: FOO/, 'console.warn'); +like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/, + 'console.time foo'); +like($t->read_file('error.log'), qr/\[info\].*js: Timer \"default\" already/, + 'console.time already started'); +like($t->read_file('error.log'), qr/\[info\].*js: Timer \"test\" doesn't/, + 'console.time not started'); + +############################################################################### From amdeich at gmail.com Sun Sep 24 11:30:14 2023 From: amdeich at gmail.com (Andrey Kulikov) Date: Sun, 24 Sep 2023 14:30:14 +0300 Subject: [Proposal] Expose HTTP and Stream proxy modules structures for developers. Message-ID: Hello, Sometimes custom module developers need to iterate over configured location(s), configured in some or all server(s) blocks. For example, to perform post-initialization steps, defined by custom directives in their modules. Now it is virtually impossible. I've posted question about this matter few years ago: https://mailman.nginx.org/pipermail/nginx-devel/2016-October/008882.html Since then, I received quite a few private emails about whether I succeeded in my findings. So, I'm not alone, who faced this issue. Fortunately, the described goal could be easily achieved by making just internal proxy modules structures available in a separate header. After that implementation of desired functionality is just a matter of writing a few lines of code. Patches а following. No functional changes, just code movement from .c to .h files. Development community, at least through my voice, will be grateful to see them applied to the latest development branch. -- Cheers, Andrey -------------- next part -------------- An HTML attachment was scrubbed... URL: From amdeich at gmail.com Sun Sep 24 11:32:00 2023 From: amdeich at gmail.com (Andrey Kulikov) Date: Sun, 24 Sep 2023 14:32:00 +0300 Subject: [PATCH] Expose HTTP proxy module structures for module developers. Message-ID: No functional changes. Made agains latest development branch. Could be applied with patch -p1 < 0001-Expose-HTTP-proxy-module-structures-for-module-devel.patch -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-Expose-HTTP-proxy-module-structures-for-module-devel.patch Type: application/octet-stream Size: 10367 bytes Desc: not available URL: From amdeich at gmail.com Sun Sep 24 11:32:48 2023 From: amdeich at gmail.com (Andrey Kulikov) Date: Sun, 24 Sep 2023 14:32:48 +0300 Subject: [PATCH] Expose Stream proxy module structures for module developers. Message-ID: No functional changes. Made agains latest development branch. Could be applied with patch -p1 < 0001-Expose-Stream-proxy-module-structures-for-module-dev.patc -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-Expose-Stream-proxy-module-structures-for-module-dev.patch Type: application/octet-stream Size: 5991 bytes Desc: not available URL: From zaihan at unrealasia.net Sun Sep 24 16:01:11 2023 From: zaihan at unrealasia.net (Muhammad Nuzaihan) Date: Mon, 25 Sep 2023 00:01:11 +0800 Subject: NGINX status code based on *response* body Message-ID: Hi, I would like to return an error message based on the *response* body. I know i can do it easily with *request* body (with "return NGX_HTTP_BAD_REQUEST") but my case i want to do it with *response* body. Also i have tried this code with response body filter but i got "*1 header already sent, client: 127.0.0.1, server: localhost": Thank you for your guidance. Code: ``` return ngx_http_filter_finalize_request(request, &ngx_m_module, NGX_HTTP_BAD_REQUEST); ``` RESPONSE BODY CODE: ngx_int_t ResponseBodyFilter(ngx_http_request_t *request, ngx_chain_t *chain_head) { // Set the logging level to debug // TODO: remove request->connection->log->log_level = NGX_LOG_DEBUG; // Get our context so we can store the response body data FilterContext *ctx = GetModuleFilterContext(request); if (ctx == NULL) { return NGX_ERROR; } return ngx_http_filter_finalize_request(request, &ngx_my_module, NGX_HTTP_BAD_REQUEST); } ngx_int_t ModuleInit(ngx_conf_t *cf) { kNextRequestBodyFilter = ngx_http_top_request_body_filter; ngx_http_top_request_body_filter = RequestBodyFilter; kNextResponseBodyFilter = ngx_http_top_body_filter; ngx_http_top_body_filter = ResponseBodyFilter; kNextHeaderFilter = ngx_http_top_header_filter; ngx_http_top_header_filter = HeaderFilter; return NGX_OK; } From arut at nginx.com Mon Sep 25 11:20:17 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 25 Sep 2023 11:20:17 +0000 Subject: [nginx] QUIC: handle callback errors in compat. Message-ID: details: https://hg.nginx.org/nginx/rev/3db945fda515 branches: changeset: 9164:3db945fda515 user: Vladimir Khomutov date: Fri Sep 22 19:23:57 2023 +0400 description: QUIC: handle callback errors in compat. The error may be triggered in add_handhshake_data() by incorrect transport parameter sent by client. The expected behaviour in this case is to close connection complaining about incorrect parameter. Currently the connection just times out. diffstat: src/event/quic/ngx_event_quic_openssl_compat.c | 14 ++++++++++++-- 1 files changed, 12 insertions(+), 2 deletions(-) diffs (35 lines): diff -r 32b5aaebcca5 -r 3db945fda515 src/event/quic/ngx_event_quic_openssl_compat.c --- a/src/event/quic/ngx_event_quic_openssl_compat.c Wed Sep 13 17:48:15 2023 +0400 +++ b/src/event/quic/ngx_event_quic_openssl_compat.c Fri Sep 22 19:23:57 2023 +0400 @@ -408,7 +408,9 @@ ngx_quic_compat_message_callback(int wri "quic compat tx %s len:%uz ", ngx_quic_level_name(level), len); - (void) com->method->add_handshake_data(ssl, level, buf, len); + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { + goto failed; + } break; @@ -420,11 +422,19 @@ ngx_quic_compat_message_callback(int wri "quic compat %s alert:%ui len:%uz ", ngx_quic_level_name(level), alert, len); - (void) com->method->send_alert(ssl, level, alert); + if (com->method->send_alert(ssl, level, alert) != 1) { + goto failed; + } } break; } + + return; + +failed: + + ngx_post_event(&qc->close, &ngx_posted_events); } From arut at nginx.com Mon Sep 25 11:21:55 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 25 Sep 2023 15:21:55 +0400 Subject: [PATCH] QUIC openssl compat mode error handling In-Reply-To: References: <20230922152711.amodnb2asarxhmev@N00W24XTQX> Message-ID: Hi, > On 22 Sep 2023, at 19:58, Vladimir Homutov wrote: > > On Fri, Sep 22, 2023 at 07:30:50PM +0400, Roman Arutyunyan wrote: >> Hi Vladimir, >> >> On Fri, Sep 22, 2023 at 03:44:08PM +0300, Vladimir Homutov via nginx-devel wrote: >>> # HG changeset patch >>> # User Vladimir Khomutov >>> # Date 1695386443 -10800 >>> # Fri Sep 22 15:40:43 2023 +0300 >>> # Node ID 974ba23e68909ba708616410aa77074213d4d1e5 >>> # Parent 5741eddf82e826766cd0f5ec7c6fe383145ca581 >>> QUIC: handle add_handhshake_data() callback errors in compat. >>> >>> The error may be triggered by incorrect transport parameter sent by client. >>> The expected behaviour in this case is to close connection complaining >>> about incorrect parameter. Currently the connection just times out. >>> >>> 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 >>> @@ -408,7 +408,10 @@ ngx_quic_compat_message_callback(int wri >>> "quic compat tx %s len:%uz ", >>> ngx_quic_level_name(level), len); >>> >>> - (void) com->method->add_handshake_data(ssl, level, buf, len); >>> + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { >>> + ngx_post_event(&qc->close, &ngx_posted_events); >>> + return; >>> + } >>> >>> break; >> >> Thanks for the patch. Indeed, it's a simple way to handle errors in callbacks. >> I'd also handle the error in send_alert(), even though we don't generate any >> errors in it now. > > Yes, although I was not sure if we need to close connection if we failed > to send alert (but probably if we are sending it, everything is already > bad enough). In either case, handling both cases similarly looks > as a way to go. I assume, send_alert() would return an error on a fatal condition, not just a local error. > >> >> -- >> Roman Arutyunyan > >> # HG changeset patch >> # User Vladimir Khomutov > >> # Date 1695396237 -14400 >> # Fri Sep 22 19:23:57 2023 +0400 >> # Node ID 3db945fda515014d220151046d02f3960bcfca0a >> # Parent 32b5aaebcca51854de6e1f8a40798edb13662edb >> QUIC: handle callback errors in compat. >> >> The error may be triggered in add_handhshake_data() by incorrect transport >> parameter sent by client. The expected behaviour in this case is to close >> connection complaining about incorrect parameter. Currently the connection >> just times out. >> >> 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 >> @@ -408,7 +408,9 @@ ngx_quic_compat_message_callback(int wri >> "quic compat tx %s len:%uz ", >> ngx_quic_level_name(level), len); >> >> - (void) com->method->add_handshake_data(ssl, level, buf, len); >> + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { >> + goto failed; >> + } >> >> break; >> >> @@ -420,11 +422,19 @@ ngx_quic_compat_message_callback(int wri >> "quic compat %s alert:%ui len:%uz ", >> ngx_quic_level_name(level), alert, len); >> >> - (void) com->method->send_alert(ssl, level, alert); >> + if (com->method->send_alert(ssl, level, alert) != 1) { >> + goto failed; >> + } >> } >> >> break; >> } >> + >> + return; >> + >> +failed: >> + >> + ngx_post_event(&qc->close, &ngx_posted_events); >> } >> > > Looks good! Committed, thanks. ---- Roman Arutyunyan arut at nginx.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From arut at nginx.com Mon Sep 25 14:22:14 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 25 Sep 2023 18:22:14 +0400 Subject: [Proposal] Expose HTTP and Stream proxy modules structures for developers. In-Reply-To: References: Message-ID: <20230925142214.myhuhotvzd3vald5@N00W24XTQX> Hello Andrey, On Sun, Sep 24, 2023 at 02:30:14PM +0300, Andrey Kulikov wrote: > Hello, > > Sometimes custom module developers need to iterate over configured > location(s), configured in some or all server(s) blocks. > For example, to perform post-initialization steps, defined by custom > directives in their modules. > Now it is virtually impossible. > > I've posted question about this matter few years ago: > https://mailman.nginx.org/pipermail/nginx-devel/2016-October/008882.html > > Since then, I received quite a few private emails about whether I succeeded > in my findings. > So, I'm not alone, who faced this issue. You can iterate over locations in a server block using "locations" queue in ngx_http_core_loc_conf_t. You need to use the location configuration at the server level to access the queue. Referencing the example from your last email, it should be something like this: clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; /* iterate over clcf->locations */ See ngx_http_core_location() as an example of how this queue is filled and ngx_http_merge_locations() as an example of how to iterate over it. > Fortunately, the described goal could be easily achieved by making just > internal proxy modules structures available in a separate header. > After that implementation of desired functionality is just a matter of > writing a few lines of code. Accessing proxy module to iterate over locations does not sound like the right decision. > Patches а following. > No functional changes, just code movement from .c to .h files. > > Development community, at least through my voice, will be grateful to see > them applied to the latest development branch. > > -- > Cheers, > Andrey -- Roman Arutyunyan From sdunn934 at gmail.com Mon Sep 25 14:27:40 2023 From: sdunn934 at gmail.com (stewart dunn) Date: Mon, 25 Sep 2023 15:27:40 +0100 Subject: [Proposal] Expose HTTP and Stream proxy modules structures for developers. In-Reply-To: <20230925142214.myhuhotvzd3vald5@N00W24XTQX> References: <20230925142214.myhuhotvzd3vald5@N00W24XTQX> Message-ID: Fyck off On Mon, 25 Sep 2023 at 15:22, Roman Arutyunyan wrote: > Hello Andrey, > > On Sun, Sep 24, 2023 at 02:30:14PM +0300, Andrey Kulikov wrote: > > Hello, > > > > Sometimes custom module developers need to iterate over configured > > location(s), configured in some or all server(s) blocks. > > For example, to perform post-initialization steps, defined by custom > > directives in their modules. > > Now it is virtually impossible. > > > > I've posted question about this matter few years ago: > > https://mailman.nginx.org/pipermail/nginx-devel/2016-October/008882.html > > > > Since then, I received quite a few private emails about whether I > succeeded > > in my findings. > > So, I'm not alone, who faced this issue. > > You can iterate over locations in a server block using "locations" queue in > ngx_http_core_loc_conf_t. You need to use the location configuration at > the server level to access the queue. Referencing the example from your > last > email, it should be something like this: > > clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; > > /* iterate over clcf->locations */ > > See ngx_http_core_location() as an example of how this queue is filled and > ngx_http_merge_locations() as an example of how to iterate over it. > > > Fortunately, the described goal could be easily achieved by making just > > internal proxy modules structures available in a separate header. > > After that implementation of desired functionality is just a matter of > > writing a few lines of code. > > Accessing proxy module to iterate over locations does not sound like the > right decision. > > > Patches а following. > > No functional changes, just code movement from .c to .h files. > > > > Development community, at least through my voice, will be grateful to see > > them applied to the latest development branch. > > > > -- > > Cheers, > > Andrey > > -- > Roman Arutyunyan > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From arut at nginx.com Mon Sep 25 14:54:10 2023 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 25 Sep 2023 18:54:10 +0400 Subject: NGINX status code based on *response* body In-Reply-To: References: Message-ID: <20230925145410.cvpetxjgs6vwbosf@N00W24XTQX> Hi Muhammad, On Mon, Sep 25, 2023 at 12:01:11AM +0800, Muhammad Nuzaihan wrote: > Hi, > > I would like to return an error message based on the *response* body. > > I know i can do it easily with *request* body (with "return > NGX_HTTP_BAD_REQUEST") but my case i want to do it with *response* > body. > > Also i have tried this code with response body filter but i got "*1 > header already sent, client: 127.0.0.1, server: localhost": That's because you need to delay sending the response, both header and body, before making the decision about the response code. It's quite complicated. If you send the header right away, there's no way to change it in the future. You can look into ngx_http_xslt_filter_module and ngx_http_image_filter_module for inspiration. For the xslt module (src/http/modules/ngx_http_xslt_filter_module.c), its header filter ngx_http_xslt_header_filter() does not call ngx_http_next_header_filter() on first call (ctx == NULL). Its body filter ngx_http_xslt_body_filter() also does not call ngx_http_next_body_filter() right away. Instead, it buffers the entire response body. Upon body completion, ngx_http_xslt_send() is called, which sends both header (ngx_http_next_header_filter()) and buffered body (ngx_http_next_body_filter()). If a decision is made to trigger an error, ngx_http_filter_finalize_request() is called instead. > Thank you for your guidance. > > Code: > > ``` > return ngx_http_filter_finalize_request(request, > &ngx_m_module, > NGX_HTTP_BAD_REQUEST); > ``` > RESPONSE BODY CODE: > > ngx_int_t ResponseBodyFilter(ngx_http_request_t *request, > ngx_chain_t *chain_head) { > // Set the logging level to debug > // TODO: remove > request->connection->log->log_level = NGX_LOG_DEBUG; > > // Get our context so we can store the response body data > FilterContext *ctx = GetModuleFilterContext(request); > if (ctx == NULL) { > return NGX_ERROR; > } > > return ngx_http_filter_finalize_request(request, > &ngx_my_module, > NGX_HTTP_BAD_REQUEST); > > } > > ngx_int_t ModuleInit(ngx_conf_t *cf) { > kNextRequestBodyFilter = ngx_http_top_request_body_filter; > ngx_http_top_request_body_filter = RequestBodyFilter; > > kNextResponseBodyFilter = ngx_http_top_body_filter; > ngx_http_top_body_filter = ResponseBodyFilter; > > kNextHeaderFilter = ngx_http_top_header_filter; > ngx_http_top_header_filter = HeaderFilter; > > return NGX_OK; > } > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -- Roman Arutyunyan From mdounin at mdounin.ru Mon Sep 25 19:28:12 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 25 Sep 2023 22:28:12 +0300 Subject: [PATCH 2 of 2] Added "max_shutdown_workers" directive In-Reply-To: <42c3166b675a6a7624bd.1693225844@arut-laptop> References: <42c3166b675a6a7624bd.1693225844@arut-laptop> Message-ID: Hello! On Mon, Aug 28, 2023 at 04:30:44PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan > # Date 1693218974 -14400 > # Mon Aug 28 14:36:14 2023 +0400 > # Node ID 42c3166b675a6a7624bdf3d78c0d4685ce8172e3 > # Parent fdad00808cb485ab83e919be59e9211ef06ac56a > Added "max_shutdown_workers" directive. > > The directive sets maximum number of workers in shutdown state while reloading > nginx configuration. Upon reaching this value, workers restart is postponed > until shutdown workers start exiting. This allows for lower peak resource > consumption at the cost of slower reconfiguration. I can't say I like the name, as the preferred term is "worker processes", not "workers". max_shutdown_processes? Also note that this doesn't really limit the total resource usage during configuration reloads, since cache manager and cache loader processes are exempt from the limit, but new processes are started on each configuration reload. It would be nice to actually provide a limit which makes multiple/frequent configuration reloads completely safe from the resource usage point of view, that is, to limit all processes spawned during configuration reloads, and not just worker processes. > > diff --git a/src/core/nginx.c b/src/core/nginx.c > --- a/src/core/nginx.c > +++ b/src/core/nginx.c > @@ -83,6 +83,13 @@ static ngx_command_t ngx_core_commands[ > 0, > NULL }, > > + { ngx_string("max_shutdown_workers"), > + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, > + ngx_conf_set_num_slot, > + 0, > + offsetof(ngx_core_conf_t, max_shutdown_workers), > + NULL }, > + > { ngx_string("debug_points"), > NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, > ngx_conf_set_enum_slot, > @@ -1117,6 +1124,7 @@ ngx_core_module_create_conf(ngx_cycle_t > ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC; > > ccf->worker_processes = NGX_CONF_UNSET; > + ccf->max_shutdown_workers = NGX_CONF_UNSET; > ccf->debug_points = NGX_CONF_UNSET; > > ccf->rlimit_nofile = NGX_CONF_UNSET; > @@ -1146,6 +1154,7 @@ ngx_core_module_init_conf(ngx_cycle_t *c > ngx_conf_init_msec_value(ccf->shutdown_timeout, 0); > > ngx_conf_init_value(ccf->worker_processes, 1); > + ngx_conf_init_value(ccf->max_shutdown_workers, 0); > ngx_conf_init_value(ccf->debug_points, 0); > > #if (NGX_HAVE_CPU_AFFINITY) > diff --git a/src/core/ngx_cycle.h b/src/core/ngx_cycle.h > --- a/src/core/ngx_cycle.h > +++ b/src/core/ngx_cycle.h > @@ -94,6 +94,7 @@ typedef struct { > ngx_msec_t shutdown_timeout; > > ngx_int_t worker_processes; > + ngx_int_t max_shutdown_workers; > ngx_int_t debug_points; > > ngx_int_t rlimit_nofile; > diff --git a/src/os/unix/ngx_process.c b/src/os/unix/ngx_process.c > --- a/src/os/unix/ngx_process.c > +++ b/src/os/unix/ngx_process.c > @@ -216,6 +216,7 @@ ngx_spawn_process(ngx_cycle_t *cycle, ng > ngx_processes[s].data = data; > ngx_processes[s].name = name; > ngx_processes[s].exiting = 0; > + ngx_processes[s].old = 0; > > switch (respawn) { > > diff --git a/src/os/unix/ngx_process.h b/src/os/unix/ngx_process.h > --- a/src/os/unix/ngx_process.h > +++ b/src/os/unix/ngx_process.h > @@ -33,6 +33,7 @@ typedef struct { > unsigned detached:1; > unsigned exiting:1; > unsigned exited:1; > + unsigned old:1; > } ngx_process_t; > > > diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c > --- a/src/os/unix/ngx_process_cycle.c > +++ b/src/os/unix/ngx_process_cycle.c > @@ -15,6 +15,7 @@ static void ngx_start_worker_processes(n > ngx_int_t type); > static void ngx_start_worker_process(ngx_cycle_t *cycle, ngx_int_t i, > ngx_int_t type); > +static void ngx_restart_worker_processes(ngx_cycle_t *cycle); > static void ngx_start_cache_manager_processes(ngx_cycle_t *cycle, > ngx_uint_t respawn); > static void ngx_pass_open_channel(ngx_cycle_t *cycle); > @@ -22,6 +23,7 @@ static void ngx_signal_worker_processes( > static void ngx_signal_worker_process(ngx_cycle_t *cycle, ngx_int_t i, > int signo); > static ngx_uint_t ngx_reap_children(ngx_cycle_t *cycle); > +static ngx_uint_t ngx_restart_worker_process(ngx_cycle_t *cycle); > static void ngx_master_process_exit(ngx_cycle_t *cycle); > static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data); > static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker); > @@ -235,8 +237,8 @@ ngx_master_process_cycle(ngx_cycle_t *cy > ngx_cycle = cycle; > ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, > ngx_core_module); > - ngx_start_worker_processes(cycle, ccf->worker_processes, > - NGX_PROCESS_JUST_RESPAWN); > + ngx_restart_worker_processes(cycle); > + > ngx_start_cache_manager_processes(cycle, 1); > > /* allow new processes to start */ > @@ -360,6 +362,70 @@ ngx_start_worker_process(ngx_cycle_t *cy > > > static void > +ngx_restart_worker_processes(ngx_cycle_t *cycle) > +{ > + ngx_int_t i, n, m, worker; > + ngx_core_conf_t *ccf; > + > + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); > + > + n = 0; > + m = ccf->max_shutdown_workers; > + > + if (m == 0) { > + goto start; > + } > + > + for (i = 0; i < ngx_last_process; i++) { > + > + if (ngx_processes[i].pid == -1 > + || ngx_processes[i].proc != ngx_worker_process_cycle) > + { Checking "proc" here looks like a layering violation. For example, ngx_signal_worker_process() does try to depend on the process entry point, and instead relies on the "detached" flag to decide which processes need to be signalled. > + continue; > + } > + > + ngx_processes[i].old = 0; > + worker = (intptr_t) ngx_processes[i].data; > + > + if (!ngx_processes[i].exiting && worker < ccf->worker_processes) { > + n++; It is not clear what we are counting here, and why. My best guess it that it's an attempt to count worker processes we are going to restart (and not just stop). It is not clear why we are doing this though: the limit as defined does not differentiate between shutting down processes being stopped and processes being restarted. Further, I don't think it's done correctly: since "worker" is an index number of the worker process, there can be at least gaps (if worker process failed to start and cannot be respawn). > + > + } else if (m > 0) { > + m--; > + } > + } > + > + n = (n > m) ? n - m : 0; n = ngx_max(n - m, 0); ? Also, some better names for the variables and/or some comments explaining what is being done might worth the effort. (As far as I can tell, "n" here is the number of processes we want to restart but not allowed to.) > + > + if (n == 0) { > + goto start; > + } > + > + for (i = 0; i < ngx_last_process; i++) { > + > + if (ngx_processes[i].pid == -1 > + || ngx_processes[i].proc != ngx_worker_process_cycle) > + { > + continue; > + } > + > + ngx_processes[i].old = 1; > + worker = (intptr_t) ngx_processes[i].data; > + > + if (!ngx_processes[i].exiting && worker < n) { Here "n" is the number of processes we are not allowed to restart right now, while "worker" is the index number of the worker process in question. For a given "n", number of worker processes matching this condition might be at least less than "n": if some worker processes failed to start. (Just in case, the rule of thumb is: if you are using worker process index number for anything, most likely you are doing it wrong.) > + ngx_processes[i].just_spawn = 1; The "just_spawn" flag implies the process was just started, and so it is ignored by the following ngx_signal_worker_process() call. Abusing it to ignore processes during shutdown might work, but I can't say I like this approach. Using a separate flag might be better. > + } > + } > + > +start: > + > + for (i = n; i < ccf->worker_processes; i++) { > + ngx_start_worker_process(cycle, i, NGX_PROCESS_JUST_RESPAWN); > + } The order of processes being started looks somewhat counter-intuitive here. > +} > + > + > +static void > ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn) > { > ngx_uint_t i, manager, loader; > @@ -486,15 +552,16 @@ ngx_signal_worker_process(ngx_cycle_t *c > ch.fd = -1; > > > - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0, > - "child: %i %P e:%d t:%d d:%d r:%d j:%d", > + ngx_log_debug8(NGX_LOG_DEBUG_EVENT, cycle->log, 0, > + "child: %i %P e:%d t:%d d:%d r:%d j:%d o:%d", > i, > ngx_processes[i].pid, > ngx_processes[i].exiting, > ngx_processes[i].exited, > ngx_processes[i].detached, > ngx_processes[i].respawn, > - ngx_processes[i].just_spawn); > + ngx_processes[i].just_spawn, > + ngx_processes[i].old); > > if (ngx_processes[i].detached || ngx_processes[i].pid == -1) { > return; > @@ -563,15 +630,16 @@ ngx_reap_children(ngx_cycle_t *cycle) > live = 0; > for (i = 0; i < ngx_last_process; i++) { > > - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0, > - "child: %i %P e:%d t:%d d:%d r:%d j:%d", > + ngx_log_debug8(NGX_LOG_DEBUG_EVENT, cycle->log, 0, > + "child: %i %P e:%d t:%d d:%d r:%d j:%d o:%d", > i, > ngx_processes[i].pid, > ngx_processes[i].exiting, > ngx_processes[i].exited, > ngx_processes[i].detached, > ngx_processes[i].respawn, > - ngx_processes[i].just_spawn); > + ngx_processes[i].just_spawn, > + ngx_processes[i].old); > > if (ngx_processes[i].pid == -1) { > continue; > @@ -660,6 +728,16 @@ ngx_reap_children(ngx_cycle_t *cycle) > ngx_processes[i].pid = -1; > } > Note: when a process dies, it is restarted if the "respawn" flag is set, regardless of the "old" flag. The "old" flag is preserved even if it is no longer relevant after the respawn: the process is restarted with the new configuration, and there is no need to restart it again. > + if (ngx_processes[i].old > + && ngx_processes[i].exiting > + && !ngx_terminate > + && !ngx_quit) > + { > + if (ngx_restart_worker_process(cycle)) { > + live = 1; > + } > + } > + > } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) { > live = 1; > } > @@ -669,6 +747,53 @@ ngx_reap_children(ngx_cycle_t *cycle) > } > > > +static ngx_uint_t > +ngx_restart_worker_process(ngx_cycle_t *cycle) > +{ > + ngx_int_t i, j, m; > + ngx_core_conf_t *ccf; > + > + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); > + > + j = -1; > + m = ccf->max_shutdown_workers; > + > + if (m == 0) { > + return 0; > + } > + > + for (i = 0; i < ngx_last_process; i++) { > + > + if (ngx_processes[i].pid == -1 || !ngx_processes[i].old) { > + continue; > + } > + > + if (!ngx_processes[i].exiting && !ngx_processes[i].exited) { The "exited" check here is not needed to catch the processes we were called for, or other processes being shut down - they already have "exiting" flag set. On the other hand, this can catch unexpectedly died processes (the ones which are still waiting for restart). As per the code, such processes will be counted against max_shutdown_workers as if they are shutting down - this looks wrong and can cause reload hang. > + j = i; > + continue; > + } > + > + if (--m == 0) { > + return 0; > + } > + } > + > + if (j == -1) { > + return 0; > + } > + > + ngx_start_worker_process(cycle, (intptr_t) ngx_processes[j].data, > + NGX_PROCESS_RESPAWN); > + > + ngx_msleep(100); > + > + ngx_signal_worker_process(cycle, j, > + ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); Also note that the "exited" check above implies that there can be multiple exited processes at the same time, but we only restart just one process here. It might be a good idea to restart multiple processes at the same time: sleeping for 100ms for each process means that if, for example, 64 processes will exit at the same time after shutdown timeout expiration, master process will be unresponsive for 6 seconds. Also, this looks very close to ngx_restart_worker_processes(). It might be a good idea to rework things to ensure unified approach to restarting processes instead of providing multiple duplicate ways. > + > + return 1; > +} > + > + > static void > ngx_master_process_exit(ngx_cycle_t *cycle) > { Overall, I tend to think that the suggested code is very fragile, and I would rather consider re-thinking the approach. -- Maxim Dounin http://mdounin.ru/ From yar at nginx.com Tue Sep 26 13:14:18 2023 From: yar at nginx.com (=?utf-8?q?Yaroslav_Zhuravlev?=) Date: Tue, 26 Sep 2023 14:14:18 +0100 Subject: [PATCH] Updated OpenSSL and zlib versions Message-ID: <32a78db44cf00ab54063.1695734058@ORK-ML-00007151> xml/en/docs/configure.xml | 6 +++--- xml/en/docs/howto_build_on_win32.xml | 10 +++++----- xml/ru/docs/configure.xml | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: nginx.org.patch Type: text/x-patch Size: 3265 bytes Desc: not available URL: From jt26wzz at gmail.com Tue Sep 26 17:13:44 2023 From: jt26wzz at gmail.com (=?UTF-8?B?5LiK5Yu+5ouz?=) Date: Wed, 27 Sep 2023 01:13:44 +0800 Subject: Memory Leak Issue in Nginx PCRE2 Message-ID: Dear Nginx Developers, I hope this email finds you well. I am reaching out to the mailing list for the first time to report and discuss an issue I encountered while working on supporting PCRE2 in OpenResty. If I have made any errors in my reporting or discussion, please do not hesitate to provide feedback. Your guidance is greatly appreciated. During my recent work, I used the sanitizer to inspect potential issues, and I identified a small memory leak in the PCRE2 code section of Nginx. While this issue does not seem to be critical, it could potentially disrupt memory checking tools. To help you reproduce the problem, I have included a minimal configuration below. Please note that this issue occurs when Nginx is configured to use PCRE2, and the version is 1.22.1 or higher. *Minimal Configuration for Reproduction:* worker_processes 1; daemon off; master_process off; error_log /home/zhenzhongw/code/pcre_pr/lua-nginx-module/t/servroot/logs/error.log debug; pid /home/zhenzhongw/code/pcre_pr/lua-nginx-module/t/servroot/logs/nginx.pid; http { access_log /home/zhenzhongw/code/pcre_pr/lua-nginx-module/t/servroot/logs/access.log; #access_log off; default_type text/plain; keepalive_timeout 68000ms; server { listen 1984; #placeholder server_name 'localhost'; client_max_body_size 30M; #client_body_buffer_size 4k; # Begin preamble config... # End preamble config... # Begin test case config... location ~ '^/[a-d]$' { return 200; } } } events { accept_mutex off; worker_connections 64; } *nginx -V :* nginx version: nginx/1.25.1 (no pool) built by gcc 11.4.1 20230605 (Red Hat 11.4.1-2) (GCC) built with OpenSSL 1.1.1u 30 May 2023 TLS SNI support enabled configure arguments: --prefix=/home/zhenzhongw/code/pcre_pr/lua-nginx-module/work/nginx --with-threads --with-pcre-jit --with-ipv6 --with-cc-opt='-fno-omit-frame-pointer -fsanitize=address -DNGX_LUA_USE_ASSERT -I/opt/pcre2/include -I/opt/ssl/include' --with-http_v2_module --with-http_v3_module --with-http_realip_module --with-http_ssl_module --add-module=/home/zhenzhongw/code/pcre_pr/ndk-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/set-misc-nginx-module --with-ld-opt='-fsanitize=address -L/opt/pcre2/lib -L/opt/ssl/lib -Wl,-rpath,/opt/pcre2/lib:/opt/drizzle/lib:/opt/ssl/lib' --without-mail_pop3_module --without-mail_imap_module --with-http_image_filter_module --without-mail_smtp_module --with-stream --with-stream_ssl_module --without-http_upstream_ip_hash_module --without-http_memcached_module --without-http_auth_basic_module --without-http_userid_module --with-http_auth_request_module --add-module=/home/zhenzhongw/code/pcre_pr/echo-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/memc-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/srcache-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/lua-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/lua-upstream-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/headers-more-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/drizzle-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/rds-json-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/coolkit-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/redis2-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/stream-lua-nginx-module --add-module=/home/zhenzhongw/code/pcre_pr/lua-nginx-module/t/data/fake-module --add-module=/home/zhenzhongw/code/pcre_pr/lua-nginx-module/t/data/fake-shm-module --add-module=/home/zhenzhongw/code/pcre_pr/lua-nginx-module/t/data/fake-delayed-load-module --with-http_gunzip_module --with-http_dav_module --with-select_module --with-poll_module --with-debug --with-poll_module --with-cc=gcc *The sanitizer tool reported the following error message: * ================================================================= ==555798==ERROR: LeakSanitizer: detected memory leaks Direct leak of 72 byte(s) in 1 object(s) allocated from: #0 0x7f502f6b4a07 in __interceptor_malloc (/lib64/libasan.so.6+0xb4a07) #1 0x4a1737 in ngx_alloc src/os/unix/ngx_alloc.c:22 #2 0x525796 in ngx_regex_malloc src/core/ngx_regex.c:509 #3 0x7f502f3e745e in _pcre2_memctl_malloc_8 (/opt/pcre2/lib/libpcre2-8.so.0+0x1145e) #4 0x5771ad in ngx_http_regex_compile src/http/ngx_http_variables.c:2555 #5 0x536088 in ngx_http_core_regex_location src/http/ngx_http_core_module.c:3263 #6 0x537f94 in ngx_http_core_location src/http/ngx_http_core_module.c:3115 #7 0x46ba0a in ngx_conf_handler src/core/ngx_conf_file.c:463 #8 0x46ba0a in ngx_conf_parse src/core/ngx_conf_file.c:319 #9 0x5391ec in ngx_http_core_server src/http/ngx_http_core_module.c:2991 #10 0x46ba0a in ngx_conf_handler src/core/ngx_conf_file.c:463 #11 0x46ba0a in ngx_conf_parse src/core/ngx_conf_file.c:319 #12 0x528e4c in ngx_http_block src/http/ngx_http.c:239 #13 0x46ba0a in ngx_conf_handler src/core/ngx_conf_file.c:463 #14 0x46ba0a in ngx_conf_parse src/core/ngx_conf_file.c:319 #15 0x463f74 in ngx_init_cycle src/core/ngx_cycle.c:284 #12 0x528e4c in ngx_http_block src/http/ngx_http.c:239 #13 0x46ba0a in ngx_conf_handler src/core/ngx_conf_file.c:463 #14 0x46ba0a in ngx_conf_parse src/core/ngx_conf_file.c:319 #15 0x463f74 in ngx_init_cycle src/core/ngx_cycle.c:284 #16 0x4300c7 in main src/core/nginx.c:295 #17 0x7ff31a43feaf in __libc_start_call_main (/lib64/libc.so.6+0x3feaf) SUMMARY: AddressSanitizer: 72 byte(s) leaked in 1 allocation(s). *I have created a patch to address this memory leak issue, which I am sharing below:* diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 91381f499..71f583789 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -600,6 +600,8 @@ ngx_regex_cleanup(void *data) * the new cycle, these will be re-allocated. */ + ngx_regex_malloc_init(NULL); + if (ngx_regex_compile_context) { pcre2_compile_context_free(ngx_regex_compile_context); ngx_regex_compile_context = NULL; @@ -611,6 +613,8 @@ ngx_regex_cleanup(void *data) ngx_regex_match_data_size = 0; } + ngx_regex_malloc_done(); + #endif } @@ -706,7 +710,13 @@ ngx_regex_module_init(ngx_cycle_t *cycle) ngx_regex_malloc_done(); ngx_regex_studies = NULL; + #if (NGX_PCRE2) + if (ngx_regex_compile_context) { + ngx_regex_malloc_init(NULL); + pcre2_compile_context_free(ngx_regex_compile_context); + ngx_regex_malloc_done(); + } ngx_regex_compile_context = NULL; #endif I kindly request your assistance in reviewing this matter and considering the patch for inclusion in Nginx. If you have any questions or need further information, please feel free to reach out to me. Your expertise and feedback are highly valuable in resolving this issue. Thank you for your time and attention to this matter. Best regards, ZhenZhong -------------- next part -------------- An HTML attachment was scrubbed... URL: From xeioex at nginx.com Thu Sep 28 18:01:46 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 28 Sep 2023 18:01:46 +0000 Subject: [njs] Fetch: fixed HEAD response handling with large Content-Length. Message-ID: details: https://hg.nginx.org/njs/rev/a60c0acce58b branches: changeset: 2209:a60c0acce58b user: Dmitry Volyntsev date: Thu Sep 28 11:00:56 2023 -0700 description: Fetch: fixed HEAD response handling with large Content-Length. This closes #658 issue on Github. diffstat: nginx/ngx_js_fetch.c | 5 +++-- nginx/t/js_fetch.t | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diffs (64 lines): diff -r ed935fa4805b -r a60c0acce58b nginx/ngx_js_fetch.c --- a/nginx/ngx_js_fetch.c Fri Sep 22 13:00:06 2023 -0700 +++ b/nginx/ngx_js_fetch.c Thu Sep 28 11:00:56 2023 -0700 @@ -2426,8 +2426,9 @@ ngx_js_http_process_headers(ngx_js_http_ return NGX_ERROR; } - if (hp->content_length_n - > (off_t) http->max_response_body_size) + if (!http->header_only + && hp->content_length_n + > (off_t) http->max_response_body_size) { ngx_js_http_error(http, 0, "fetch content length is too large"); diff -r ed935fa4805b -r a60c0acce58b nginx/t/js_fetch.t --- a/nginx/t/js_fetch.t Fri Sep 22 13:00:06 2023 -0700 +++ b/nginx/t/js_fetch.t Thu Sep 28 11:00:56 2023 -0700 @@ -46,6 +46,8 @@ http { listen 127.0.0.1:8080; server_name localhost; + js_fetch_max_response_buffer_size 128k; + location /njs { js_content test.njs; } @@ -399,7 +401,7 @@ my $p2 = port(8082); host_header, multi, loc, property}; EOF -$t->try_run('no njs.fetch')->plan(35); +$t->try_run('no njs.fetch')->plan(36); $t->run_daemon(\&http_daemon, port(8082)); $t->waitforsocket('127.0.0.1:' . port(8082)); @@ -493,6 +495,13 @@ like(http_get('/host_header?host=FOOBAR' 'fetch host header'); } +TODO: { +local $TODO = 'not yet' unless has_version('0.8.2'); + +like(http_get('/body_special?loc=head/large&method=HEAD'), + qr/200 OK.*$/s, 'fetch head method large content-length'); +} + ############################################################################### sub has_version { @@ -620,6 +629,13 @@ sub http_daemon { "Connection: close" . CRLF . CRLF; + } elsif ($uri eq '/head/large') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 1000000" . CRLF . + "Connection: close" . CRLF . + CRLF; + } elsif ($uri eq '/parted') { print $client "HTTP/1.1 200 OK" . CRLF . From xeioex at nginx.com Sat Sep 30 00:22:03 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 30 Sep 2023 00:22:03 +0000 Subject: [njs] Stream: adjusted periodic timeout in tests. Message-ID: details: https://hg.nginx.org/njs/rev/febba7cb31e0 branches: changeset: 2210:febba7cb31e0 user: Dmitry Volyntsev date: Thu Sep 28 18:11:15 2023 -0700 description: Stream: adjusted periodic timeout in tests. diffstat: nginx/t/stream_js_periodic.t | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r a60c0acce58b -r febba7cb31e0 nginx/t/stream_js_periodic.t --- a/nginx/t/stream_js_periodic.t Thu Sep 28 11:00:56 2023 -0700 +++ b/nginx/t/stream_js_periodic.t Thu Sep 28 18:11:15 2023 -0700 @@ -275,7 +275,7 @@ EOF ############################################################################### -select undef, undef, undef, 0.1; +select undef, undef, undef, 0.2; is(stream('127.0.0.1:' . port(8080))->io('affinity'), 'affinity', 'affinity test'); From xeioex at nginx.com Sat Sep 30 00:22:05 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 30 Sep 2023 00:22:05 +0000 Subject: [njs] RegExp: improved memory footprint when match fails. Message-ID: details: https://hg.nginx.org/njs/rev/88318e18dec9 branches: changeset: 2211:88318e18dec9 user: Dmitry Volyntsev date: Thu Sep 28 19:09:56 2023 -0700 description: RegExp: improved memory footprint when match fails. diffstat: src/njs_regexp.c | 8 +++++--- 1 files changed, 5 insertions(+), 3 deletions(-) diffs (33 lines): diff -r febba7cb31e0 -r 88318e18dec9 src/njs_regexp.c --- a/src/njs_regexp.c Thu Sep 28 18:11:15 2023 -0700 +++ b/src/njs_regexp.c Thu Sep 28 19:09:56 2023 -0700 @@ -941,6 +941,9 @@ njs_regexp_builtin_exec(njs_vm_t *vm, nj string.size, match_data); if (ret >= 0) { result = njs_regexp_exec_result(vm, r, utf8, &string, match_data); + + njs_regex_match_data_free(match_data, vm->regex_generic_ctx); + if (njs_slow_path(result == NULL)) { return NJS_ERROR; } @@ -949,8 +952,9 @@ njs_regexp_builtin_exec(njs_vm_t *vm, nj return NJS_OK; } + njs_regex_match_data_free(match_data, vm->regex_generic_ctx); + if (njs_slow_path(ret == NJS_ERROR)) { - njs_regex_match_data_free(match_data, vm->regex_generic_ctx); return NJS_ERROR; } @@ -1154,8 +1158,6 @@ fail: done: - njs_regex_match_data_free(match_data, vm->regex_generic_ctx); - return (ret == NJS_OK) ? array : NULL; } From mdounin at mdounin.ru Sat Sep 30 03:38:17 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 30 Sep 2023 06:38:17 +0300 Subject: [PATCH] Improve performance when starting nginx with a lot of locations In-Reply-To: References: Message-ID: Hello! On Fri, Sep 22, 2023 at 03:58:41PM +0900, Yusuke Nojima wrote: > # HG changeset patch > # User Yusuke Nojima > # Date 1679555707 -32400 > # Thu Mar 23 16:15:07 2023 +0900 > # Node ID 6aac98fb135e47ca9cf7ad7d780cf4a10e9aa55c > # Parent 8771d35d55d0a2b1cefaab04401d6f837f5a05a2 > Improve performance when starting nginx with a lot of locations > > Our team has a configuration file with a very large number of > locations, and we found that starting nginx with this file takes an > unacceptable amount of time. After investigating the issue, we > discovered that the root cause of the long startup time is the sorting > of the location list. Interesting. Could you please provide some more details about the use case, such as how locations are used, and what is the approximate number of locations being used? In my practice, it is extremely uncommon to use more than 1k-10k prefix locations (and even these numbers are huge for normal setups). Further, since each location contains configuration for all modules, such configurations are expected to require a lot of memory (currently about 5-10KB per empty location, so about 50-100MB per 10k locations, and 0.5-1G per 100k locations). Accordingly, using other approaches such as map (assuming exact match is actually needed) might be beneficial regardless of the sorting costs. Nevertheless, swapping the sorting algorithm to a faster one looks like an obvious improvement. > > Currently, the sorting algorithm used in nginx is insertion sort, > which requires O(n^2) time for n locations. We have modified the > sorting algorithm to use merge sort instead, which has a time > complexity of O(n log n). > > We have tested the modified code using micro-benchmarks and confirmed > that the new algorithm improves nginx startup time significantly > (shown below). We believe that this change would be valuable for other > users who are experiencing similar issues. > > > Table: nginx startup time in seconds > > n current patched > 2000 0.033 0.018 > 4000 0.047 0.028 > 6000 0.062 0.038 > 8000 0.079 0.050 > 10000 0.091 0.065 > 12000 0.367 0.081 > 14000 0.683 0.086 > 16000 0.899 0.097 > 18000 1.145 0.110 > 20000 1.449 0.122 > 22000 1.650 0.137 > 24000 2.155 0.151 > 26000 3.096 0.155 > 28000 3.711 0.168 > 30000 3.539 0.184 > 32000 3.980 0.193 > 34000 4.543 0.208 > 36000 4.349 0.217 > 38000 5.021 0.229 > 40000 4.918 0.245 > 42000 4.835 0.256 > 44000 5.159 0.262 > 46000 5.802 0.331 > 48000 6.205 0.295 > 50000 5.701 0.308 > 52000 5.992 0.335 > 54000 6.561 0.323 > 56000 6.856 0.333 > 58000 6.515 0.347 > 60000 7.051 0.359 > 62000 6.956 0.377 > 64000 7.376 0.376 > 66000 7.506 0.404 > 68000 7.292 0.407 > 70000 7.422 0.461 > 72000 10.090 0.443 > 74000 18.505 0.463 > 76000 11.857 0.462 > 78000 9.752 0.470 > 80000 12.485 0.481 > 82000 11.027 0.498 > 84000 9.804 0.523 > 86000 8.482 0.515 > 88000 9.838 0.560 > 90000 12.341 0.546 > 92000 13.881 0.648 > 94000 8.309 0.635 > 96000 8.854 0.650 > 98000 12.871 0.674 > 100000 8.261 0.698 This probably can be reduced to something like 3-5 data points. > > diff -r 8771d35d55d0 -r 6aac98fb135e src/core/ngx_queue.c > --- a/src/core/ngx_queue.c Fri Mar 10 07:43:50 2023 +0300 > +++ b/src/core/ngx_queue.c Thu Mar 23 16:15:07 2023 +0900 > @@ -45,36 +45,103 @@ > } > > > -/* the stable insertion sort */ > +/* merge queue2 into queue1. queue2 becomes empty after merge. */ > + > +static void > +ngx_queue_merge(ngx_queue_t *queue1, ngx_queue_t *queue2, > + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) > +{ > + ngx_queue_t *p1, *p2; Nitpicking: there are various style issues here and in other places. > + > + p1 = ngx_queue_head(queue1); > + p2 = ngx_queue_head(queue2); > + > + while (p1 != ngx_queue_sentinel(queue1) > + && p2 != ngx_queue_sentinel(queue2)) { > + > + if (cmp(p1, p2) > 0) { > + ngx_queue_t *next, *prev; > + > + next = ngx_queue_next(p2); > + ngx_queue_remove(p2); > + prev = ngx_queue_prev(p1); > + ngx_queue_insert_after(prev, p2); > + p2 = next; Nitpicking: there is no need to preserve "next" here, since p2 is always the head of queue2 and, and the next element can be obtained by ngx_queue_head(queue2). Also, instead of obtaining "prev" and using ngx_queue_insert_after() it would be easier to use ngx_queue_insert_before(). It is not currently defined, but it is trivial to define one: it is an alias to ngx_queue_insert_tail(), much like ngx_queue_insert_after() is an alias to ngx_queue_insert_head(). > + } else { > + p1 = ngx_queue_next(p1); > + } > + } > + if (p2 != ngx_queue_sentinel(queue2)) { > + ngx_queue_add(queue1, queue2); > + ngx_queue_init(queue2); > + } > +} > + > + > +/* move all elements from src to dest. dest should be empty before call. */ > + > +static void > +ngx_queue_move(ngx_queue_t *dest, ngx_queue_t *src) > +{ > + *dest = *src; > + ngx_queue_init(src); > + > + if (dest->next == src) { > + dest->next = dest; > + } else { > + dest->next->prev = dest; > + } > + if (dest->prev == src) { > + dest->prev = dest; > + } else { > + dest->prev->next = dest; > + } > +} This function looks strange to me. There is the ngx_queue_add() macro, which probably should be used instead (if needed). > + > + > +/* the stable merge sort */ > > void > ngx_queue_sort(ngx_queue_t *queue, > ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) > { > - ngx_queue_t *q, *prev, *next; > + ngx_queue_t merged[64], *p, *last; > > - q = ngx_queue_head(queue); > - > - if (q == ngx_queue_last(queue)) { > + if (ngx_queue_head(queue) == ngx_queue_last(queue)) { > return; > } > > - for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) { > + last = merged; > > - prev = ngx_queue_prev(q); > - next = ngx_queue_next(q); > + while (!ngx_queue_empty(queue)) { > + /* > + * Loop invariant: > + * merged[i] must have exactly 0 or 2^i elements in sorted order. > + * For each iteration, we take one element from the given queue and > + * insert it into merged without violating the invariant condition. > + */ > > - ngx_queue_remove(q); > + ngx_queue_t carry, *h; > + > + h = ngx_queue_head(queue); > + ngx_queue_remove(h); > + ngx_queue_init(&carry); > + ngx_queue_insert_head(&carry, h); > > - do { > - if (cmp(prev, q) <= 0) { > - break; > - } > + for (p = merged; p != last && !ngx_queue_empty(p); p++) { > + ngx_queue_merge(p, &carry, cmp); > + ngx_queue_move(&carry, p); > + } > + if (p == last) { > + ngx_queue_init(last); > + last++; > + } > + ngx_queue_move(p, &carry); > + } > > - prev = ngx_queue_prev(prev); > - > - } while (prev != ngx_queue_sentinel(queue)); > - > - ngx_queue_insert_after(prev, q); > + /* Merge all queues into one queue */ > + for (p = merged + 1; p != last; p++) { > + ngx_queue_merge(p, p-1, cmp); > } > + ngx_queue_move(queue, last-1); > } While bottom-up merge sort implementation might be more efficient, I find it disturbing to use fixed array of queues without any checks if we are within the array bounds. Rather, I would suggest recursive top-bottom merge sort implementation instead, which is much simpler and uses stack as temporary storage (so it'll naturally die if there will be a queue which requires more space for sorting than we have). Please take a look if it works for you: diff --git a/src/core/ngx_queue.c b/src/core/ngx_queue.c --- a/src/core/ngx_queue.c +++ b/src/core/ngx_queue.c @@ -9,6 +9,10 @@ #include +static void ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)); + + /* * find the middle queue element if the queue has odd number of elements * or the first element of the queue's second part otherwise @@ -45,13 +49,13 @@ ngx_queue_middle(ngx_queue_t *queue) } -/* the stable insertion sort */ +/* the stable merge sort */ void ngx_queue_sort(ngx_queue_t *queue, ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) { - ngx_queue_t *q, *prev, *next; + ngx_queue_t *q, tail; q = ngx_queue_head(queue); @@ -59,22 +63,44 @@ ngx_queue_sort(ngx_queue_t *queue, return; } - for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) { + q = ngx_queue_middle(queue); + + ngx_queue_split(queue, q, &tail); + + ngx_queue_sort(queue, cmp); + ngx_queue_sort(&tail, cmp); + + ngx_queue_merge(queue, &tail, cmp); +} - prev = ngx_queue_prev(q); - next = ngx_queue_next(q); - ngx_queue_remove(q); +static void +ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) +{ + ngx_queue_t *q1, *q2; + + q1 = ngx_queue_head(queue); + q2 = ngx_queue_head(tail); - do { - if (cmp(prev, q) <= 0) { - break; - } + for ( ;; ) { + if (q1 == ngx_queue_sentinel(queue)) { + ngx_queue_add(queue, tail); + break; + } + + if (q2 == ngx_queue_sentinel(tail)) { + break; + } - prev = ngx_queue_prev(prev); + if (cmp(q1, q2) <= 0) { + q1 = ngx_queue_next(q1); + continue; + } - } while (prev != ngx_queue_sentinel(queue)); + ngx_queue_remove(q2); + ngx_queue_insert_before(q1, q2); - ngx_queue_insert_after(prev, q); + q2 = ngx_queue_head(tail); } } diff --git a/src/core/ngx_queue.h b/src/core/ngx_queue.h --- a/src/core/ngx_queue.h +++ b/src/core/ngx_queue.h @@ -47,6 +47,9 @@ struct ngx_queue_s { (h)->prev = x +#define ngx_queue_insert_before ngx_queue_insert_tail + + #define ngx_queue_head(h) \ (h)->next -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Sat Sep 30 05:43:55 2023 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Sat, 30 Sep 2023 05:43:55 +0000 Subject: [njs] FS: introduced fs.existsSync(). Message-ID: details: https://hg.nginx.org/njs/rev/f076da3c7a6f branches: changeset: 2212:f076da3c7a6f user: Dmitry Volyntsev date: Fri Sep 29 21:41:34 2023 -0700 description: FS: introduced fs.existsSync(). diffstat: external/njs_fs_module.c | 30 ++++++++++++++++++++++++++++++ test/fs/methods.t.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 0 deletions(-) diffs (102 lines): diff -r 88318e18dec9 -r f076da3c7a6f external/njs_fs_module.c --- a/external/njs_fs_module.c Thu Sep 28 19:09:56 2023 -0700 +++ b/external/njs_fs_module.c Fri Sep 29 21:41:34 2023 -0700 @@ -146,6 +146,8 @@ typedef njs_int_t (*njs_file_tree_walk_c 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); +static njs_int_t njs_fs_exists_sync(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_mkdir(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_open(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, @@ -583,6 +585,16 @@ static njs_external_t njs_ext_fs[] = { { .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("existsSync"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_exists_sync, + } + }, + + { + .flags = NJS_EXTERN_METHOD, .name.string = njs_str("fstatSync"), .writable = 1, .configurable = 1, @@ -1470,6 +1482,24 @@ njs_fs_access(njs_vm_t *vm, njs_value_t static njs_int_t +njs_fs_exists_sync(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype, njs_value_t *retval) +{ + const char *path; + 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)) { + return NJS_ERROR; + } + + njs_value_boolean_set(retval, access(path, F_OK) == 0); + + return NJS_OK; +} + + +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) { diff -r 88318e18dec9 -r f076da3c7a6f test/fs/methods.t.js --- a/test/fs/methods.t.js Thu Sep 28 19:09:56 2023 -0700 +++ b/test/fs/methods.t.js Fri Sep 29 21:41:34 2023 -0700 @@ -359,6 +359,36 @@ let appendFileP_tsuite = { get tests() { return append_tests() }, }; +async function exists_test(params) { + let res = await method("exists", params); + + if (res !== params.expected) { + throw Error(`exists failed check`); + } + + return 'SUCCESS'; +} + +let exists_tests = () => [ + { args: ["test/fs/ascii"], + expected: true }, + { args: ["test/fs"], + expected: true }, + { args: ["test/fs_NONEXISTENT/ascii"], + expected: false }, + { args: ["test/fs/ascii_NONEXISTENT"], + expected: false }, +]; + +let existsSync_tsuite = { + name: "fs existsSync", + skip: () => (!has_fs() || !has_buffer()), + T: exists_test, + prepare_args: p, + opts: { type: "sync" }, + get tests() { return exists_tests() }, +}; + async function realpath_test(params) { let data = await method("realpath", params); @@ -1153,6 +1183,7 @@ run([ appendFile_tsuite, appendFileSync_tsuite, appendFileP_tsuite, + existsSync_tsuite, realpath_tsuite, realpathSync_tsuite, realpathP_tsuite, From mdounin at mdounin.ru Sat Sep 30 17:48:59 2023 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 30 Sep 2023 20:48:59 +0300 Subject: [PATCH] Updated OpenSSL and zlib versions In-Reply-To: <32a78db44cf00ab54063.1695734058@ORK-ML-00007151> References: <32a78db44cf00ab54063.1695734058@ORK-ML-00007151> Message-ID: Hello! On Tue, Sep 26, 2023 at 02:14:18PM +0100, Yaroslav Zhuravlev wrote: > xml/en/docs/configure.xml | 6 +++--- > xml/en/docs/howto_build_on_win32.xml | 10 +++++----- > xml/ru/docs/configure.xml | 6 +++--- > 3 files changed, 11 insertions(+), 11 deletions(-) > > > # HG changeset patch > # User Yaroslav Zhuravlev > # Date 1695733917 -3600 > # Tue Sep 26 14:11:57 2023 +0100 > # Node ID 32a78db44cf00ab54063905e2a2ff4e5092a7b28 > # Parent 1ad61bfc7630adf1d6460cf84cec484de4017326 > Updated OpenSSL and zlib versions. > > diff --git a/xml/en/docs/configure.xml b/xml/en/docs/configure.xml > --- a/xml/en/docs/configure.xml > +++ b/xml/en/docs/configure.xml > @@ -8,7 +8,7 @@ >
link="/en/docs/configure.html" > lang="en" > - rev="22"> > + rev="23"> > >
> > @@ -1270,7 +1270,7 @@ > > sets the path to the sources of the zlib library. > The library distribution (version > -1.1.3—1.2.11) needs to be downloaded from the > +1.1.3—1.2.13) needs to be downloaded from the > zlib site and extracted. > The rest is done by nginx’s ./configure and > make. > @@ -1359,7 +1359,7 @@ > --pid-path=/usr/local/nginx/nginx.pid > --with-http_ssl_module > --with-pcre=../pcre2-10.39 > - --with-zlib=../zlib-1.2.11 > + --with-zlib=../zlib-1.2.13 > > > zlib 1.3 was recently released and works just fine, so this probably should be bumped to 1.3 instead. > diff --git a/xml/en/docs/howto_build_on_win32.xml b/xml/en/docs/howto_build_on_win32.xml > --- a/xml/en/docs/howto_build_on_win32.xml > +++ b/xml/en/docs/howto_build_on_win32.xml > @@ -9,7 +9,7 @@ >
link="/en/docs/howto_build_on_win32.html" > lang="en" > - rev="25"> > + rev="26"> > >
> > @@ -81,8 +81,8 @@ > mkdir objs/lib > cd objs/lib > tar -xzf ../../pcre2-10.39.tar.gz > -tar -xzf ../../zlib-1.2.11.tar.gz > -tar -xzf ../../openssl-1.1.1m.tar.gz > +tar -xzf ../../zlib-1.2.13.tar.gz > +tar -xzf ../../openssl-3.0.10.tar.gz > > > > @@ -105,8 +105,8 @@ > --http-uwsgi-temp-path=temp/uwsgi_temp \ > --with-cc-opt=-DFD_SETSIZE=1024 \ > --with-pcre=objs/lib/pcre2-10.39 \ > - --with-zlib=objs/lib/zlib-1.2.11 \ > - --with-openssl=objs/lib/openssl-1.1.1m \ > + --with-zlib=objs/lib/zlib-1.2.13 \ > + --with-openssl=objs/lib/openssl-3.0.10 \ > --with-openssl-opt=no-asm \ > --with-http_ssl_module > And this used to match latest release of nginx/Windows, so zlib 1.2.13 and OpenSSL 3.0.10 are probably fine here. On the other hand, bumping to zlib 1.3 and OpenSSL 3.0.11 might be good enough as well (and will save an update to this file). > diff --git a/xml/ru/docs/configure.xml b/xml/ru/docs/configure.xml > --- a/xml/ru/docs/configure.xml > +++ b/xml/ru/docs/configure.xml > @@ -8,7 +8,7 @@ >
link="/ru/docs/configure.html" > lang="ru" > - rev="22"> > + rev="23"> > >
> > @@ -1250,7 +1250,7 @@ > > задаёт путь к исходным текстам библиотеки zlib. > Дистрибутив библиотеки (версию > -1.1.3—1.2.11) нужно взять на сайте > +1.1.3—1.2.13) нужно взять на сайте > zlib и распаковать. > Всё остальное сделают ./configure nginx’а и > make. > @@ -1339,7 +1339,7 @@ > --pid-path=/usr/local/nginx/nginx.pid > --with-http_ssl_module > --with-pcre=../pcre2-10.39 > - --with-zlib=../zlib-1.2.11 > + --with-zlib=../zlib-1.2.13 > > > Otherwise looks good to me. -- Maxim Dounin http://mdounin.ru/