aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey A. Osokin <osa@FreeBSD.org>2022-02-01 14:57:06 +0000
committerSergey A. Osokin <osa@FreeBSD.org>2022-02-01 14:57:26 +0000
commit117d256c411ae24dc0e1c0868a1825a59300ca4e (patch)
tree014c56233ef4a3989e9a0cac1f5f89fe91b9a3c1
parent37991a7381a067c2e744e7debafcf18aef664e35 (diff)
downloadports-117d256c411ae24dc0e1c0868a1825a59300ca4e.tar.gz
ports-117d256c411ae24dc0e1c0868a1825a59300ca4e.zip
www/nginx-devel: update HTTPv3/QUIC patch to the recent commit
-rw-r--r--www/nginx-devel/Makefile2
-rw-r--r--www/nginx-devel/files/extra-patch-httpv3785
2 files changed, 393 insertions, 394 deletions
diff --git a/www/nginx-devel/Makefile b/www/nginx-devel/Makefile
index 332014908b34..381dee0d872e 100644
--- a/www/nginx-devel/Makefile
+++ b/www/nginx-devel/Makefile
@@ -2,7 +2,7 @@
PORTNAME?= nginx
PORTVERSION= 1.21.6
-PORTREVISION= 3
+PORTREVISION= 4
CATEGORIES= www
MASTER_SITES= https://nginx.org/download/ \
LOCAL/osa
diff --git a/www/nginx-devel/files/extra-patch-httpv3 b/www/nginx-devel/files/extra-patch-httpv3
index 9f0ab11e7c7c..84104bfbf152 100644
--- a/www/nginx-devel/files/extra-patch-httpv3
+++ b/www/nginx-devel/files/extra-patch-httpv3
@@ -2,7 +2,7 @@ diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
-@@ -0,0 +1,261 @@
+@@ -0,0 +1,233 @@
+Experimental QUIC support for nginx
+-----------------------------------
+
@@ -39,8 +39,7 @@ new file mode 100644
+
+ What works now:
+
-+ Currently we support IETF-QUIC draft-29 through final RFC documents.
-+ Earlier drafts are NOT supported as they have incompatible wire format.
++ We support IETF QUIC version 1. Internet drafts are no longer supported.
+
+ nginx should be able to respond to HTTP/3 requests over QUIC and
+ it should be possible to upload and download big files without errors.
@@ -58,21 +57,6 @@ new file mode 100644
+ + Lost packets are detected and retransmitted properly
+ + Clients may migrate to new address
+
-+ Not (yet) supported features:
-+
-+ - Explicit Congestion Notification (ECN) as specified in quic-recovery [5]
-+ - A connection with the spin bit succeeds and the bit is spinning
-+ - Structured Logging
-+
-+ Since the code is experimental and still under development,
-+ a lot of things may not work as expected, for example:
-+
-+ - Flow control mechanism is basic and intended to avoid CPU hog and make
-+ simple interactions possible
-+
-+ - Not all protocol requirements are strictly followed; some of checks are
-+ omitted for the sake of simplicity of initial implementation
-+
+2. Installing
+
+ You will need a BoringSSL [4] library that provides QUIC support
@@ -183,21 +167,12 @@ new file mode 100644
+
+ * Browsers
+
-+ Known to work: Firefox 80+ and Chrome 85+ (QUIC draft 29+)
++ Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1)
+
+ Beware of strange issues: sometimes browser may decide to ignore QUIC
+ Cache clearing/restart might help. Always check access.log and
+ error.log to make sure you are using HTTP/3 and not TCP https.
+
-+ + to enable QUIC in Firefox, set the following in 'about:config':
-+ network.http.http3.enabled = true
-+
-+ + to enable QUIC in Chrome, enable it on command line and force it
-+ on your site:
-+
-+ $ ./chrome --enable-quic --quic-version=h3-29 \
-+ --origin-to-force-quic-on=example.com:8443
-+
+ * Console clients
+
+ Known to work: ngtcp2, firefox's neqo and chromium's console clients:
@@ -206,10 +181,7 @@ new file mode 100644
+
+ $ ./neqo-client https://127.0.0.1:8443/
+
-+ $ chromium-build/out/my_build/quic_client http://example.com:8443 \
-+ --quic_version=h3-29 \
-+ --allow_unknown_root_cert \
-+ --disable_certificate_verification
++ $ chromium-build/out/my_build/quic_client http://example.com:8443
+
+
+ If you've got it right, in the access log you should see something like:
@@ -227,7 +199,7 @@ new file mode 100644
+ + Ensure you are using the proper SSL library in runtime
+ (`nginx -V` will show you what you are using)
+
-+ + Ensure your client is actually sending QUIC requests
++ + Ensure your client is actually sending requests over QUIC
+ (see "Clients" section about browsers and cache)
+
+ We recommend to start with simple console client like ngtcp2
@@ -257,7 +229,7 @@ new file mode 100644
+
+ [1] https://datatracker.ietf.org/doc/html/rfc9000
+ [2] https://datatracker.ietf.org/doc/html/draft-ietf-quic-http
-+ [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel
++ [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/
+ [4] https://boringssl.googlesource.com/boringssl/
+ [5] https://datatracker.ietf.org/doc/html/rfc9002
+ [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen
@@ -962,7 +934,7 @@ diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h
diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c
--- a/src/event/ngx_event.c
+++ b/src/event/ngx_event.c
-@@ -266,6 +266,18 @@ ngx_process_events_and_timers(ngx_cycle_
+@@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_
ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
@@ -981,7 +953,7 @@ diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
-@@ -336,9 +348,15 @@ ngx_handle_write_event(ngx_event_t *wev,
+@@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev,
{
ngx_connection_t *c;
@@ -1002,7 +974,7 @@ diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c
diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h
--- a/src/event/ngx_event.h
+++ b/src/event/ngx_event.h
-@@ -493,12 +493,6 @@ extern ngx_module_t ngx_event_
+@@ -494,12 +494,6 @@ extern ngx_module_t ngx_event_
void ngx_event_accept(ngx_event_t *ev);
@@ -1015,7 +987,7 @@ diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle);
u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len);
-@@ -528,6 +522,7 @@ ngx_int_t ngx_send_lowat(ngx_connection_
+@@ -529,6 +523,7 @@ ngx_int_t ngx_send_lowat(ngx_connection_
#include <ngx_event_timer.h>
#include <ngx_event_posted.h>
@@ -1026,7 +998,7 @@ diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
-@@ -3146,6 +3146,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
+@@ -3149,6 +3149,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
ngx_err_t err;
ngx_uint_t tries;
@@ -1929,7 +1901,7 @@ diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic.c
-@@ -0,0 +1,1491 @@
+@@ -0,0 +1,1457 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -1948,7 +1920,6 @@ new file mode 100644
+ ngx_quic_header_t *pkt);
+static void ngx_quic_input_handler(ngx_event_t *rev);
+
-+static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc);
+static void ngx_quic_close_timer_handler(ngx_event_t *ev);
+
+static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
@@ -2142,17 +2113,13 @@ new file mode 100644
+
+ rc = ngx_quic_handle_datagram(c, c->buffer, conf);
+ if (rc != NGX_OK) {
-+ ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR);
++ ngx_quic_close_connection(c, rc);
+ return;
+ }
+
++ /* quic connection is now created */
+ qc = ngx_quic_get_connection(c);
+
-+ if (qc == NULL) {
-+ ngx_quic_close_connection(c, NGX_DONE);
-+ return;
-+ }
-+
+ ngx_add_timer(c->read, qc->tp.max_idle_timeout);
+ ngx_quic_connstate_dbg(c);
+
@@ -2261,8 +2228,7 @@ new file mode 100644
+ }
+ }
+
-+ if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid,
-+ qc->version)
++ if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid)
+ != NGX_OK)
+ {
+ return NULL;
@@ -2376,7 +2342,7 @@ new file mode 100644
+ return;
+ }
+
-+ if (rc == NGX_DECLINED) {
++ if (rc == NGX_DONE) {
+ return;
+ }
+
@@ -2392,54 +2358,22 @@ new file mode 100644
+void
+ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
+{
++ ngx_uint_t i;
+ ngx_pool_t *pool;
++ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+
-+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic ngx_quic_close_connection rc:%i", rc);
-+
+ qc = ngx_quic_get_connection(c);
+
+ if (qc == NULL) {
-+ if (rc == NGX_ERROR) {
-+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic close connection early error");
-+ }
-+
-+ } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) {
-+ return;
-+ }
-+
-+ if (c->ssl) {
-+ (void) ngx_ssl_shutdown(c);
-+ }
-+
-+ if (c->read->timer_set) {
-+ ngx_del_timer(c->read);
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet rejected rc:%i, cleanup connection", rc);
++ goto quic_done;
+ }
+
-+#if (NGX_STAT_STUB)
-+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
-+#endif
-+
-+ c->destroyed = 1;
-+
-+ pool = c->pool;
-+
-+ ngx_close_connection(c);
-+
-+ ngx_destroy_pool(pool);
-+}
-+
-+
-+static ngx_int_t
-+ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc)
-+{
-+ ngx_uint_t i;
-+ ngx_quic_send_ctx_t *ctx;
-+ ngx_quic_connection_t *qc;
-+
-+ qc = ngx_quic_get_connection(c);
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close %s rc:%i",
++ qc->closing ? "resumed": "initiated", rc);
+
+ if (!qc->closing) {
+
@@ -2458,10 +2392,11 @@ new file mode 100644
+ * closed and its state is discarded when it remains idle
+ */
+
-+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic closing %s connection",
-+ qc->draining ? "drained" : "idle");
++ /* this case also handles some errors from ngx_quic_run() */
+
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close silent drain:%d timedout:%d",
++ qc->draining, c->read->timedout);
+ } else {
+
+ /*
@@ -2476,7 +2411,7 @@ new file mode 100644
+
+ if (rc == NGX_OK) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic immediate close drain:%d",
++ "quic close immediate drain:%d",
+ qc->draining);
+
+ qc->close.log = c->log;
@@ -2496,7 +2431,7 @@ new file mode 100644
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic immediate close due to %s error: %ui %s",
++ "quic close immediate due to %serror: %ui %s",
+ qc->error_app ? "app " : "", qc->error,
+ qc->error_reason ? qc->error_reason : "");
+ }
@@ -2519,7 +2454,7 @@ new file mode 100644
+ }
+
+ if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) {
-+ return NGX_AGAIN;
++ return;
+ }
+
+ if (qc->push.timer_set) {
@@ -2539,18 +2474,37 @@ new file mode 100644
+ }
+
+ if (qc->close.timer_set) {
-+ return NGX_AGAIN;
++ return;
+ }
+
+ ngx_quic_close_sockets(c);
+
-+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic part of connection is terminated");
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed");
+
+ /* may be tested from SSL callback during SSL shutdown */
+ c->udp = NULL;
+
-+ return NGX_OK;
++quic_done:
++
++ if (c->ssl) {
++ (void) ngx_ssl_shutdown(c);
++ }
++
++ if (c->read->timer_set) {
++ ngx_del_timer(c->read);
++ }
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
++#endif
++
++ c->destroyed = 1;
++
++ pool = c->pool;
++
++ ngx_close_connection(c);
++
++ ngx_destroy_pool(pool);
+}
+
+
@@ -2633,22 +2587,18 @@ new file mode 100644
+#if (NGX_DEBUG)
+ if (pkt.parsed) {
+ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic packet %s done decr:%d pn:%L perr:%ui rc:%i",
-+ ngx_quic_level_name(pkt.level), pkt.decrypted,
-+ pkt.pn, pkt.error, rc);
++ "quic packet done rc:%i level:%s"
++ " decr:%d pn:%L perr:%ui",
++ rc, ngx_quic_level_name(pkt.level),
++ pkt.decrypted, pkt.pn, pkt.error);
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic packet done parse failed rc:%i", rc);
++ "quic packet done rc:%i parse failed", rc);
+ }
+#endif
+
-+ if (rc == NGX_ERROR) {
-+ return NGX_ERROR;
-+ }
-+
-+ if (rc == NGX_DONE) {
-+ /* stop further processing */
-+ return NGX_DECLINED;
++ if (rc == NGX_ERROR || rc == NGX_DONE) {
++ return rc;
+ }
+
+ if (rc == NGX_OK) {
@@ -2683,7 +2633,7 @@ new file mode 100644
+ }
+
+ if (!good) {
-+ return NGX_DECLINED;
++ return NGX_DONE;
+ }
+
+ qc = ngx_quic_get_connection(c);
@@ -2717,13 +2667,13 @@ new file mode 100644
+
+ rc = ngx_quic_parse_packet(pkt);
+
-+ if (rc == NGX_DECLINED || rc == NGX_ERROR) {
-+ return rc;
++ if (rc == NGX_ERROR) {
++ return NGX_DECLINED;
+ }
+
+ pkt->parsed = 1;
+
-+ c->log->action = "processing quic packet";
++ c->log->action = "handling quic packet";
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic packet rx dcid len:%uz %xV",
@@ -2808,10 +2758,12 @@ new file mode 100644
+ }
+
+ if (pkt->level != ssl_encryption_initial) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic expected initial, got handshake");
+ return NGX_ERROR;
+ }
+
-+ c->log->action = "processing initial packet";
++ c->log->action = "handling initial packet";
+
+ if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
+ /* RFC 9000, 7.2. Negotiating Connection IDs */
@@ -3363,7 +3315,7 @@ new file mode 100644
+{
+ ngx_connection_t *c;
+
-+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer");
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler");
+
+ c = ev->data;
+
@@ -3407,25 +3359,11 @@ new file mode 100644
+
+ ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason);
+}
-+
-+
-+uint32_t
-+ngx_quic_version(ngx_connection_t *c)
-+{
-+ uint32_t version;
-+ ngx_quic_connection_t *qc;
-+
-+ qc = ngx_quic_get_connection(c);
-+
-+ version = qc->version;
-+
-+ return (version & 0xff000000) == 0xff000000 ? version & 0xff : version;
-+}
diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic.h
-@@ -0,0 +1,88 @@
+@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -3456,44 +3394,66 @@ new file mode 100644
+#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
+
+
-+typedef struct {
-+ ngx_ssl_t *ssl;
++typedef enum {
++ NGX_QUIC_STREAM_SEND_READY = 0,
++ NGX_QUIC_STREAM_SEND_SEND,
++ NGX_QUIC_STREAM_SEND_DATA_SENT,
++ NGX_QUIC_STREAM_SEND_DATA_RECVD,
++ NGX_QUIC_STREAM_SEND_RESET_SENT,
++ NGX_QUIC_STREAM_SEND_RESET_RECVD
++} ngx_quic_stream_send_state_e;
++
++
++typedef enum {
++ NGX_QUIC_STREAM_RECV_RECV = 0,
++ NGX_QUIC_STREAM_RECV_SIZE_KNOWN,
++ NGX_QUIC_STREAM_RECV_DATA_RECVD,
++ NGX_QUIC_STREAM_RECV_DATA_READ,
++ NGX_QUIC_STREAM_RECV_RESET_RECVD,
++ NGX_QUIC_STREAM_RECV_RESET_READ
++} ngx_quic_stream_recv_state_e;
+
-+ ngx_flag_t retry;
-+ ngx_flag_t gso_enabled;
-+ ngx_flag_t disable_active_migration;
-+ ngx_msec_t timeout;
-+ ngx_str_t host_key;
-+ size_t mtu;
-+ size_t stream_buffer_size;
-+ ngx_uint_t max_concurrent_streams_bidi;
-+ ngx_uint_t max_concurrent_streams_uni;
-+ ngx_uint_t active_connection_id_limit;
-+ ngx_int_t stream_close_code;
-+ ngx_int_t stream_reject_code_uni;
-+ ngx_int_t stream_reject_code_bidi;
+
-+ u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
-+ u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
++typedef struct {
++ ngx_ssl_t *ssl;
++
++ ngx_flag_t retry;
++ ngx_flag_t gso_enabled;
++ ngx_flag_t disable_active_migration;
++ ngx_msec_t timeout;
++ ngx_str_t host_key;
++ size_t mtu;
++ size_t stream_buffer_size;
++ ngx_uint_t max_concurrent_streams_bidi;
++ ngx_uint_t max_concurrent_streams_uni;
++ ngx_uint_t active_connection_id_limit;
++ ngx_int_t stream_close_code;
++ ngx_int_t stream_reject_code_uni;
++ ngx_int_t stream_reject_code_bidi;
++
++ u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
++ u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
+} ngx_quic_conf_t;
+
+
+struct ngx_quic_stream_s {
-+ ngx_rbtree_node_t node;
-+ ngx_queue_t queue;
-+ ngx_connection_t *parent;
-+ ngx_connection_t *connection;
-+ uint64_t id;
-+ uint64_t acked;
-+ uint64_t send_max_data;
-+ uint64_t recv_max_data;
-+ uint64_t recv_offset;
-+ uint64_t recv_window;
-+ uint64_t recv_last;
-+ uint64_t final_size;
-+ ngx_chain_t *in;
-+ ngx_chain_t *out;
-+ ngx_uint_t cancelable; /* unsigned cancelable:1; */
++ ngx_rbtree_node_t node;
++ ngx_queue_t queue;
++ ngx_connection_t *parent;
++ ngx_connection_t *connection;
++ uint64_t id;
++ uint64_t acked;
++ uint64_t send_max_data;
++ uint64_t recv_max_data;
++ uint64_t recv_offset;
++ uint64_t recv_window;
++ uint64_t recv_last;
++ uint64_t final_size;
++ ngx_chain_t *in;
++ ngx_chain_t *out;
++ ngx_uint_t cancelable; /* unsigned cancelable:1; */
++ ngx_quic_stream_send_state_e send_state;
++ ngx_quic_stream_recv_state_e recv_state;
+};
+
+
@@ -3505,7 +3465,6 @@ new file mode 100644
+ const char *reason);
+ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err);
+ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how);
-+uint32_t ngx_quic_version(ngx_connection_t *c);
+ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
+ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat);
+ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
@@ -3518,7 +3477,7 @@ diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic_ack.c
-@@ -0,0 +1,1190 @@
+@@ -0,0 +1,1193 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -4138,10 +4097,13 @@ new file mode 100644
+ case NGX_QUIC_FT_STREAM:
+ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
+
-+ if (qs && qs->connection->write->error) {
-+ /* RESET_STREAM was sent */
-+ ngx_quic_free_frame(c, f);
-+ break;
++ if (qs) {
++ if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT
++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD)
++ {
++ ngx_quic_free_frame(c, f);
++ break;
++ }
+ }
+
+ /* fall through */
@@ -6091,7 +6053,7 @@ new file mode 100644
+ }
+
+ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic cid #%uL received id:%uz:%xV:%*xs",
++ "quic cid seq:%uL received id:%uz:%xV:%*xs",
+ cid->seqnum, id->len, id,
+ (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token);
+
@@ -6171,7 +6133,7 @@ new file mode 100644
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic socket #%uL is retired", qsock->sid.seqnum);
++ "quic socket seq:%uL is retired", qsock->sid.seqnum);
+
+ ngx_quic_close_socket(c, qsock);
+
@@ -7349,7 +7311,7 @@ new file mode 100644
+ }
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
-+ "quic path #%uL addr:%V successfully validated",
++ "quic path seq:%uL addr:%V successfully validated",
+ path->seqnum, &path->addr_text);
+
+ ngx_quic_path_dbg(c, "is validated", path);
@@ -7407,7 +7369,7 @@ new file mode 100644
+ NGX_SOCKADDR_STRLEN, 1);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic path #%uL created addr:%V",
++ "quic path seq:%uL created addr:%V",
+ path->seqnum, &path->addr_text);
+ return path;
+}
@@ -7535,8 +7497,8 @@ new file mode 100644
+ path->received += len;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic packet len:%O via sock#%uL path#%uL",
-+ len, qsock->sid.seqnum, path->seqnum);
++ "quic packet len:%O via sock seq:%L path seq:%uL",
++ len, (int64_t) qsock->sid.seqnum, path->seqnum);
+ ngx_quic_path_dbg(c, "status", path);
+
+ return NGX_OK;
@@ -7564,7 +7526,7 @@ new file mode 100644
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic path #%uL addr:%V retired",
++ "quic path seq:%uL addr:%V retired",
+ path->seqnum, &path->addr_text);
+
+ return NGX_OK;
@@ -7587,7 +7549,7 @@ new file mode 100644
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic send path set to #%uL addr:%V",
++ "quic send path set to seq:%uL addr:%V",
+ path->seqnum, &path->addr_text);
+}
+
@@ -7664,7 +7626,7 @@ new file mode 100644
+ }
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
-+ "quic migrated to path#%uL addr:%V",
++ "quic migrated to path seq:%uL addr:%V",
+ qc->path->seqnum, &qc->path->addr_text);
+
+ ngx_quic_path_dbg(c, "is now active", qc->path);
@@ -7683,7 +7645,7 @@ new file mode 100644
+ qc = ngx_quic_get_connection(c);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic initiated validation of path #%uL", path->seqnum);
++ "quic initiated validation of path seq:%uL", path->seqnum);
+
+ path->validating = 1;
+
@@ -7719,7 +7681,7 @@ new file mode 100644
+ ngx_quic_frame_t frame;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic path #%uL send path_challenge tries:%ui",
++ "quic path seq:%uL send path_challenge tries:%ui",
+ path->seqnum, path->tries);
+
+ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
@@ -7809,7 +7771,7 @@ new file mode 100644
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
-+ "quic path #%uL validation failed", path->seqnum);
++ "quic path seq:%uL validation failed", path->seqnum);
+
+ /* found expired path */
+
@@ -7843,7 +7805,7 @@ new file mode 100644
+ ngx_quic_set_connection_path(c, qc->path);
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
-+ "quic path #%uL addr:%V is restored from backup",
++ "quic path seq:%uL addr:%V is restored from backup",
+ qc->path->seqnum, &qc->path->addr_text);
+
+ ngx_quic_path_dbg(c, "is active", qc->path);
@@ -7885,7 +7847,7 @@ new file mode 100644
+
+#define ngx_quic_path_dbg(c, msg, path) \
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \
-+ "quic path#%uL %s sent:%O recvd:%O state:%s%s%s", \
++ "quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \
+ path->seqnum, msg, path->sent, path->received, \
+ path->limited ? "L" : "", path->validated ? "V": "N", \
+ path->validating ? "R": "");
@@ -7910,7 +7872,7 @@ diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_q
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic_output.c
-@@ -0,0 +1,1273 @@
+@@ -0,0 +1,1268 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -8318,7 +8280,7 @@ new file mode 100644
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ char msg_control[CMSG_SPACE(sizeof(uint16_t))
+ + CMSG_SPACE(sizeof(ngx_addrinfo_t))];
+#else
@@ -8351,7 +8313,7 @@ new file mode 100644
+ valp = (void *) CMSG_DATA(cmsg);
+ *valp = segment;
+
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
+ cmsg = CMSG_NXTHDR(&msg, cmsg);
+ clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
@@ -8586,13 +8548,10 @@ new file mode 100644
+ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
+ ngx_quic_header_t *pkt, ngx_quic_path_t *path)
+{
-+ ngx_quic_socket_t *qsock;
+ ngx_quic_connection_t *qc;
+
+ qc = ngx_quic_get_connection(c);
+
-+ qsock = ngx_quic_get_socket(c);
-+
+ ngx_memzero(pkt, sizeof(ngx_quic_header_t));
+
+ pkt->flags = NGX_QUIC_PKT_FIXED_BIT;
@@ -8612,8 +8571,7 @@ new file mode 100644
+ pkt->dcid.data = path->cid->id;
+ pkt->dcid.len = path->cid->len;
+
-+ pkt->scid.data = qsock->sid.id;
-+ pkt->scid.len = qsock->sid.len;
++ pkt->scid = qc->tp.initial_scid;
+
+ pkt->version = qc->version;
+ pkt->log = c->log;
@@ -8632,7 +8590,7 @@ new file mode 100644
+ ssize_t n;
+ struct iovec iov;
+ struct msghdr msg;
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ struct cmsghdr *cmsg;
+ char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
+#endif
@@ -8648,7 +8606,7 @@ new file mode 100644
+ msg.msg_name = sockaddr;
+ msg.msg_namelen = socklen;
+
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
+
+ msg.msg_control = msg_control;
@@ -8863,8 +8821,7 @@ new file mode 100644
+ return NGX_ERROR;
+ }
+
-+ if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid,
-+ inpkt->version)
++ if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
@@ -8897,7 +8854,7 @@ new file mode 100644
+ return NGX_ERROR;
+ }
+
-+ return NGX_OK;
++ return NGX_DONE;
+}
+
+
@@ -9233,7 +9190,7 @@ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_eve
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic_protection.c
-@@ -0,0 +1,1186 @@
+@@ -0,0 +1,1177 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -9382,7 +9339,7 @@ new file mode 100644
+
+ngx_int_t
+ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys,
-+ ngx_str_t *secret, uint32_t version)
++ ngx_str_t *secret)
+{
+ size_t is_len;
+ uint8_t is[SHA256_DIGEST_LENGTH];
@@ -9393,9 +9350,6 @@ new file mode 100644
+ static const uint8_t salt[20] =
+ "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17"
+ "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a";
-+ static const uint8_t salt29[20] =
-+ "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97"
-+ "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99";
+
+ client = &keys->secrets[ssl_encryption_initial].client;
+ server = &keys->secrets[ssl_encryption_initial].server;
@@ -9411,7 +9365,7 @@ new file mode 100644
+ is_len = SHA256_DIGEST_LENGTH;
+
+ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
-+ (version & 0xff000000) ? salt29 : salt, sizeof(salt))
++ salt, sizeof(salt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
@@ -10128,12 +10082,8 @@ new file mode 100644
+ /* 5.8. Retry Packet Integrity */
+ static u_char key[16] =
+ "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e";
-+ static u_char key29[16] =
-+ "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1";
+ static u_char nonce[NGX_QUIC_IV_LEN] =
+ "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb";
-+ static u_char nonce29[NGX_QUIC_IV_LEN] =
-+ "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c";
+ static ngx_str_t in = ngx_string("");
+
+ ad.data = res->data;
@@ -10152,12 +10102,10 @@ new file mode 100644
+ }
+
+ secret.key.len = sizeof(key);
-+ secret.key.data = (pkt->version & 0xff000000) ? key29 : key;
++ secret.key.data = key;
+ secret.iv.len = NGX_QUIC_IV_LEN;
+
-+ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag,
-+ (pkt->version & 0xff000000) ? nonce29 : nonce,
-+ &in, &ad, pkt->log)
++ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
@@ -10446,7 +10394,7 @@ new file mode 100644
+
+ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool);
+ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool,
-+ ngx_quic_keys_t *keys, ngx_str_t *secret, uint32_t version);
++ ngx_quic_keys_t *keys, ngx_str_t *secret);
+ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool,
+ ngx_uint_t is_write, ngx_quic_keys_t *keys,
+ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
@@ -10629,7 +10577,7 @@ new file mode 100644
+ qc->nsockets--;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic socket #%L closed nsock:%ui",
++ "quic socket seq:%L closed nsock:%ui",
+ (int64_t) qsock->sid.seqnum, qc->nsockets);
+}
+
@@ -10654,7 +10602,7 @@ new file mode 100644
+ qsock->quic = qc;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic socket #%L listening at sid:%xV nsock:%ui",
++ "quic socket seq:%L listening at sid:%xV nsock:%ui",
+ (int64_t) sid->seqnum, &id, qc->nsockets);
+
+ return NGX_OK;
@@ -11307,8 +11255,8 @@ new file mode 100644
+ }
+#endif
+
-+#if BORINGSSL_API_VERSION >= 13
-+ SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1);
++#if (BORINGSSL_API_VERSION >= 13 && BORINGSSL_API_VERSION < 15)
++ SSL_set_quic_use_legacy_codepoint(ssl_conn, 0);
+#endif
+
+ qsock = ngx_quic_get_socket(c);
@@ -11384,7 +11332,7 @@ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic_streams.c
-@@ -0,0 +1,1599 @@
+@@ -0,0 +1,1586 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -11421,17 +11369,19 @@ new file mode 100644
+static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last);
+static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c);
+static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c);
++static void ngx_quic_set_event(ngx_event_t *ev);
+
+
+ngx_connection_t *
+ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi)
+{
+ uint64_t id;
-+ ngx_quic_stream_t *qs, *nqs;
++ ngx_connection_t *pc;
++ ngx_quic_stream_t *nqs;
+ ngx_quic_connection_t *qc;
+
-+ qs = c->quic;
-+ qc = ngx_quic_get_connection(qs->parent);
++ pc = c->quic ? c->quic->parent : c;
++ qc = ngx_quic_get_connection(pc);
+
+ if (bidi) {
+ if (qc->streams.server_streams_bidi
@@ -11477,7 +11427,7 @@ new file mode 100644
+ qc->streams.server_streams_uni++;
+ }
+
-+ nqs = ngx_quic_create_stream(qs->parent, id);
++ nqs = ngx_quic_create_stream(pc, id);
+ if (nqs == NULL) {
+ return NULL;
+ }
@@ -11542,7 +11492,6 @@ new file mode 100644
+{
+ ngx_pool_t *pool;
+ ngx_queue_t *q;
-+ ngx_event_t *rev, *wev;
+ ngx_rbtree_t *tree;
+ ngx_rbtree_node_t *node;
+ ngx_quic_stream_t *qs;
@@ -11578,19 +11527,11 @@ new file mode 100644
+ {
+ qs = (ngx_quic_stream_t *) node;
+
-+ rev = qs->connection->read;
-+ rev->error = 1;
-+ rev->ready = 1;
-+
-+ wev = qs->connection->write;
-+ wev->error = 1;
-+ wev->ready = 1;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
+
-+ ngx_post_event(rev, &ngx_posted_events);
-+
-+ if (rev->timer_set) {
-+ ngx_del_timer(rev);
-+ }
++ ngx_quic_set_event(qs->connection->read);
++ ngx_quic_set_event(qs->connection->write);
+
+#if (NGX_DEBUG)
+ ns++;
@@ -11607,19 +11548,22 @@ new file mode 100644
+ngx_int_t
+ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err)
+{
-+ ngx_event_t *wev;
+ ngx_connection_t *pc;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
-+ wev = c->write;
++ qs = c->quic;
+
-+ if (wev->error) {
++ if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD
++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT
++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD)
++ {
+ return NGX_OK;
+ }
+
-+ qs = c->quic;
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
++
+ pc = qs->parent;
+ qc = ngx_quic_get_connection(pc);
+
@@ -11636,9 +11580,6 @@ new file mode 100644
+
+ ngx_quic_queue_frame(qc, frame);
+
-+ wev->error = 1;
-+ wev->ready = 1;
-+
+ return NGX_OK;
+}
+
@@ -11646,27 +11587,15 @@ new file mode 100644
+ngx_int_t
+ngx_quic_shutdown_stream(ngx_connection_t *c, int how)
+{
-+ ngx_quic_stream_t *qs;
-+
-+ qs = c->quic;
-+
+ if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) {
-+ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED)
-+ || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
-+ {
-+ if (ngx_quic_shutdown_stream_send(c) != NGX_OK) {
-+ return NGX_ERROR;
-+ }
++ if (ngx_quic_shutdown_stream_send(c) != NGX_OK) {
++ return NGX_ERROR;
+ }
+ }
+
+ if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) {
-+ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0
-+ || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
-+ {
-+ if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) {
-+ return NGX_ERROR;
-+ }
++ if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) {
++ return NGX_ERROR;
+ }
+ }
+
@@ -11677,19 +11606,21 @@ new file mode 100644
+static ngx_int_t
+ngx_quic_shutdown_stream_send(ngx_connection_t *c)
+{
-+ ngx_event_t *wev;
+ ngx_connection_t *pc;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
-+ wev = c->write;
++ qs = c->quic;
+
-+ if (wev->error) {
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_READY
++ && qs->send_state != NGX_QUIC_STREAM_SEND_SEND)
++ {
+ return NGX_OK;
+ }
+
-+ qs = c->quic;
++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT;
++
+ pc = qs->parent;
+ qc = ngx_quic_get_connection(pc);
+
@@ -11713,8 +11644,6 @@ new file mode 100644
+
+ ngx_quic_queue_frame(qc, frame);
+
-+ wev->error = 1;
-+
+ return NGX_OK;
+}
+
@@ -11722,19 +11651,19 @@ new file mode 100644
+static ngx_int_t
+ngx_quic_shutdown_stream_recv(ngx_connection_t *c)
+{
-+ ngx_event_t *rev;
+ ngx_connection_t *pc;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
-+ rev = c->read;
++ qs = c->quic;
+
-+ if (rev->pending_eof || rev->error) {
++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
++ && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
++ {
+ return NGX_OK;
+ }
+
-+ qs = c->quic;
+ pc = qs->parent;
+ qc = ngx_quic_get_connection(pc);
+
@@ -11757,8 +11686,6 @@ new file mode 100644
+
+ ngx_quic_queue_frame(qc, frame);
+
-+ rev->error = 1;
-+
+ return NGX_OK;
+}
+
@@ -12076,9 +12003,13 @@ new file mode 100644
+ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
+ qs->send_max_data = qc->ctp.initial_max_stream_data_uni;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ;
++ qs->send_state = NGX_QUIC_STREAM_SEND_READY;
+
+ } else {
+ qs->recv_max_data = qc->tp.initial_max_stream_data_uni;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RECV;
++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD;
+ }
+
+ } else {
@@ -12090,6 +12021,9 @@ new file mode 100644
+ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local;
+ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote;
+ }
++
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RECV;
++ qs->send_state = NGX_QUIC_STREAM_SEND_READY;
+ }
+
+ qs->recv_window = qs->recv_max_data;
@@ -12130,25 +12064,19 @@ new file mode 100644
+ pc = qs->parent;
+ rev = c->read;
+
-+ if (rev->error) {
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
++ || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
++ {
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ;
++ rev->error = 1;
+ return NGX_ERROR;
+ }
+
-+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic stream id:0x%xL recv eof:%d buf:%uz",
-+ qs->id, rev->pending_eof, size);
-+
-+ if (qs->in == NULL || qs->in->buf->sync) {
-+ rev->ready = 0;
-+
-+ if (qs->recv_offset == qs->final_size) {
-+ rev->eof = 1;
-+ return 0;
-+ }
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv buf:%uz", qs->id, size);
+
-+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-+ "quic stream id:0x%xL recv() not ready", qs->id);
-+ return NGX_AGAIN;
++ if (size == 0) {
++ return 0;
+ }
+
+ in = ngx_quic_read_chain(pc, &qs->in, size);
@@ -12166,8 +12094,23 @@ new file mode 100644
+
+ ngx_quic_free_chain(pc, in);
+
-+ if (qs->in == NULL) {
-+ rev->ready = rev->pending_eof;
++ if (len == 0) {
++ rev->ready = 0;
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN
++ && qs->recv_offset == qs->final_size)
++ {
++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ;
++ }
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) {
++ rev->eof = 1;
++ return 0;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv() not ready", qs->id);
++ return NGX_AGAIN;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -12225,10 +12168,15 @@ new file mode 100644
+ qc = ngx_quic_get_connection(pc);
+ wev = c->write;
+
-+ if (wev->error) {
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_READY
++ && qs->send_state != NGX_QUIC_STREAM_SEND_SEND)
++ {
++ wev->error = 1;
+ return NGX_CHAIN_ERROR;
+ }
+
++ qs->send_state = NGX_QUIC_STREAM_SEND_SEND;
++
+ flow = ngx_quic_max_stream_flow(c);
+ if (flow == 0) {
+ wev->ready = 0;
@@ -12406,7 +12354,6 @@ new file mode 100644
+ ngx_quic_frame_t *frame)
+{
+ uint64_t last;
-+ ngx_event_t *rev;
+ ngx_connection_t *sc;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
@@ -12437,9 +12384,9 @@ new file mode 100644
+
+ sc = qs->connection;
+
-+ rev = sc->read;
-+
-+ if (rev->error) {
++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
++ && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
++ {
+ return NGX_OK;
+ }
+
@@ -12472,8 +12419,8 @@ new file mode 100644
+ return NGX_ERROR;
+ }
+
-+ rev->pending_eof = 1;
+ qs->final_size = last;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN;
+ }
+
+ if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length,
@@ -12484,11 +12431,7 @@ new file mode 100644
+ }
+
+ if (f->offset == qs->recv_offset) {
-+ rev->ready = 1;
-+
-+ if (rev->active) {
-+ ngx_post_event(rev, &ngx_posted_events);
-+ }
++ ngx_quic_set_event(sc->read);
+ }
+
+ return NGX_OK;
@@ -12499,7 +12442,6 @@ new file mode 100644
+ngx_quic_handle_max_data_frame(ngx_connection_t *c,
+ ngx_quic_max_data_frame_t *f)
+{
-+ ngx_event_t *wev;
+ ngx_rbtree_t *tree;
+ ngx_rbtree_node_t *node;
+ ngx_quic_stream_t *qs;
@@ -12521,12 +12463,7 @@ new file mode 100644
+ node = ngx_rbtree_next(tree, node))
+ {
+ qs = (ngx_quic_stream_t *) node;
-+ wev = qs->connection->write;
-+
-+ if (wev->active) {
-+ wev->ready = 1;
-+ ngx_post_event(wev, &ngx_posted_events);
-+ }
++ ngx_quic_set_event(qs->connection->write);
+ }
+ }
+
@@ -12587,7 +12524,6 @@ new file mode 100644
+ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f)
+{
+ uint64_t sent;
-+ ngx_event_t *wev;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
@@ -12617,12 +12553,7 @@ new file mode 100644
+ sent = qs->connection->sent;
+
+ if (sent >= qs->send_max_data) {
-+ wev = qs->connection->write;
-+
-+ if (wev->active) {
-+ wev->ready = 1;
-+ ngx_post_event(wev, &ngx_posted_events);
-+ }
++ ngx_quic_set_event(qs->connection->write);
+ }
+
+ qs->send_max_data = f->limit;
@@ -12635,7 +12566,6 @@ new file mode 100644
+ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f)
+{
-+ ngx_event_t *rev;
+ ngx_connection_t *sc;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
@@ -12659,11 +12589,15 @@ new file mode 100644
+ return NGX_OK;
+ }
+
-+ sc = qs->connection;
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
++ || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
++ {
++ return NGX_OK;
++ }
++
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
+
-+ rev = sc->read;
-+ rev->error = 1;
-+ rev->ready = 1;
++ sc = qs->connection;
+
+ if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) {
+ return NGX_ERROR;
@@ -12685,9 +12619,7 @@ new file mode 100644
+ return NGX_ERROR;
+ }
+
-+ if (rev->active) {
-+ ngx_post_event(rev, &ngx_posted_events);
-+ }
++ ngx_quic_set_event(qs->connection->read);
+
+ return NGX_OK;
+}
@@ -12697,7 +12629,6 @@ new file mode 100644
+ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f)
+{
-+ ngx_event_t *wev;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
@@ -12724,11 +12655,7 @@ new file mode 100644
+ return NGX_ERROR;
+ }
+
-+ wev = qs->connection->write;
-+
-+ if (wev->active) {
-+ ngx_post_event(wev, &ngx_posted_events);
-+ }
++ ngx_quic_set_event(qs->connection->write);
+
+ return NGX_OK;
+}
@@ -12767,7 +12694,6 @@ new file mode 100644
+ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
+{
+ uint64_t sent, unacked;
-+ ngx_event_t *wev;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
@@ -12778,13 +12704,11 @@ new file mode 100644
+ return;
+ }
+
-+ wev = qs->connection->write;
+ sent = qs->connection->sent;
+ unacked = sent - qs->acked;
+
-+ if (unacked >= qc->conf->stream_buffer_size && wev->active) {
-+ wev->ready = 1;
-+ ngx_post_event(wev, &ngx_posted_events);
++ if (unacked >= qc->conf->stream_buffer_size) {
++ ngx_quic_set_event(qs->connection->write);
+ }
+
+ qs->acked += f->u.stream.length;
@@ -12799,11 +12723,9 @@ new file mode 100644
+ngx_quic_control_flow(ngx_connection_t *c, uint64_t last)
+{
+ uint64_t len;
-+ ngx_event_t *rev;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
-+ rev = c->read;
+ qs = c->quic;
+ qc = ngx_quic_get_connection(qs->parent);
+
@@ -12820,7 +12742,9 @@ new file mode 100644
+
+ qs->recv_last += len;
+
-+ if (!rev->error && qs->recv_last > qs->recv_max_data) {
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV
++ && qs->recv_last > qs->recv_max_data)
++ {
+ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
+ return NGX_ERROR;
+ }
@@ -12840,12 +12764,10 @@ new file mode 100644
+ngx_quic_update_flow(ngx_connection_t *c, uint64_t last)
+{
+ uint64_t len;
-+ ngx_event_t *rev;
+ ngx_connection_t *pc;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
-+ rev = c->read;
+ qs = c->quic;
+ pc = qs->parent;
+ qc = ngx_quic_get_connection(pc);
@@ -12861,9 +12783,7 @@ new file mode 100644
+
+ qs->recv_offset += len;
+
-+ if (!rev->pending_eof && !rev->error
-+ && qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2)
-+ {
++ if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) {
+ if (ngx_quic_update_max_stream_data(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
@@ -12896,6 +12816,10 @@ new file mode 100644
+ pc = qs->parent;
+ qc = ngx_quic_get_connection(pc);
+
++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) {
++ return NGX_OK;
++ }
++
+ recv_max_data = qs->recv_offset + qs->recv_window;
+
+ if (qs->recv_max_data == recv_max_data) {
@@ -12958,6 +12882,17 @@ new file mode 100644
+}
+
+
++static void
++ngx_quic_set_event(ngx_event_t *ev)
++{
++ ev->ready = 1;
++
++ if (ev->active) {
++ ngx_post_event(ev, &ngx_posted_events);
++ }
++}
++
++
+ngx_int_t
+ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
+{
@@ -13365,7 +13300,7 @@ diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_even
new file mode 100644
--- /dev/null
+++ b/src/event/quic/ngx_event_quic_transport.c
-@@ -0,0 +1,2170 @@
+@@ -0,0 +1,2164 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
@@ -13441,8 +13376,6 @@ new file mode 100644
+#define ngx_quic_build_int_set(p, value, len, bits) \
+ (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6))
+
-+#define NGX_QUIC_VERSION(c) (0xff000000 + (c))
-+
+
+static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out);
+static ngx_uint_t ngx_quic_varint_len(uint64_t value);
@@ -13505,10 +13438,6 @@ new file mode 100644
+uint32_t ngx_quic_versions[] = {
+ /* QUICv1 */
+ 0x00000001,
-+ NGX_QUIC_VERSION(29),
-+ NGX_QUIC_VERSION(30),
-+ NGX_QUIC_VERSION(31),
-+ NGX_QUIC_VERSION(32),
+};
+
+#define NGX_QUIC_NVERSIONS \
@@ -13659,14 +13588,14 @@ new file mode 100644
+
+ if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK)
+ {
-+ return NGX_DECLINED;
++ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
-+ return NGX_DECLINED;
++ return NGX_ERROR;
+ }
+
+ if (!ngx_quic_supported_version(pkt->version)) {
@@ -13674,7 +13603,7 @@ new file mode 100644
+ }
+
+ if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) {
-+ return NGX_DECLINED;
++ return NGX_ERROR;
+ }
+
+ return NGX_OK;
@@ -15941,15 +15870,12 @@ new file mode 100644
diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
-@@ -419,16 +419,22 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
+@@ -419,16 +419,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
unsigned char *outlen, const unsigned char *in, unsigned int inlen,
void *arg)
{
- unsigned int srvlen;
- unsigned char *srv;
-+#if (NGX_HTTP_V3)
-+ const char *fmt;
-+#endif
+ unsigned int srvlen;
+ unsigned char *srv;
#if (NGX_DEBUG)
@@ -15971,7 +15897,7 @@ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_
c = ngx_ssl_get_connection(ssl_conn);
#endif
-@@ -441,14 +447,46 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
+@@ -441,14 +444,34 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
}
#endif
@@ -15995,23 +15921,11 @@ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_
+ if (h3scf->hq) {
+ srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO;
+ srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1;
-+ fmt = NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT;
+ } else
+#endif
+ {
+ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO;
+ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1;
-+ fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT;
-+ }
-+
-+ /* QUIC draft */
-+
-+ if (ngx_quic_version(c) > 1) {
-+ srv = ngx_pnalloc(c->pool, sizeof("\x05h3-xx") - 1);
-+ if (srv == NULL) {
-+ return SSL_TLSEXT_ERR_NOACK;
-+ }
-+ srvlen = ngx_sprintf(srv, fmt, ngx_quic_version(c)) - srv;
+ }
+
+ } else
@@ -16019,7 +15933,7 @@ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_
{
srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1;
-@@ -1240,6 +1278,7 @@ static ngx_int_t
+@@ -1240,6 +1263,7 @@ static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
ngx_uint_t a, p, s;
@@ -16027,7 +15941,7 @@ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_
ngx_http_conf_addr_t *addr;
ngx_http_conf_port_t *port;
ngx_http_ssl_srv_conf_t *sscf;
-@@ -1289,22 +1328,38 @@ ngx_http_ssl_init(ngx_conf_t *cf)
+@@ -1289,22 +1313,38 @@ ngx_http_ssl_init(ngx_conf_t *cf)
addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
@@ -16069,7 +15983,7 @@ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_
return NGX_ERROR;
}
-@@ -1325,8 +1380,8 @@ ngx_http_ssl_init(ngx_conf_t *cf)
+@@ -1325,8 +1365,8 @@ ngx_http_ssl_init(ngx_conf_t *cf)
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"no \"ssl_certificate\" is defined for "
@@ -16599,7 +16513,7 @@ diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c
new file mode 100644
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.c
-@@ -0,0 +1,115 @@
+@@ -0,0 +1,123 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
@@ -16649,6 +16563,10 @@ new file mode 100644
+ h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
+ h3c->keepalive.cancelable = 1;
+
++ h3c->table.send_insert_count.log = pc->log;
++ h3c->table.send_insert_count.data = pc;
++ h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
++
+ cln = ngx_pool_cleanup_add(pc->pool, 0);
+ if (cln == NULL) {
+ goto failed;
@@ -16680,8 +16598,8 @@ new file mode 100644
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler");
+
-+ ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
-+ "keepalive timeout");
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
++ "keepalive timeout");
+}
+
+
@@ -16695,6 +16613,10 @@ new file mode 100644
+ if (h3c->keepalive.timer_set) {
+ ngx_del_timer(&h3c->keepalive);
+ }
++
++ if (h3c->table.send_insert_count.posted) {
++ ngx_delete_posted_event(&h3c->table.send_insert_count);
++ }
+}
+
+
@@ -16742,10 +16664,7 @@ new file mode 100644
+
+
+#define NGX_HTTP_V3_ALPN_PROTO "\x02h3"
-+#define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD"
-+
+#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop"
-+#define NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT "\x05hq-%02uD"
+
+#define NGX_HTTP_V3_VARLEN_INT_LEN 4
+#define NGX_HTTP_V3_PREFIX_INT_LEN 11
@@ -16803,7 +16722,8 @@ new file mode 100644
+
+
+#define ngx_http_quic_get_connection(c) \
-+ ((ngx_http_connection_t *) (c)->quic->parent->data)
++ ((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
+
@@ -16816,10 +16736,12 @@ new file mode 100644
+ module)
+
+#define ngx_http_v3_finalize_connection(c, code, reason) \
-+ ngx_quic_finalize_connection(c->quic->parent, code, reason)
++ ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \
++ code, reason)
+
+#define ngx_http_v3_shutdown_connection(c, code, reason) \
-+ ngx_quic_shutdown_connection(c->quic->parent, code, reason)
++ ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \
++ code, reason)
+
+
+typedef struct {
@@ -19337,7 +19259,7 @@ diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c
new file mode 100644
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.c
-@@ -0,0 +1,2005 @@
+@@ -0,0 +1,2007 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
@@ -19735,6 +19657,8 @@ new file mode 100644
+ if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) {
+ return NGX_ERROR;
+ }
++
++ ngx_http_v3_ack_insert_count(c, st->prefix.insert_count);
+ }
+
+ st->state = sw_start;
@@ -23190,7 +23114,7 @@ diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c
new file mode 100644
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_table.c
-@@ -0,0 +1,678 @@
+@@ -0,0 +1,719 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
@@ -23425,11 +23349,9 @@ new file mode 100644
+ dt->elts[dt->nelts++] = field;
+ dt->size += size;
+
-+ /* TODO increment can be sent less often */
++ dt->insert_count++;
+
-+ if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) {
-+ return NGX_ERROR;
-+ }
++ ngx_post_event(&dt->send_insert_count, &ngx_posted_events);
+
+ if (ngx_http_v3_new_entry(c) != NGX_OK) {
+ return NGX_ERROR;
@@ -23439,6 +23361,34 @@ new file mode 100644
+}
+
+
++void
++ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++ ngx_http_v3_session_t *h3c;
++ ngx_http_v3_dynamic_table_t *dt;
++
++ c = ev->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 inc insert count handler");
++
++ h3c = ngx_http_v3_get_session(c);
++ dt = &h3c->table;
++
++ if (dt->insert_count > dt->ack_insert_count) {
++ if (ngx_http_v3_send_inc_insert_count(c,
++ dt->insert_count - dt->ack_insert_count)
++ != NGX_OK)
++ {
++ return;
++ }
++
++ dt->ack_insert_count = dt->insert_count;
++ }
++}
++
++
+ngx_int_t
+ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
@@ -23800,6 +23750,21 @@ new file mode 100644
+}
+
+
++void
++ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count)
++{
++ ngx_http_v3_session_t *h3c;
++ ngx_http_v3_dynamic_table_t *dt;
++
++ h3c = ngx_http_v3_get_session(c);
++ dt = &h3c->table;
++
++ if (dt->ack_insert_count < insert_count) {
++ dt->ack_insert_count = insert_count;
++ }
++}
++
++
+static void
+ngx_http_v3_unblock(void *data)
+{
@@ -23873,7 +23838,7 @@ diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h
new file mode 100644
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_table.h
-@@ -0,0 +1,53 @@
+@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
@@ -23902,9 +23867,13 @@ new file mode 100644
+ ngx_uint_t base;
+ size_t size;
+ size_t capacity;
++ uint64_t insert_count;
++ uint64_t ack_insert_count;
++ ngx_event_t send_insert_count;
+} ngx_http_v3_dynamic_table_t;
+
+
++void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev);
+void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c);
+ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value);
@@ -23922,6 +23891,7 @@ new file mode 100644
+ ngx_uint_t *insert_count);
+ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
+ ngx_uint_t insert_count);
++void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count);
+ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
+ uint64_t value);
+
@@ -23931,7 +23901,7 @@ diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c
new file mode 100644
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_uni.c
-@@ -0,0 +1,733 @@
+@@ -0,0 +1,762 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
@@ -23960,7 +23930,8 @@ new file mode 100644
+
+static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
+static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
-+static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
++static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev);
++static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev);
+static void ngx_http_v3_push_cleanup(void *data);
+static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
+ ngx_uint_t type);
@@ -24002,7 +23973,7 @@ new file mode 100644
+ c->data = us;
+
+ c->read->handler = ngx_http_v3_uni_read_handler;
-+ c->write->handler = ngx_http_v3_dummy_write_handler;
++ c->write->handler = ngx_http_v3_uni_dummy_write_handler;
+
+ ngx_http_v3_uni_read_handler(c->read);
+}
@@ -24186,7 +24157,33 @@ new file mode 100644
+
+
+static void
-+ngx_http_v3_dummy_write_handler(ngx_event_t *wev)
++ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev)
++{
++ u_char ch;
++ ngx_connection_t *c;
++
++ c = rev->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler");
++
++ if (rev->ready) {
++ if (c->recv(c, &ch, 1) != 0) {
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL);
++ ngx_http_v3_close_uni_stream(c);
++ return;
++ }
++ }
++
++ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
++ NULL);
++ ngx_http_v3_close_uni_stream(c);
++ }
++}
++
++
++static void
++ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+
@@ -24327,8 +24324,8 @@ new file mode 100644
+
+ sc->data = us;
+
-+ sc->read->handler = ngx_http_v3_uni_read_handler;
-+ sc->write->handler = ngx_http_v3_dummy_write_handler;
++ sc->read->handler = ngx_http_v3_uni_dummy_read_handler;
++ sc->write->handler = ngx_http_v3_uni_dummy_write_handler;
+
+ if (index >= 0) {
+ h3c->known_streams[index] = sc;
@@ -24343,6 +24340,8 @@ new file mode 100644
+ goto failed;
+ }
+
++ ngx_post_event(sc->read, &ngx_posted_events);
++
+ return sc;
+
+failed:
@@ -24779,7 +24778,7 @@ diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_c
- u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
-#endif
-
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ struct cmsghdr *cmsg;
+ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
#endif
@@ -24790,7 +24789,7 @@ diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_c
msg.msg_iovlen = vec->count;
-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
+
+ msg.msg_control = msg_control;
@@ -24807,7 +24806,7 @@ diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_c
+}
+
+
-+#if defined(NGX_HAVE_ADDRINFO_CMSG)
++#if (NGX_HAVE_ADDRINFO_CMSG)
- if (c->listening && c->listening->wildcard && c->local_sockaddr) {
+size_t