changeset 8481:0d2b2664b41c quic

QUIC: added "quic" listen parameter. The parameter allows processing HTTP/0.9-2 over QUIC. Also, introduced ngx_http_quic_module and moved QUIC settings there
author Roman Arutyunyan <arut@nginx.com>
date Tue, 21 Jul 2020 23:09:22 +0300
parents f537f99b86ee
children 893b3313f53c
files auto/lib/openssl/conf auto/modules auto/options src/core/ngx_connection.c src/core/ngx_connection.h src/event/ngx_event.c src/event/ngx_event_quic.c src/event/ngx_event_quic.h src/http/modules/ngx_http_quic_module.c src/http/modules/ngx_http_quic_module.h src/http/modules/ngx_http_ssl_module.c src/http/ngx_http.c src/http/ngx_http.h src/http/ngx_http_core_module.c src/http/ngx_http_core_module.h src/http/ngx_http_request.c src/http/ngx_http_request.h src/http/v3/ngx_http_v3.h src/http/v3/ngx_http_v3_module.c src/http/v3/ngx_http_v3_streams.c
diffstat 20 files changed, 599 insertions(+), 399 deletions(-) [+]
line wrap: on
line diff
--- a/auto/lib/openssl/conf
+++ b/auto/lib/openssl/conf
@@ -166,6 +166,7 @@ END
         exit 1
     fi
 
+    have=NGX_QUIC . auto/have
 fi
 
 
--- a/auto/modules
+++ b/auto/modules
@@ -404,12 +404,9 @@ if [ $HTTP = YES ]; then
     ngx_module_type=HTTP
 
     if [ $HTTP_V3 = YES ]; then
-        USE_OPENSSL=YES
-        USE_OPENSSL_QUIC=YES
         have=NGX_HTTP_V3 . auto/have
         have=NGX_HTTP_HEADERS . auto/have
-
-        HTTP_SSL=YES
+        HTTP_QUIC=YES
 
         # XXX for Huffman
         HTTP_V2=YES
@@ -696,6 +693,21 @@ if [ $HTTP = YES ]; then
         . auto/module
     fi
 
+    if [ $HTTP_QUIC = YES ]; then
+        USE_OPENSSL_QUIC=YES
+        have=NGX_HTTP_QUIC . auto/have
+        HTTP_SSL=YES
+
+        ngx_module_name=ngx_http_quic_module
+        ngx_module_incs=
+        ngx_module_deps=src/http/modules/ngx_http_quic_module.h
+        ngx_module_srcs=src/http/modules/ngx_http_quic_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_QUIC
+
+        . auto/module
+    fi
+
     if [ $HTTP_SSL = YES ]; then
         USE_OPENSSL=YES
         have=NGX_HTTP_SSL . auto/have
--- a/auto/options
+++ b/auto/options
@@ -58,6 +58,7 @@ HTTP_CACHE=YES
 HTTP_CHARSET=YES
 HTTP_GZIP=YES
 HTTP_SSL=NO
+HTTP_QUIC=NO
 HTTP_V2=NO
 HTTP_V3=NO
 HTTP_SSI=YES
@@ -225,6 +226,7 @@ do
         --http-scgi-temp-path=*)         NGX_HTTP_SCGI_TEMP_PATH="$value" ;;
 
         --with-http_ssl_module)          HTTP_SSL=YES               ;;
+        --with-http_quic_module)         HTTP_QUIC=YES              ;;
         --with-http_v2_module)           HTTP_V2=YES                ;;
         --with-http_v3_module)           HTTP_V3=YES                ;;
         --with-http_realip_module)       HTTP_REALIP=YES            ;;
@@ -441,6 +443,7 @@ cat << END
   --with-file-aio                    enable file AIO support
 
   --with-http_ssl_module             enable ngx_http_ssl_module
+  --with-http_quic_module            enable ngx_http_quic_module
   --with-http_v2_module              enable ngx_http_v2_module
   --with-http_v3_module              enable ngx_http_v3_module
   --with-http_realip_module          enable ngx_http_realip_module
