changeset 8247:e9891e8ee975 quic

Configurable transport parameters. - integer parameters can be configured using the following directives: quic_max_idle_timeout quic_max_ack_delay quic_max_packet_size quic_initial_max_data quic_initial_max_stream_data_bidi_local quic_initial_max_stream_data_bidi_remote quic_initial_max_stream_data_uni quic_initial_max_streams_bidi quic_initial_max_streams_uni quic_ack_delay_exponent quic_active_migration quic_active_connection_id_limit - only following parameters are actually sent: active_connection_id_limit initial_max_streams_uni initial_max_streams_bidi initial_max_stream_data_bidi_local initial_max_stream_data_bidi_remote initial_max_stream_data_uni (other parameters are to be added into ngx_quic_create_transport_params() function as needed, should be easy now) - draft 24 and draft 27 are now supported (at compile-time using quic_version macro)
author Vladimir Homutov <vl@nginx.com>
date Fri, 20 Mar 2020 13:47:44 +0300
parents 0d9bc77ae30d
children abb7c1a4c9d5
files src/event/ngx_event_quic.c src/event/ngx_event_quic.h src/event/ngx_event_quic_transport.c src/event/ngx_event_quic_transport.h src/http/ngx_http_request.c src/http/v3/ngx_http_v3.h src/http/v3/ngx_http_v3_module.c
diffstat 7 files changed, 361 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -32,6 +32,8 @@ struct ngx_quic_connection_s {
     ngx_str_t                         dcid;
     ngx_str_t                         token;
 
+    ngx_quic_tp_t                     tp;
+
     /* current packet numbers  for each namespace */
     ngx_uint_t                        initial_pn;
     ngx_uint_t                        handshake_pn;
@@ -67,7 +69,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_header_t *pkt);
+    ngx_quic_tp_t *tp, ngx_quic_header_t *pkt);
 static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
 static void ngx_quic_handshake_handler(ngx_event_t *rev);
 static void ngx_quic_close_connection(ngx_connection_t *c);
@@ -283,8 +285,8 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_
 
 
 void
-ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout,
-    ngx_connection_handler_pt handler)
+ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
+    ngx_msec_t timeout, ngx_connection_handler_pt handler)
 {
     ngx_buf_t          *b;
     ngx_quic_header_t   pkt;
@@ -302,7 +304,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, &pkt) != NGX_OK) {
+    if (ngx_quic_new_connection(c, ssl, tp, &pkt) != NGX_OK) {
         ngx_quic_close_connection(c);
         return;
     }
@@ -320,7 +322,7 @@ 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_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
     ngx_quic_header_t *pkt)
 {
     ngx_quic_connection_t  *qc;
@@ -354,6 +356,7 @@ ngx_quic_new_connection(ngx_connection_t
 
     c->quic = qc;
     qc->ssl = ssl;
+    qc->tp = *tp;
 
     qc->dcid.len = pkt->dcid.len;
     qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len);
@@ -402,19 +405,11 @@ static ngx_int_t
 ngx_quic_init_connection(ngx_connection_t *c)
 {
     int                     n, sslerr;
+    u_char                 *p;
+    ssize_t                 len;
     ngx_ssl_conn_t         *ssl_conn;
     ngx_quic_connection_t  *qc;
 
-    static const uint8_t params[] =
-        "\x00\x29"                         /* parameters length: 41 bytes         */
-        "\x00\x0e\x00\x01\x05"             /* active connection id limit: 5       */
-        "\x00\x04\x00\x04\x80\x98\x96\x80" /* initial max data = 10000000         */
-        "\x00\x09\x00\x01\x03"             /* initial max streams uni: 3          */
-        "\x00\x08\x00\x01\x10"             /* initial max streams bidi: 16        */
-        "\x00\x05\x00\x02\x40\xff"         /* initial max stream bidi local: 255  */
-        "\x00\x06\x00\x02\x40\xff"         /* initial max stream bidi remote: 255 */
-        "\x00\x07\x00\x02\x40\xff";        /* initial max stream data uni: 255    */
-
     qc = c->quic;
 
     if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) {
@@ -429,7 +424,20 @@ ngx_quic_init_connection(ngx_connection_
         return NGX_ERROR;
     }
 
-    if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params) - 1) == 0) {
+    len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp);
+    /* always succeeds */
+
+    p = ngx_pnalloc(c->pool, len);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    len = ngx_quic_create_transport_params(p, p + len, &qc->tp);
+    if (len < 0) {
+        return NGX_ERROR;
+    }
+
+    if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "SSL_set_quic_transport_params() failed");
         return NGX_ERROR;
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -15,6 +15,29 @@
 //#define quic_version      0xff00001b  /* draft-27 (FFN 76) */
 
 
+typedef struct {
+    /* configurable */
+    ngx_msec_t          max_idle_timeout;
+    ngx_msec_t          max_ack_delay;
+
+    ngx_uint_t          max_packet_size;
+    ngx_uint_t          initial_max_data;
+    ngx_uint_t          initial_max_stream_data_bidi_local;
+    ngx_uint_t          initial_max_stream_data_bidi_remote;
+    ngx_uint_t          initial_max_stream_data_uni;
+    ngx_uint_t          initial_max_streams_bidi;
+    ngx_uint_t          initial_max_streams_uni;
+    ngx_uint_t          ack_delay_exponent;
+    ngx_uint_t          disable_active_migration;
+    ngx_uint_t          active_connection_id_limit;
+
+    /* TODO */
+    ngx_uint_t          original_connection_id;
+    u_char              stateless_reset_token[16];
+    void               *preferred_address;
+} ngx_quic_tp_t;
+
+
 struct ngx_quic_stream_s {
     uint64_t            id;
     ngx_uint_t          unidirectional:1;
@@ -25,8 +48,8 @@ struct ngx_quic_stream_s {
 
 void ngx_quic_init_ssl_methods(SSL_CTX* ctx);
 
-void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout,
-    ngx_connection_handler_pt handler);
+void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
+    ngx_msec_t timeout, ngx_connection_handler_pt handler);
 ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c);
 
 
--- a/src/event/ngx_event_quic_transport.c
+++ b/src/event/ngx_event_quic_transport.c
@@ -87,6 +87,7 @@ static char *ngx_quic_errors[] = {
     "INVALID_TOKEN",
     "",
     "CRYPTO_BUFFER_EXCEEDED",
+    "",
     "CRYPTO_ERROR",
 };
 
@@ -639,11 +640,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *
             return NGX_ERROR;
         }
 
-        if (f->u.close.error_code > NGX_QUIC_ERR_LAST) {
+        if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) {
             ngx_log_error(NGX_LOG_ERR, pkt->log, 0,
                           "unkown error code: %ui, truncated",
                           f->u.close.error_code);
-            f->u.close.error_code = NGX_QUIC_ERR_LAST;
+            f->u.close.error_code = NGX_QUIC_ERR_LAST - 1;
         }
 
         ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
@@ -963,6 +964,105 @@ ngx_quic_create_max_streams(u_char *p, n
 }
 
 