--- a/src/core/ngx_connection.c
+++ b/src/core/ngx_connection.c
@@ -1034,9 +1034,11 @@ ngx_close_listening_sockets(ngx_cycle_t 
     ls = cycle->listening.elts;
     for (i = 0; i < cycle->listening.nelts; i++) {
 
+#if (NGX_QUIC)
         if (ls[i].quic) {
             continue;
         }
+#endif
 
         c = ls[i].connection;
 
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -150,9 +150,12 @@ struct ngx_connection_s {
 
     ngx_proxy_protocol_t   *proxy_protocol;
 
-#if (NGX_SSL || NGX_COMPAT)
+#if (NGX_QUIC || NGX_COMPAT)
     ngx_quic_connection_t  *quic;
     ngx_quic_stream_t      *qs;
+#endif
+
+#if (NGX_SSL || NGX_COMPAT)
     ngx_ssl_connection_t   *ssl;
 #endif
 
--- a/src/event/ngx_event.c
+++ b/src/event/ngx_event.c
@@ -268,6 +268,8 @@ ngx_process_events_and_timers(ngx_cycle_
 ngx_int_t
 ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
 {
+#if (NGX_QUIC)
+
     ngx_connection_t  *c;
 
     c = rev->data;
@@ -284,6 +286,8 @@ ngx_handle_read_event(ngx_event_t *rev, 
         return NGX_OK;
     }
 
+#endif
+
     if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
 
         /* kqueue, epoll */
@@ -362,6 +366,8 @@ ngx_handle_write_event(ngx_event_t *wev,
         }
     }
 
+#if (NGX_QUIC)
+
     if (c->qs) {
 
         if (!wev->active && !wev->ready) {
@@ -374,6 +380,8 @@ ngx_handle_write_event(ngx_event_t *wev,
         return NGX_OK;
     }
 
+#endif
+
     if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
 
         /* kqueue, epoll */
@@ -944,9 +952,11 @@ ngx_send_lowat(ngx_connection_t *c, size
 {
     int  sndlowat;
 
+#if (NGX_QUIC)
     if (c->qs) {
         return NGX_OK;
     }
+#endif
 
 #if (NGX_HAVE_LOWAT_EVENT)
 
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -93,6 +93,8 @@ struct ngx_quic_connection_s {
     ngx_quic_secrets_t                next_key;
     ngx_quic_frames_stream_t          crypto[NGX_QUIC_ENCRYPTION_LAST];
 
+    ngx_quic_conf_t                  *conf;
+
     ngx_ssl_t                        *ssl;
 
     ngx_event_t                       push;
@@ -160,7 +162,7 @@ static int ngx_quic_send_alert(ngx_ssl_c
 
 
 static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
-    ngx_quic_tp_t *tp, ngx_quic_header_t *pkt,
+    ngx_quic_conf_t *conf, ngx_quic_header_t *pkt,
     ngx_connection_handler_pt handler);
 static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid);
 static ngx_int_t ngx_quic_retry(ngx_connection_t *c);
@@ -585,7 +587,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_
 
 
 void
-ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
+ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf,
     ngx_connection_handler_pt handler)
 {
     ngx_buf_t          *b;
@@ -604,7 +606,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ss
     pkt.data = b->start;
     pkt.len = b->last - b->start;
 
-    if (ngx_quic_new_connection(c, ssl, tp, &pkt, handler) != NGX_OK) {
+    if (ngx_quic_new_connection(c, ssl, conf, &pkt, handler) != NGX_OK) {
         ngx_quic_close_connection(c, NGX_ERROR);
         return;
     }
@@ -619,8 +621,9 @@ ngx_quic_run(ngx_connection_t *c, ngx_ss
 
 
 static ngx_int_t
-ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
-    ngx_quic_header_t *pkt, ngx_connection_handler_pt handler)
+ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
+    ngx_quic_conf_t *conf, ngx_quic_header_t *pkt,
+    ngx_connection_handler_pt handler)
 {
     ngx_int_t               rc;
     ngx_uint_t              i;
@@ -703,7 +706,8 @@ ngx_quic_new_connection(ngx_connection_t
 
     c->quic = qc;
     qc->ssl = ssl;
-    qc->tp = *tp;
+    qc->conf = conf;
+    qc->tp = conf->tp;
     qc->streams.handler = handler;
 
     ctp = &qc->ctp;
@@ -767,7 +771,7 @@ ngx_quic_new_connection(ngx_connection_t
         /* NGX_OK */
         qc->validated = 1;
 
-    } else if (tp->retry) {
+    } else if (conf->retry) {
         return ngx_quic_retry(c);
     }
 
@@ -949,7 +953,7 @@ ngx_quic_new_token(ngx_connection_t *c, 
         return NGX_ERROR;
     }
 
-    key = c->quic->tp.token_key;
+    key = c->quic->conf->token_key;
     iv = token->data;
 
     if (RAND_bytes(iv, iv_len) <= 0
@@ -1023,7 +1027,7 @@ ngx_quic_validate_token(ngx_connection_t
     /* NEW_TOKEN in a previous connection */
 
     cipher = EVP_aes_256_cbc();
-    key = c->quic->tp.token_key;
+    key = c->quic->conf->token_key;
     iv = pkt->token.data;
     iv_len = EVP_CIPHER_iv_length(cipher);
 
@@ -2237,7 +2241,7 @@ ngx_quic_send_new_token(ngx_connection_t
     ngx_str_t          token;
     ngx_quic_frame_t  *frame;
 
-    if (!c->quic->tp.retry) {
+    if (!c->quic->conf->retry) {
         return NGX_OK;
     }
 
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -78,9 +78,6 @@ typedef struct {
     ngx_str_t                  initial_scid;
     ngx_str_t                  retry_scid;
 
-    ngx_flag_t                 retry;
-    u_char                     token_key[32]; /* AES 256 */
-
     /* TODO */
     u_char                     stateless_reset_token[16];
     void                      *preferred_address;
@@ -88,6 +85,13 @@ typedef struct {
 
 
 typedef struct {
+    ngx_quic_tp_t              tp;
+    ngx_flag_t                 retry;
+    u_char                     token_key[32]; /* AES 256 */
+} ngx_quic_conf_t;
+
+
+typedef struct {
     uint64_t                   sent;
     uint64_t                   received;
     ngx_queue_t                frames;   /* reorder queue */
@@ -107,7 +111,7 @@ struct ngx_quic_stream_s {
 };
 
 
-void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
+void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf,
     ngx_connection_handler_pt handler);
 ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c);
 void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_quic_module.c
@@ -0,0 +1,344 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_quic_add_variables(ngx_conf_t *cf);
+static void *ngx_http_quic_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post,
+    void *data);
+static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post,
+    void *data);
+
+
+static ngx_conf_post_t  ngx_http_quic_max_ack_delay_post =
+    { ngx_http_quic_max_ack_delay };
+static ngx_conf_post_t  ngx_http_quic_max_udp_payload_size_post =
+    { ngx_http_quic_max_udp_payload_size };
+static ngx_conf_num_bounds_t  ngx_http_quic_ack_delay_exponent_bounds =
+    { ngx_conf_check_num_bounds, 0, 20 };
+static ngx_conf_num_bounds_t  ngx_http_quic_active_connection_id_limit_bounds =
+    { ngx_conf_check_num_bounds, 2, -1 };
+
+
+static ngx_command_t  ngx_http_quic_commands[] = {
+
+    { ngx_string("quic_max_idle_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.max_idle_timeout),
+      NULL },
+
+    { ngx_string("quic_max_ack_delay"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.max_ack_delay),
+      &ngx_http_quic_max_ack_delay_post },
+
+    { ngx_string("quic_max_udp_payload_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.max_udp_payload_size),
+      &ngx_http_quic_max_udp_payload_size_post },
+
+    { ngx_string("quic_initial_max_data"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.initial_max_data),
+      NULL },
+
+    { ngx_string("quic_initial_max_stream_data_bidi_local"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local),
+      NULL },
+
+    { ngx_string("quic_initial_max_stream_data_bidi_remote"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote),
+      NULL },
+
+    { ngx_string("quic_initial_max_stream_data_uni"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni),
+      NULL },
+
+    { ngx_string("quic_initial_max_streams_bidi"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi),
+      NULL },
+
+    { ngx_string("quic_initial_max_streams_uni"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni),
+      NULL },
+
+    { ngx_string("quic_ack_delay_exponent"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.ack_delay_exponent),
+      &ngx_http_quic_ack_delay_exponent_bounds },
+
+    { ngx_string("quic_active_migration"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.disable_active_migration),
+      NULL },
+
+    { ngx_string("quic_active_connection_id_limit"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, tp.active_connection_id_limit),
+      &ngx_http_quic_active_connection_id_limit_bounds },
+
+    { ngx_string("quic_retry"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_quic_conf_t, retry),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_quic_module_ctx = {
+    ngx_http_quic_add_variables,           /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_http_quic_create_srv_conf,         /* create server configuration */
+    ngx_http_quic_merge_srv_conf,          /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_quic_module = {
+    NGX_MODULE_V1,
+    &ngx_http_quic_module_ctx,             /* module context */
+    ngx_http_quic_commands,                /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t  ngx_http_quic_vars[] = {
+
+    { ngx_string("quic"), NULL, ngx_http_variable_quic, 0, 0, 0 },
+
+      ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_variable_quic(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->connection->qs) {
+
+        v->len = 4;
+        v->valid = 1;
+        v->no_cacheable = 1;
+        v->not_found = 0;
+        v->data = (u_char *) "quic";
+        return NGX_OK;
+    }
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_quic_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_quic_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_quic_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_quic_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->tp.original_dcid = { 0, NULL };
+     *     conf->tp.initial_scid = { 0, NULL };
+     *     conf->tp.retry_scid = { 0, NULL };
+     *     conf->tp.stateless_reset_token = { 0 }
+     *     conf->tp.preferred_address = NULL
+     */
+
+    conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;
+    conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC;
+    conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE;
+    conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE;
+    conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE;
+    conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE;
+    conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE;
+    conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
+    conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
+    conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT;
+    conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT;
+    conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+
+    conf->retry = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *
+ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_quic_conf_t *prev = parent;
+    ngx_quic_conf_t *conf = child;
+
+    ngx_conf_merge_msec_value(conf->tp.max_idle_timeout,
+                              prev->tp.max_idle_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->tp.max_ack_delay,
+                              prev->tp.max_ack_delay,
+                              NGX_QUIC_DEFAULT_MAX_ACK_DELAY);
+
+    ngx_conf_merge_size_value(conf->tp.max_udp_payload_size,
+                              prev->tp.max_udp_payload_size,
+                              NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
+
+    ngx_conf_merge_size_value(conf->tp.initial_max_data,
+                              prev->tp.initial_max_data,
+                              16 * NGX_QUIC_STREAM_BUFSIZE);
+
+    ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local,
+                              prev->tp.initial_max_stream_data_bidi_local,
+                              NGX_QUIC_STREAM_BUFSIZE);
+
+    ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote,
+                              prev->tp.initial_max_stream_data_bidi_remote,
+                              NGX_QUIC_STREAM_BUFSIZE);
+
+    ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni,
+                              prev->tp.initial_max_stream_data_uni,
+                              NGX_QUIC_STREAM_BUFSIZE);
+
+    ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi,
+                              prev->tp.initial_max_streams_bidi, 16);
+
+    ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni,
+                              prev->tp.initial_max_streams_uni, 16);
+
+    ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent,
+                              prev->tp.ack_delay_exponent,
+                              NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
+
+    ngx_conf_merge_uint_value(conf->tp.disable_active_migration,
+                              prev->tp.disable_active_migration, 1);
+
+    ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit,
+                              prev->tp.active_connection_id_limit, 2);
+
+    ngx_conf_merge_value(conf->retry, prev->retry, 0);
+
+    if (conf->retry) {
+        if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_msec_t *sp = data;
+
+    if (*sp > 16384) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"quic_max_ack_delay\" must be less than 16384");
+
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data)
+{
+    size_t *sp = data;
+
+    if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
+        || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"quic_max_udp_payload_size\" must be between "
+                           "%d and %d",
+                           NGX_QUIC_MIN_INITIAL_SIZE,
+                           NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
+
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_quic_module.h
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#ifndef _NGX_HTTP_QUIC_H_INCLUDED_
+#define _NGX_HTTP_QUIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_QUIC_ALPN(s)         NGX_HTTP_QUIC_ALPN_DRAFT(s)
+#define NGX_HTTP_QUIC_ALPN_DRAFT(s)   "\x05hq-" #s
+#define NGX_HTTP_QUIC_ALPN_ADVERTISE  NGX_HTTP_QUIC_ALPN(NGX_QUIC_DRAFT_VERSION)
+
+
+extern ngx_module_t  ngx_http_quic_module;
+
+
+#endif /* _NGX_HTTP_QUIC_H_INCLUDED_ */
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -402,7 +402,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t 
 #if (NGX_DEBUG)
     unsigned int            i;
 #endif
-#if (NGX_HTTP_V2 || NGX_HTTP_V3)
+#if (NGX_HTTP_V2 || NGX_HTTP_QUIC)
     ngx_http_connection_t  *hc;
 #endif
 #if (NGX_HTTP_V2 || NGX_DEBUG)
@@ -419,7 +419,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t 
     }
 #endif
 
-#if (NGX_HTTP_V2 || NGX_HTTP_V3)
+#if (NGX_HTTP_V2 || NGX_HTTP_QUIC)
     hc = c->data;
 #endif
 
@@ -437,6 +437,12 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t 
         srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1;
     } else
 #endif
+#if (NGX_HTTP_QUIC)
+    if (hc->addr_conf->quic) {
+        srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_ADVERTISE;
+        srvlen = sizeof(NGX_HTTP_QUIC_ALPN_ADVERTISE) - 1;
+    } else
+#endif
     {
         srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
         srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -1247,6 +1253,7 @@ static ngx_int_t
 ngx_http_ssl_init(ngx_conf_t *cf)
 {
     ngx_uint_t                   a, p, s;
+    const char                  *name;
     ngx_http_conf_addr_t        *addr;
     ngx_http_conf_port_t        *port;
     ngx_http_ssl_srv_conf_t     *sscf;
@@ -1296,26 +1303,36 @@ ngx_http_ssl_init(ngx_conf_t *cf)
         addr = port[p].addrs.elts;
         for (a = 0; a < port[p].addrs.nelts; a++) {
 
-            if (!addr[a].opt.ssl && !addr[a].opt.http3) {
+            if (!addr[a].opt.ssl && !addr[a].opt.quic) {
                 continue;
             }
 
+            if (addr[a].opt.http3) {
+                name = "http3";
+
+            } else if (addr[a].opt.quic) {
+                name = "quic";
+
+            } else {
+                name = "ssl";
+            }
+
             cscf = addr[a].default_server;
             sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
 
             if (sscf->certificates == NULL) {
                 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                               "no \"ssl_certificate\" is defined for "
-                              "the \"listen ... ssl\" directive in %s:%ui",
-                              cscf->file_name, cscf->line);
+                              "the \"listen ... %s\" directive in %s:%ui",
+                              name, cscf->file_name, cscf->line);
                 return NGX_ERROR;
             }
 
-            if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
+            if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
                 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                               "\"ssl_protocols\" did not enable TLSv1.3 for "
-                              "the \"listen ... http3\" directive in %s:%ui",
-                              cscf->file_name, cscf->line);
+                              "the \"listen ... %s\" directives in %s:%ui",
+                              name, cscf->file_name, cscf->line);
                 return NGX_ERROR;
             }
         }
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1200,10 +1200,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_SSL)
     ngx_uint_t             ssl;
 #endif
+#if (NGX_HTTP_QUIC)
+    ngx_uint_t             quic;
+#endif
 #if (NGX_HTTP_V2)
     ngx_uint_t             http2;
 #endif
-#if (NGX_HTTP_SSL)
+#if (NGX_HTTP_V3)
     ngx_uint_t             http3;
 #endif
 
@@ -1238,10 +1241,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_SSL)
         ssl = lsopt->ssl || addr[i].opt.ssl;
 #endif
+#if (NGX_HTTP_QUIC)
+        quic = lsopt->quic || addr[i].opt.quic;
+#endif
 #if (NGX_HTTP_V2)
         http2 = lsopt->http2 || addr[i].opt.http2;
 #endif
-#if (NGX_HTTP_SSL)
+#if (NGX_HTTP_V3)
         http3 = lsopt->http3 || addr[i].opt.http3;
 #endif
 
@@ -1277,10 +1283,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_SSL)
         addr[i].opt.ssl = ssl;
 #endif
+#if (NGX_HTTP_QUIC)
+        addr[i].opt.quic = quic;
+#endif
 #if (NGX_HTTP_V2)
         addr[i].opt.http2 = http2;
 #endif
-#if (NGX_HTTP_SSL)
+#if (NGX_HTTP_V3)
         addr[i].opt.http3 = http3;
 #endif
 
@@ -1326,12 +1335,12 @@ ngx_http_add_address(ngx_conf_t *cf, ngx
 
 #endif
 
-#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC)
-
-    if (lsopt->http3) {
+#if (NGX_HTTP_QUIC && !defined NGX_OPENSSL_QUIC)
+
+    if (lsopt->quic) {
         ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                            "nginx was built with OpenSSL that lacks QUIC "
-                           "support, HTTP/3 is not enabled for %V",
+                           "support, QUIC is not enabled for %V",
                            &lsopt->addr_text);
     }
 
@@ -1797,8 +1806,8 @@ ngx_http_add_listening(ngx_conf_t *cf, n
 
     ls->wildcard = addr->opt.wildcard;
 
-#if (NGX_HTTP_SSL)
-    ls->quic = addr->opt.http3;
+#if (NGX_HTTP_QUIC)
+    ls->quic = addr->opt.quic;
 #endif
 
     return ls;
@@ -1830,10 +1839,13 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h
 #if (NGX_HTTP_SSL)
         addrs[i].conf.ssl = addr[i].opt.ssl;
 #endif
+#if (NGX_HTTP_QUIC)
+        addrs[i].conf.quic = addr[i].opt.quic;
+#endif
 #if (NGX_HTTP_V2)
         addrs[i].conf.http2 = addr[i].opt.http2;
 #endif
-#if (NGX_HTTP_SSL)
+#if (NGX_HTTP_V3)
         addrs[i].conf.http3 = addr[i].opt.http3;
 #endif
         addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
@@ -1898,10 +1910,13 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_
 #if (NGX_HTTP_SSL)
         addrs6[i].conf.ssl = addr[i].opt.ssl;
 #endif
+#if (NGX_HTTP_QUIC)
+        addrs6[i].conf.quic = addr[i].opt.quic;
+#endif
 #if (NGX_HTTP_V2)
         addrs6[i].conf.http2 = addr[i].opt.http2;
 #endif
-#if (NGX_HTTP_SSL)
+#if (NGX_HTTP_V3)
         addrs6[i].conf.http3 = addr[i].opt.http3;
 #endif
         addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -50,6 +50,9 @@ typedef u_char *(*ngx_http_log_handler_p
 #if (NGX_HTTP_SSL)
 #include <ngx_http_ssl_module.h>
 #endif
+#if (NGX_HTTP_QUIC)
+#include <ngx_http_quic_module.h>
+#endif
 
 
 struct ngx_http_log_ctx_s {
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -4079,8 +4079,22 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
 #endif
         }
 
+        if (ngx_strcmp(value[n].data, "quic") == 0) {
+#if (NGX_HTTP_QUIC)
+            lsopt.quic = 1;
+            lsopt.type = SOCK_DGRAM;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"quic\" parameter requires "
+                               "ngx_http_quic_module");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
         if (ngx_strcmp(value[n].data, "http3") == 0) {
 #if (NGX_HTTP_V3)
+            lsopt.quic = 1;
             lsopt.http3 = 1;
             lsopt.type = SOCK_DGRAM;
             continue;
@@ -4201,6 +4215,22 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
         return NGX_CONF_ERROR;
     }
 
+#if (NGX_HTTP_SSL)
+
+#if (NGX_HTTP_V3)
+    if (lsopt.ssl && lsopt.http3) {
+        return "\"ssl\" parameter is incompatible with \"http3\"";
+    }
+#endif
+
+#if (NGX_HTTP_QUIC)
+    if (lsopt.ssl && lsopt.quic) {
+        return "\"ssl\" parameter is incompatible with \"quic\"";
+    }
+#endif
+
+#endif
+
     for (n = 0; n < u.naddrs; n++) {
         lsopt.sockaddr = u.addrs[n].sockaddr;
         lsopt.socklen = u.addrs[n].socklen;
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -74,6 +74,7 @@ typedef struct {
     unsigned                   bind:1;
     unsigned                   wildcard:1;
     unsigned                   ssl:1;
+    unsigned                   quic:1;
     unsigned                   http2:1;
     unsigned                   http3:1;
 #if (NGX_HAVE_INET6)
@@ -238,6 +239,7 @@ struct ngx_http_addr_conf_s {
     ngx_http_virtual_names_t  *virtual_names;
 
     unsigned                   ssl:1;
+    unsigned                   quic:1;
     unsigned                   http2:1;
     unsigned                   http3:1;
     unsigned                   proxy_protocol:1;
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -64,9 +64,6 @@ static void ngx_http_ssl_handshake(ngx_e
 static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
 #endif
 
-#if (NGX_HTTP_V3)
-static void ngx_http_quic_stream_handler(ngx_connection_t *c);
-#endif
 
 static char *ngx_http_client_errors[] = {
 
@@ -221,26 +218,7 @@ ngx_http_init_connection(ngx_connection_
     ngx_http_in6_addr_t    *addr6;
 #endif
 
-#if (NGX_HTTP_V3)
-    if (c->type == SOCK_DGRAM) {
-        ngx_http_v3_connection_t  *h3c;
-
-        h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
-        if (h3c == NULL) {
-            ngx_http_close_connection(c);
-            return;
-        }
-
-        ngx_queue_init(&h3c->blocked);
-
-        hc = &h3c->hc;
-        hc->quic = 1;
-        hc->ssl = 1;
-
-    } else
-#endif
-        hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
-
+    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
     if (hc == NULL) {
         ngx_http_close_connection(c);
         return;
@@ -325,6 +303,46 @@ ngx_http_init_connection(ngx_connection_
     /* the default server configuration for the address:port */
     hc->conf_ctx = hc->addr_conf->default_server->ctx;
 
+#if (NGX_HTTP_QUIC)
+
+    if (hc->addr_conf->quic) {
+        ngx_quic_conf_t          *qcf;
+        ngx_http_ssl_srv_conf_t  *sscf;
+
+#if (NGX_HTTP_V3)
+
+        if (hc->addr_conf->http3) {
+            ngx_int_t  rc;
+
+            rc = ngx_http_v3_init_connection(c);
+
+            if (rc == NGX_ERROR) {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            if (rc == NGX_DONE) {
+                return;
+            }
+        }
+
+#endif
+
+        if (c->qs == NULL) {
+            c->log->connection = c->number;
+
+            qcf = ngx_http_get_module_srv_conf(hc->conf_ctx,
+                                               ngx_http_quic_module);
+            sscf = ngx_http_get_module_srv_conf(hc->conf_ctx,
+                                                ngx_http_ssl_module);
+
+            ngx_quic_run(c, &sscf->ssl, qcf, ngx_http_init_connection);
+            return;
+        }
+    }
+
+#endif
+
     ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
     if (ctx == NULL) {
         ngx_http_close_connection(c);
@@ -346,23 +364,6 @@ ngx_http_init_connection(ngx_connection_
     rev->handler = ngx_http_wait_request_handler;
     c->write->handler = ngx_http_empty_handler;
 
-    if (c->shared) {
-        rev->ready = 1;
-    }
-
-#if (NGX_HTTP_V3)
-    if (hc->quic) {
-        ngx_http_v3_srv_conf_t   *v3cf;
-        ngx_http_ssl_srv_conf_t  *sscf;
-
-        v3cf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
-        sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
-
-        ngx_quic_run(c, &sscf->ssl, &v3cf->quic, ngx_http_quic_stream_handler);
-        return;
-    }
-#endif
-
 #if (NGX_HTTP_V2)
     if (hc->addr_conf->http2) {
         rev->handler = ngx_http_v2_init;
@@ -410,72 +411,6 @@ ngx_http_init_connection(ngx_connection_
 }
 
 
-#if (NGX_HTTP_V3)
-
-static void
-ngx_http_quic_stream_handler(ngx_connection_t *c)
-{
-    ngx_event_t               *rev;
-    ngx_http_log_ctx_t        *ctx;
-    ngx_http_connection_t     *hc;
-    ngx_http_v3_connection_t  *h3c;
-
-    h3c = c->qs->parent->data;
-
-    if (!h3c->settings_sent) {
-        h3c->settings_sent = 1;
-
-        if (ngx_http_v3_send_settings(c) != NGX_OK) {
-            ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
-                                            "could not send settings");
-            ngx_http_close_connection(c);
-            return;
-        }
-    }
-
-    if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
-        ngx_http_v3_handle_client_uni_stream(c);
-        return;
-    }
-
-    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
-    if (hc == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t));
-    c->data = hc;
-
-    ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
-    if (ctx == NULL) {
-        ngx_http_close_connection(c);
-        return;
-    }
-
-    ctx->connection = c;
-    ctx->request = NULL;
-    ctx->current_request = NULL;
-
-    c->log->handler = ngx_http_log_error;
-    c->log->data = ctx;
-    c->log->action = "waiting for request";
-
-    c->log_error = NGX_ERROR_INFO;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 new stream id:0x%uXL", c->qs->id);
-
-    rev = c->read;
-    rev->handler = ngx_http_wait_request_handler;
-    c->write->handler = ngx_http_empty_handler;
-
-    rev->handler(rev);
-}
-
-#endif
-
-
 static void
 ngx_http_wait_request_handler(ngx_event_t *rev)
 {
@@ -725,7 +660,7 @@ ngx_http_alloc_request(ngx_connection_t 
     r->http_version = NGX_HTTP_VERSION_10;
 
 #if (NGX_HTTP_V3)
-    if (hc->quic) {
+    if (hc->addr_conf->http3) {
         r->http_version = NGX_HTTP_VERSION_30;
     }
 #endif
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -324,7 +324,6 @@ typedef struct {
     ngx_chain_t                      *free;
 
     unsigned                          ssl:1;
-    unsigned                          quic:1;
     unsigned                          proxy_protocol:1;
 } ngx_http_connection_t;
 
--- a/src/http/v3/ngx_http_v3.h
+++ b/src/http/v3/ngx_http_v3.h
@@ -118,6 +118,8 @@ typedef struct {
 } ngx_http_v3_connection_t;
 
 
+ngx_int_t ngx_http_v3_init_connection(ngx_connection_t *c);
+
 ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b);
 ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b,
     ngx_uint_t allow_underscores);
@@ -130,9 +132,6 @@ uintptr_t ngx_http_v3_encode_varlen_int(
 uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,
     ngx_uint_t prefix);
 
-ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
-void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c);
-
 ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
     ngx_uint_t index, ngx_str_t *value);
 ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
--- a/src/http/v3/ngx_http_v3_module.c
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -10,114 +10,8 @@
 #include <ngx_http.h>
 
 
-static char *ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data);
-static char *ngx_http_v3_max_udp_payload_size(ngx_conf_t *cf, void *post,
-    void *data);
-
-
-static ngx_conf_post_t  ngx_http_v3_max_ack_delay_post =
-    { ngx_http_v3_max_ack_delay };
-static ngx_conf_post_t  ngx_http_v3_max_udp_payload_size_post =
-    { ngx_http_v3_max_udp_payload_size };
-static ngx_conf_num_bounds_t  ngx_http_v3_ack_delay_exponent_bounds =
-    { ngx_conf_check_num_bounds, 0, 20 };
-static ngx_conf_num_bounds_t  ngx_http_v3_active_connection_id_limit_bounds =
-    { ngx_conf_check_num_bounds, 2, -1 };
-
-
 static ngx_command_t  ngx_http_v3_commands[] = {
 
-    { ngx_string("quic_max_idle_timeout"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_msec_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.max_idle_timeout),
-      NULL },
-
-    { ngx_string("quic_max_ack_delay"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_msec_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.max_ack_delay),
-      &ngx_http_v3_max_ack_delay_post },
-
-    { ngx_string("quic_max_udp_payload_size"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_size_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.max_udp_payload_size),
-      &ngx_http_v3_max_udp_payload_size_post },
-
-    { ngx_string("quic_initial_max_data"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_size_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_data),
-      NULL },
-
-    { ngx_string("quic_initial_max_stream_data_bidi_local"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_size_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_local),
-      NULL },
-
-    { ngx_string("quic_initial_max_stream_data_bidi_remote"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_size_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_remote),
-      NULL },
-
-    { ngx_string("quic_initial_max_stream_data_uni"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_size_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_uni),
-      NULL },
-
-    { ngx_string("quic_initial_max_streams_bidi"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_bidi),
-      NULL },
-
-    { ngx_string("quic_initial_max_streams_uni"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_uni),
-      NULL },
-
-    { ngx_string("quic_ack_delay_exponent"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.ack_delay_exponent),
-      &ngx_http_v3_ack_delay_exponent_bounds },
-
-    { ngx_string("quic_active_migration"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.disable_active_migration),
-      NULL },
-
-    { ngx_string("quic_active_connection_id_limit"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_num_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
-      &ngx_http_v3_active_connection_id_limit_bounds },
-
-    { ngx_string("quic_retry"),
-      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
-      ngx_conf_set_flag_slot,
-      NGX_HTTP_SRV_CONF_OFFSET,
-      offsetof(ngx_http_v3_srv_conf_t, quic.retry),
-      NULL },
-
     { ngx_string("http3_max_field_size"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_size_slot,
@@ -143,8 +37,6 @@ static ngx_command_t  ngx_http_v3_comman
 };
 
 
-static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r,
-    ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf);
@@ -185,8 +77,6 @@ ngx_module_t  ngx_http_v3_module = {
 
 
 static ngx_http_variable_t  ngx_http_v3_vars[] = {
-    { ngx_string("quic"), NULL, ngx_http_variable_quic,
-      0, 0, 0 },
 
     { ngx_string("http3"), NULL, ngx_http_variable_http3,
       0, 0, 0 },
@@ -196,26 +86,6 @@ static ngx_http_variable_t  ngx_http_v3_
 
 
 static ngx_int_t
-ngx_http_variable_quic(ngx_http_request_t *r,
-    ngx_http_variable_value_t *v, uintptr_t data)
-{
-    if (r->connection->qs) {
-
-        v->len = 4;
-        v->valid = 1;
-        v->no_cacheable = 1;
-        v->not_found = 0;
-        v->data = (u_char *) "quic";
-        return NGX_OK;
-    }
-
-    v->not_found = 1;
-
-    return NGX_OK;
-}
-
-
-static ngx_int_t
 ngx_http_variable_http3(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
 {
@@ -264,31 +134,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *
         return NULL;
     }
 
-    /*
-     * set by ngx_pcalloc():
-     *  v3cf->quic.original_dcid = { 0, NULL };
-     *  v3cf->quic.initial_scid = { 0, NULL };
-     *  v3cf->quic.retry_scid = { 0, NULL };
-     *  v3cf->quic.stateless_reset_token = { 0 }
-     *  conf->quic.preferred_address = NULL
-     */
-
-    v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC;
-    v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC;
-
-    v3cf->quic.max_udp_payload_size = NGX_CONF_UNSET_SIZE;
-    v3cf->quic.initial_max_data = NGX_CONF_UNSET_SIZE;
-    v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE;
-    v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE;
-    v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE;
-    v3cf->quic.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
-    v3cf->quic.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
-    v3cf->quic.ack_delay_exponent = NGX_CONF_UNSET_UINT;
-    v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT;
-    v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
-
-    v3cf->quic.retry = NGX_CONF_UNSET;
-
     v3cf->max_field_size = NGX_CONF_UNSET_SIZE;
     v3cf->max_table_capacity = NGX_CONF_UNSET_SIZE;
     v3cf->max_blocked_streams = NGX_CONF_UNSET_UINT;
@@ -303,57 +148,6 @@ 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_conf_merge_msec_value(conf->quic.max_idle_timeout,
-                              prev->quic.max_idle_timeout, 60000);
-
-    ngx_conf_merge_msec_value(conf->quic.max_ack_delay,
-                              prev->quic.max_ack_delay,
-                              NGX_QUIC_DEFAULT_MAX_ACK_DELAY);
-
-    ngx_conf_merge_size_value(conf->quic.max_udp_payload_size,
-                              prev->quic.max_udp_payload_size,
-                              NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
-
-    ngx_conf_merge_size_value(conf->quic.initial_max_data,
-                              prev->quic.initial_max_data,
-                              16 * NGX_QUIC_STREAM_BUFSIZE);
-
-    ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_bidi_local,
-                              prev->quic.initial_max_stream_data_bidi_local,
-                              NGX_QUIC_STREAM_BUFSIZE);
-
-    ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_bidi_remote,
-                              prev->quic.initial_max_stream_data_bidi_remote,
-                              NGX_QUIC_STREAM_BUFSIZE);
-
-    ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_uni,
-                              prev->quic.initial_max_stream_data_uni,
-                              NGX_QUIC_STREAM_BUFSIZE);
-
-    ngx_conf_merge_uint_value(conf->quic.initial_max_streams_bidi,
-                              prev->quic.initial_max_streams_bidi, 16);
-
-    ngx_conf_merge_uint_value(conf->quic.initial_max_streams_uni,
-                              prev->quic.initial_max_streams_uni, 16);
-
-    ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent,
-                              prev->quic.ack_delay_exponent,
-                              NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
-
-    ngx_conf_merge_uint_value(conf->quic.disable_active_migration,
-                              prev->quic.disable_active_migration, 1);
-
-    ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
-                              prev->quic.active_connection_id_limit, 2);
-
-    ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0);
-
-    if (conf->quic.retry) {
-        if (RAND_bytes(conf->quic.token_key, sizeof(conf->quic.token_key)) <= 0) {
-            return NGX_CONF_ERROR;
-        }
-    }
-
     ngx_conf_merge_size_value(conf->max_field_size,
                               prev->max_field_size,
                               NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE);
@@ -368,40 +162,3 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *c
 
     return NGX_CONF_OK;
 }
-
-
-static char *
-ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data)
-{
-    ngx_msec_t *sp = data;
-
-    if (*sp > 16384) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "\"quic_max_ack_delay\" must be less than 16384");
-
-        return NGX_CONF_ERROR;
-    }
-
-    return NGX_CONF_OK;
-}
-
-
-static char *
-ngx_http_v3_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data)
-{
-    size_t *sp = data;
-
-    if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
-        || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
-    {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "\"quic_max_udp_payload_size\" must be between "
-                           "%d and %d",
-                           NGX_QUIC_MIN_INITIAL_SIZE,
-                           NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
-
-        return NGX_CONF_ERROR;
-    }
-
-    return NGX_CONF_OK;
-}
--- a/src/http/v3/ngx_http_v3_streams.c
+++ b/src/http/v3/ngx_http_v3_streams.c
@@ -27,22 +27,55 @@ static void ngx_http_v3_uni_read_handler
 static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
 static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
     ngx_uint_t type);
+static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
 
 
-void
-ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c)
+ngx_int_t
+ngx_http_v3_init_connection(ngx_connection_t *c)
 {
+    ngx_http_connection_t     *hc;
     ngx_http_v3_uni_stream_t  *us;
+    ngx_http_v3_connection_t  *h3c;
 
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 new uni stream id:0x%uxL", c->qs->id);
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init connection");
+
+    hc = c->data;
+
+    if (c->qs == NULL) {
+        h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
+        if (h3c == NULL) {
+            return NGX_ERROR;
+        }
+
+        h3c->hc = *hc;
+
+        ngx_queue_init(&h3c->blocked);
+
+        c->data = h3c;
+        return NGX_OK;
+    }
+
+    h3c = c->qs->parent->data;
+
+    if (!h3c->settings_sent) {
+        h3c->settings_sent = 1;
+
+        if (ngx_http_v3_send_settings(c) != NGX_OK) {
+            ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+                                            "could not send settings");
+            return NGX_ERROR;
+        }
+    }
+
+    if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) {
+        return NGX_OK;
+    }
 
     us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
     if (us == NULL) {
         ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
                                         NULL);
-        ngx_http_v3_close_uni_stream(c);
-        return;
+        return NGX_ERROR;
     }
 
     us->index = -1;
@@ -53,6 +86,8 @@ ngx_http_v3_handle_client_uni_stream(ngx
     c->write->handler = ngx_http_v3_dummy_write_handler;
 
     ngx_http_v3_read_uni_stream_type(c->read);
+
+    return NGX_DONE;
 }
 
 
@@ -366,7 +401,7 @@ failed:
 }
 
 
-ngx_int_t
+static ngx_int_t
 ngx_http_v3_send_settings(ngx_connection_t *c)
 {
     u_char                    *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];