+ssize_t
+ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp)
+{
+    u_char  *p;
+    size_t   len;
+
+#if (quic_version < 0xff00001b)
+
+/* older drafts with static transport parameters encoding */
+
+#define ngx_quic_tp_len(id, value)                                            \
+    4 + ngx_quic_varint_len(value)
+
+#define ngx_quic_tp_vint(id, value)                                           \
+    do {                                                                      \
+        p = ngx_quic_write_uint16(p, id);                                     \
+        p = ngx_quic_write_uint16(p, ngx_quic_varint_len(value));             \
+        ngx_quic_build_int(&p, value);                                        \
+    } while (0)
+
+#else
+
+/* recent drafts with variable integer transport parameters encoding */
+
+#define ngx_quic_tp_len(id, value)                                            \
+    ngx_quic_varint_len(id)                                                   \
+    + ngx_quic_varint_len(value)                                              \
+    + ngx_quic_varint_len(ngx_quic_varint_len(value))
+
+#define ngx_quic_tp_vint(id, value)                                           \
+    do {                                                                      \
+        ngx_quic_build_int(&p, id);                                           \
+        ngx_quic_build_int(&p, ngx_quic_varint_len(value));                   \
+        ngx_quic_build_int(&p, value);                                        \
+    } while (0)
+
+#endif
+
+    p = pos;
+
+    len = ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
+                          tp->active_connection_id_limit);
+
+    len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA,tp->initial_max_data);
+
+    len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
+                           tp->initial_max_streams_uni);
+
+    len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
+                           tp->initial_max_streams_bidi);
+
+    len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+                           tp->initial_max_stream_data_bidi_local);
+
+    len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+                           tp->initial_max_stream_data_bidi_remote);
+
+    len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
+                           tp->initial_max_stream_data_uni);
+
+    if (pos == NULL) {
+#if (quic_version < 0xff00001b)
+        len += ngx_quic_varint_len(len);
+#endif
+        return len;
+    }
+
+#if (quic_version < 0xff00001b)
+    /* TLS extension length */
+    p = ngx_quic_write_uint16(p, len);
+#endif
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
+                     tp->active_connection_id_limit);
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA,
+                     tp->initial_max_data);
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
+                     tp->initial_max_streams_uni);
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
+                     tp->initial_max_streams_bidi);
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+                     tp->initial_max_stream_data_bidi_local);
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+                     tp->initial_max_stream_data_bidi_remote);
+
+    ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
+                     tp->initial_max_stream_data_uni);
+
+    ngx_quic_hexdump0(ngx_cycle->log, "transport parameters", pos, p - pos);
+
+    return p - pos;
+}
+
+
 static size_t
 ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl)
 {
--- a/src/event/ngx_event_quic_transport.h
+++ b/src/event/ngx_event_quic_transport.h
@@ -65,10 +65,28 @@
 #define NGX_QUIC_ERR_INVALID_TOKEN              0x0B
 /* 0xC is not defined */
 #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED     0x0D
+/* 0xE is not defined */
 #define NGX_QUIC_ERR_CRYPTO_ERROR               0x10
 
 #define NGX_QUIC_ERR_LAST  NGX_QUIC_ERR_CRYPTO_ERROR
 
+/* Transport parameters */
+#define NGX_QUIC_TP_ORIGINAL_CONNECTION_ID               0x00
+#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT                     0x01
+#define NGX_QUIC_TP_STATELESS_RESET_TOKEN                0x02
+#define NGX_QUIC_TP_MAX_PACKET_SIZE                      0x03
+#define NGX_QUIC_TP_INITIAL_MAX_DATA                     0x04
+#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL   0x05
+#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE  0x06
+#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI          0x07
+#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI             0x08
+#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI              0x09
+#define NGX_QUIC_TP_ACK_DELAY_EXPONENT                   0x0a
+#define NGX_QUIC_TP_MAX_ACK_DELAY                        0x0b
+#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION             0x0c
+#define NGX_QUIC_TP_PREFERRED_ADDRESS                    0x0d
+#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT           0x0e
+
 
 typedef struct {
     ngx_uint_t                                  pn;
@@ -208,4 +226,7 @@ ssize_t ngx_quic_parse_frame(ngx_quic_he
     ngx_quic_frame_t *frame);
 ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f);
 
+ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end,
+    ngx_quic_tp_t *tp);
+
 #endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -341,11 +341,14 @@ ngx_http_init_connection(ngx_connection_
 
 #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, c->listening->post_accept_timeout,
+        ngx_quic_run(c, &sscf->ssl, &v3cf->quic,
+                     c->listening->post_accept_timeout,
                      ngx_http_quic_stream_handler);
         return;
     }
--- a/src/http/v3/ngx_http_v3.h
+++ b/src/http/v3/ngx_http_v3.h
@@ -40,6 +40,11 @@
 
 
 typedef struct {
+    ngx_quic_tp_t           quic;
+} ngx_http_v3_srv_conf_t;
+
+
+typedef struct {
     ngx_http_connection_t   hc;
 
     ngx_array_t            *dynamic;
--- a/src/http/v3/ngx_http_v3_module.c
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -11,10 +11,100 @@
 
 
 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),
+      NULL },
+
+    { ngx_string("quic_max_packet_size"),
+      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.max_packet_size),
+      NULL },
+
+    { ngx_string("quic_initial_max_data"),
+      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_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_num_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_num_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_num_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),
+      NULL },
+
+    { 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),
+      NULL },
+
       ngx_null_command
 };
 
 
+static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+
 static ngx_http_module_t  ngx_http_v3_module_ctx = {
     NULL,                                  /* preconfiguration */
     NULL,                                  /* postconfiguration */
@@ -22,8 +112,8 @@ static ngx_http_module_t  ngx_http_v3_mo
     NULL,                                  /* create main configuration */
     NULL,                                  /* init main configuration */
 
-    NULL,                                  /* create server configuration */
-    NULL,                                  /* merge server configuration */
+    ngx_http_v3_create_srv_conf,           /* create server configuration */
+    ngx_http_v3_merge_srv_conf,            /* merge server configuration */
 
     NULL,                                  /* create location configuration */
     NULL                                   /* merge location configuration */
@@ -44,3 +134,91 @@ ngx_module_t  ngx_http_v3_module = {
     NULL,                                  /* exit master */
     NGX_MODULE_V1_PADDING
 };
+
+
+static void *
+ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_v3_srv_conf_t  *v3cf;
+
+    v3cf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
+    if (v3cf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *  v3cf->quic.original_connection_id = 0;
+     *  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_packet_size = NGX_CONF_UNSET_UINT;
+    v3cf->quic.initial_max_data = NGX_CONF_UNSET_UINT;
+    v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_UINT;
+    v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_UINT;
+    v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_UINT;
+    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;
+
+    return v3cf;
+}
+
+
+static char *
+ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_v3_srv_conf_t *prev = parent;
+    ngx_http_v3_srv_conf_t *conf = child;
+
+    ngx_conf_merge_msec_value(conf->quic.max_idle_timeout,
+                              prev->quic.max_idle_timeout, 10000);
+
+    // > 2 ^ 14 is invalid
+    ngx_conf_merge_msec_value(conf->quic.max_ack_delay,
+                              prev->quic.max_ack_delay, 25);
+
+    // < 1200 is invalid
+    ngx_conf_merge_uint_value(conf->quic.max_packet_size,
+                              prev->quic.max_packet_size, 65527);
+
+    ngx_conf_merge_uint_value(conf->quic.initial_max_data,
+                              prev->quic.initial_max_data, 10000000);
+
+    ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_local,
+                              prev->quic.initial_max_stream_data_bidi_local,
+                              255);
+
+    ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_remote,
+                              prev->quic.initial_max_stream_data_bidi_remote,
+                              255);
+
+    ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_uni,
+                              prev->quic.initial_max_stream_data_uni, 255);
+
+    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);
+
+    // > 20 is invalid
+    ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent,
+                              prev->quic.ack_delay_exponent, 3);
+
+    ngx_conf_merge_uint_value(conf->quic.disable_active_migration,
+                              prev->quic.disable_active_migration, 1);
+
+    // < 2 is invalid
+    ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
+                              prev->quic.active_connection_id_limit, 2);
+
+    return NGX_CONF_OK;
+}
+