changeset 8167:5d91389e0fd3 quic

Initial QUIC support in http.
author Sergey Kandaurov <pluknet@nginx.com>
date Fri, 28 Feb 2020 13:09:51 +0300
parents 7999d3fbb765
children b507592c15a7
files auto/modules src/core/ngx_connection.h src/core/ngx_core.h src/event/ngx_event_openssl.c src/event/ngx_event_openssl.h src/event/ngx_event_quic.h src/http/modules/ngx_http_ssl_module.c src/http/modules/ngx_http_ssl_module.h src/http/ngx_http.c 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
diffstat 13 files changed, 766 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/auto/modules
+++ b/auto/modules
@@ -1242,7 +1242,8 @@ if [ $USE_OPENSSL = YES ]; then
     ngx_module_type=CORE
     ngx_module_name=ngx_openssl_module
     ngx_module_incs=
-    ngx_module_deps=src/event/ngx_event_openssl.h
+    ngx_module_deps="src/event/ngx_event_openssl.h \
+                     src/event/ngx_event_quic.h"
     ngx_module_srcs="src/event/ngx_event_openssl.c
                      src/event/ngx_event_openssl_stapling.c"
     ngx_module_libs=
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -147,13 +147,14 @@ struct ngx_connection_s {
     socklen_t           socklen;
     ngx_str_t           addr_text;
 
-    ngx_proxy_protocol_t  *proxy_protocol;
+    ngx_proxy_protocol_t   *proxy_protocol;
 
 #if (NGX_SSL || NGX_COMPAT)
-    ngx_ssl_connection_t  *ssl;
+    ngx_quic_connection_t  *quic;
+    ngx_ssl_connection_t   *ssl;
 #endif
 
-    ngx_udp_connection_t  *udp;
+    ngx_udp_connection_t   *udp;
 
     struct sockaddr    *local_sockaddr;
     socklen_t           local_socklen;
--- a/src/core/ngx_core.h
+++ b/src/core/ngx_core.h
@@ -12,23 +12,24 @@
 #include <ngx_config.h>
 
 
-typedef struct ngx_module_s          ngx_module_t;
-typedef struct ngx_conf_s            ngx_conf_t;
-typedef struct ngx_cycle_s           ngx_cycle_t;
-typedef struct ngx_pool_s            ngx_pool_t;
-typedef struct ngx_chain_s           ngx_chain_t;
-typedef struct ngx_log_s             ngx_log_t;
-typedef struct ngx_open_file_s       ngx_open_file_t;
-typedef struct ngx_command_s         ngx_command_t;
-typedef struct ngx_file_s            ngx_file_t;
-typedef struct ngx_event_s           ngx_event_t;
-typedef struct ngx_event_aio_s       ngx_event_aio_t;
-typedef struct ngx_connection_s      ngx_connection_t;
-typedef struct ngx_thread_task_s     ngx_thread_task_t;
-typedef struct ngx_ssl_s             ngx_ssl_t;
-typedef struct ngx_proxy_protocol_s  ngx_proxy_protocol_t;
-typedef struct ngx_ssl_connection_s  ngx_ssl_connection_t;
-typedef struct ngx_udp_connection_s  ngx_udp_connection_t;
+typedef struct ngx_module_s           ngx_module_t;
+typedef struct ngx_conf_s             ngx_conf_t;
+typedef struct ngx_cycle_s            ngx_cycle_t;
+typedef struct ngx_pool_s             ngx_pool_t;
+typedef struct ngx_chain_s            ngx_chain_t;
+typedef struct ngx_log_s              ngx_log_t;
+typedef struct ngx_open_file_s        ngx_open_file_t;
+typedef struct ngx_command_s          ngx_command_t;
+typedef struct ngx_file_s             ngx_file_t;
+typedef struct ngx_event_s            ngx_event_t;
+typedef struct ngx_event_aio_s        ngx_event_aio_t;
+typedef struct ngx_connection_s       ngx_connection_t;
+typedef struct ngx_thread_task_s      ngx_thread_task_t;
+typedef struct ngx_ssl_s              ngx_ssl_t;
+typedef struct ngx_proxy_protocol_s   ngx_proxy_protocol_t;
+typedef struct ngx_quic_connection_s  ngx_quic_connection_t;
+typedef struct ngx_ssl_connection_s   ngx_ssl_connection_t;
+typedef struct ngx_udp_connection_s   ngx_udp_connection_t;
 
 typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
 typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
@@ -82,6 +83,7 @@ typedef void (*ngx_connection_handler_pt
 #include <ngx_resolver.h>
 #if (NGX_OPENSSL)
 #include <ngx_event_openssl.h>
+#include <ngx_event_quic.h>
 #endif
 #include <ngx_process_cycle.h>
 #include <ngx_conf_file.h>
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -89,6 +89,126 @@ static void *ngx_openssl_create_conf(ngx
 static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
 static void ngx_openssl_exit(ngx_cycle_t *cycle);
 
+#if NGX_OPENSSL_QUIC
+
+static int
+quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
+    enum ssl_encryption_level_t level, const uint8_t *read_secret,
+    const uint8_t *write_secret, size_t secret_len)
+{
+    size_t             *len;
+    uint8_t           **rsec, **wsec;
+    ngx_connection_t   *c;
+
+    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+    ngx_ssl_handshake_log(c);
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        u_char  buf[64];
+        size_t  m;
+
+        m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "set_encryption_secrets: %*s, len: %uz, level:%d",
+                       m, buf, secret_len, (int) level);
+
+        m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "set_encryption_secrets: %*s, len: %uz, level:%d",
+                       m, buf, secret_len, (int) level);
+    }
+#endif
+
+    switch (level) {
+
+    case ssl_encryption_handshake:
+        len = &c->quic->handshake_secret_len;
+        rsec = &c->quic->handshake_read_secret;
+        wsec = &c->quic->handshake_write_secret;
+        break;
+
+    case ssl_encryption_application:
+        len = &c->quic->application_secret_len;
+        rsec = &c->quic->application_read_secret;
+        wsec = &c->quic->application_write_secret;
+        break;
+
+    default:
+        return 0;
+    }
+
+    *len = secret_len;
+
+    *rsec = ngx_pnalloc(c->pool, secret_len);
+    if (*rsec == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(*rsec, read_secret, secret_len);
+
+    *wsec = ngx_pnalloc(c->pool, secret_len);
+    if (*wsec == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(*wsec, write_secret, secret_len);
+
+    return 1;
+}
+
+
+static int
+quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
+    enum ssl_encryption_level_t level, const uint8_t *data, size_t len)
+{
+    u_char             buf[512];
+    ngx_int_t          m;
+    ngx_connection_t  *c;
+
+    c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+    m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 256)) - buf;
+    ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic_add_handshake_data: %*s%s, len: %uz, level:%d",
+                   m, buf, len < 512 ? "" : "...", len, (int) level);
+
+    if (!(SSL_provide_quic_data(ssl_conn, level, data, len))) {
+        ERR_print_errors_fp(stderr);
+        return 0;
+    }
+
+    return 1;
+}
+
+
+static int
+quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
+{
+    printf("quic_flush_flight()\n");
+    return 1;
+}
+
+
+static int
+quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
+    uint8_t alert)
+{
+    printf("quic_send_alert(), lvl=%d, alert=%d\n", level, alert);
+    return 1;
+}
+
+
+static SSL_QUIC_METHOD quic_method = {
+    quic_set_encryption_secrets,
+    quic_add_handshake_data,
+    quic_flush_flight,
+    quic_send_alert,
+};
+
+#endif
+
 
 static ngx_command_t  ngx_openssl_commands[] = {
 
@@ -1460,6 +1580,29 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_s
 
 
 ngx_int_t
+ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable)
+{
+    if (!enable) {
+        return NGX_OK;
+    }
+
+#if NGX_OPENSSL_QUIC
+
+    SSL_CTX_set_quic_method(ssl->ctx, &quic_method);
+printf("%s\n", __func__);
+    return NGX_OK;
+
+#else
+
+    ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                  "\"ssl_quic\" is not supported on this platform");
+    return NGX_ERROR;
+
+#endif
+}
+
+
+ngx_int_t
 ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable)
 {
     if (!enable) {
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -14,6 +14,7 @@
 
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/aes.h>
 #include <openssl/bn.h>
 #include <openssl/conf.h>
 #include <openssl/crypto.h>
@@ -22,6 +23,7 @@
 #include <openssl/engine.h>
 #endif
 #include <openssl/evp.h>
+#include <openssl/hkdf.h>
 #include <openssl/hmac.h>
 #ifndef OPENSSL_NO_OCSP
 #include <openssl/ocsp.h>
@@ -189,6 +191,7 @@ ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf
 ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
 ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_uint_t enable);
+ngx_int_t ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable);
 ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_uint_t enable);
 ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_quic.h
@@ -0,0 +1,31 @@
+
+/*
+ *
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
+#define _NGX_EVENT_QUIC_H_INCLUDED_
+
+
+struct ngx_quic_connection_s {
+    ngx_str_t   scid;
+    ngx_str_t   dcid;
+    ngx_str_t   token;
+
+    ngx_str_t   client_in;
+    ngx_str_t   client_in_key;
+    ngx_str_t   client_in_iv;
+    ngx_str_t   client_in_hp;
+
+    size_t      handshake_secret_len;
+    uint8_t    *handshake_read_secret;
+    uint8_t    *handshake_write_secret;
+
+    size_t      application_secret_len;
+    uint8_t    *application_read_secret;
+    uint8_t    *application_write_secret;
+};
+
+
+#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -249,6 +249,13 @@ static ngx_command_t  ngx_http_ssl_comma
       offsetof(ngx_http_ssl_srv_conf_t, early_data),
       NULL },
 
+    { ngx_string("ssl_quic"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, quic),
+      NULL },
+
       ngx_null_command
 };
 
@@ -568,6 +575,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t 
     sscf->enable = NGX_CONF_UNSET;
     sscf->prefer_server_ciphers = NGX_CONF_UNSET;
     sscf->early_data = NGX_CONF_UNSET;
+    sscf->quic = NGX_CONF_UNSET;
     sscf->buffer_size = NGX_CONF_UNSET_SIZE;
     sscf->verify = NGX_CONF_UNSET_UINT;
     sscf->verify_depth = NGX_CONF_UNSET_UINT;
@@ -612,6 +620,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
 
     ngx_conf_merge_value(conf->early_data, prev->early_data, 0);
 
+    ngx_conf_merge_value(conf->quic, prev->quic, 0);
+
     ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
                          (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
                           |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
@@ -696,6 +706,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
         }
     }
 
+printf("ngx_ssl_create\n");
     if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
         return NGX_CONF_ERROR;
     }
@@ -857,6 +868,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
         return NGX_CONF_ERROR;
     }
 
+    if (ngx_ssl_quic(cf, &conf->ssl, conf->quic) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
     return NGX_CONF_OK;
 }
 
@@ -1141,13 +1156,15 @@ ngx_http_ssl_init(ngx_conf_t *cf)
 
         addr = port[p].addrs.elts;
         for (a = 0; a < port[p].addrs.nelts; a++) {
+printf("ssl %d http3 %d\n", addr[a].opt.ssl, addr[a].opt.http3);
 
-            if (!addr[a].opt.ssl) {
+            if (!addr[a].opt.ssl && !addr[a].opt.http3) {
                 continue;
             }
 
             cscf = addr[a].default_server;
             sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+printf("sscf->protocols %lx\n", sscf->protocols);
 
             if (sscf->certificates == NULL) {
                 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
@@ -1156,6 +1173,14 @@ ngx_http_ssl_init(ngx_conf_t *cf)
                               cscf->file_name, cscf->line);
                 return NGX_ERROR;
             }
+
+            if (addr[a].opt.http3 && !(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);
+                return NGX_ERROR;
+            }
         }
     }
 
--- a/src/http/modules/ngx_http_ssl_module.h
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -21,6 +21,7 @@ typedef struct {
 
     ngx_flag_t                      prefer_server_ciphers;
     ngx_flag_t                      early_data;
+    ngx_flag_t                      quic;
 
     ngx_uint_t                      protocols;
 
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1203,6 +1203,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_V2)
     ngx_uint_t             http2;
 #endif
+#if (NGX_HTTP_SSL)
+    ngx_uint_t             http3;
+#endif
 
     /*
      * we cannot compare whole sockaddr struct's as kernel
@@ -1238,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_V2)
         http2 = lsopt->http2 || addr[i].opt.http2;
 #endif
+#if (NGX_HTTP_SSL)
+        http3 = lsopt->http3 || addr[i].opt.http3;
+#endif
 
         if (lsopt->set) {
 
@@ -1274,6 +1280,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_V2)
         addr[i].opt.http2 = http2;
 #endif
+#if (NGX_HTTP_SSL)
+        addr[i].opt.http3 = http3;
+#endif
 
         return NGX_OK;
     }
@@ -1317,6 +1326,17 @@ ngx_http_add_address(ngx_conf_t *cf, ngx
 
 #endif
 
+#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC)
+
+    if (lsopt->http3) {
+        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",
+                           &lsopt->addr_text);
+    }
+
+#endif
+
     addr = ngx_array_push(&port->addrs);
     if (addr == NULL) {
         return NGX_ERROR;
@@ -1807,6 +1827,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h
 #if (NGX_HTTP_V2)
         addrs[i].conf.http2 = addr[i].opt.http2;
 #endif
+#if (NGX_HTTP_SSL)
+        addrs[i].conf.http3 = addr[i].opt.http3;
+#endif
         addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
 
         if (addr[i].hash.buckets == NULL
@@ -1872,6 +1895,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_
 #if (NGX_HTTP_V2)
         addrs6[i].conf.http2 = addr[i].opt.http2;
 #endif
+#if (NGX_HTTP_SSL)
+        addrs6[i].conf.http3 = addr[i].opt.http3;
+#endif
         addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
 
         if (addr[i].hash.buckets == NULL
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -3822,11 +3822,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
             continue;
         }
 
-        if (ngx_strcmp(value[n].data, "quic") == 0) {
-            lsopt.type = SOCK_DGRAM;
-            continue;
-        }
-
         if (ngx_strcmp(value[n].data, "bind") == 0) {
             lsopt.set = 1;
             lsopt.bind = 1;
@@ -4004,6 +3999,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
 #endif
         }
 
+        if (ngx_strcmp(value[n].data, "http3") == 0) {
+#if (NGX_HTTP_SSL)
+            lsopt.http3 = 1;
+            lsopt.type = SOCK_DGRAM;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"http3\" parameter requires "
+                               "ngx_http_ssl_module");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
         if (ngx_strcmp(value[n].data, "spdy") == 0) {
             ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                                "invalid parameter \"spdy\": "
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -75,6 +75,7 @@ typedef struct {
     unsigned                   wildcard:1;
     unsigned                   ssl:1;
     unsigned                   http2:1;
+    unsigned                   http3:1;
 #if (NGX_HAVE_INET6)
     unsigned                   ipv6only:1;
 #endif
@@ -238,6 +239,7 @@ struct ngx_http_addr_conf_s {
 
     unsigned                   ssl: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
@@ -62,6 +62,8 @@ static u_char *ngx_http_log_error_handle
 #if (NGX_HTTP_SSL)
 static void ngx_http_ssl_handshake(ngx_event_t *rev);
 static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
+
+static void ngx_http_quic_handshake(ngx_event_t *rev);
 #endif
 
 
@@ -328,6 +330,14 @@ ngx_http_init_connection(ngx_connection_
         rev->ready = 1;
     }
 
+#if (NGX_HTTP_SSL)
+    if (hc->addr_conf->http3) {
+        hc->quic = 1;
+        c->log->action = "QUIC handshaking";
+        rev->handler = ngx_http_quic_handshake;
+    }
+#endif
+
 #if (NGX_HTTP_V2)
     if (hc->addr_conf->http2) {
         rev->handler = ngx_http_v2_init;
@@ -647,6 +657,491 @@ ngx_http_alloc_request(ngx_connection_t 
 
 #if (NGX_HTTP_SSL)
 
+static uint64_t
+ngx_quic_parse_int(u_char **pos)
+{
+    u_char      *p;
+    uint64_t     value;
+    ngx_uint_t   len;
+
+    p = *pos;
+    len = 1 << ((*p & 0xc0) >> 6);
+    value = *p++ & 0x3f;
+
+    while (--len) {
+        value = (value << 8) + *p++;
+    }
+
+    *pos = p;
+    return value;
+}
+
+
+static uint64_t
+ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask)
+{
+    u_char      *p;
+    uint64_t     value;
+
+    p = *pos;
+    value = *p++ ^ *mask++;
+
+    while (--len) {
+        value = (value << 8) + (*p++ ^ *mask++);
+    }
+
+    *pos = p;
+    return value;
+}
+
+
+static void
+ngx_http_quic_handshake(ngx_event_t *rev)
+{
+    int                       n, sslerr;
+#if (NGX_DEBUG)
+    u_char                    buf[512];
+    size_t                    m;
+#endif
+    ngx_buf_t                *b;
+    ngx_connection_t         *c;
+    ngx_http_connection_t    *hc;
+    ngx_quic_connection_t    *qc;
+    ngx_http_ssl_srv_conf_t  *sscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake");
+
+    c = rev->data;
+    hc = c->data;
+    b = c->buffer;
+
+    qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
+    if (qc == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->quic = qc;
+
+    printf("buffer %p %p:%p:%p:%p \n", b, b->start, b->pos, b->last, b->end);
+
+    if ((b->pos[0] & 0xf0) != 0xc0) {
+        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid initial packet");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (ngx_buf_size(b) < 1200) {
+        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "too small UDP datagram");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_int_t flags = *b->pos++;
+    uint32_t version = ngx_http_v2_parse_uint32(b->pos);
+    b->pos += 4;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "quic flags:%xi version:%xD", flags, version);
+
+    if (version != 0xff000017) {
+        ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    qc->dcid.len = *b->pos++;
+    qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
+    if (qc->dcid.data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len);
+    b->pos += qc->dcid.len;
+
+    qc->scid.len = *b->pos++;
+    qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
+    if (qc->scid.data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_memcpy(qc->scid.data, b->pos, qc->scid.len);
+    b->pos += qc->scid.len;
+
+    qc->token.len = ngx_quic_parse_int(&b->pos);
+    qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
+    if (qc->token.data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_memcpy(qc->token.data, b->pos, qc->token.len);
+    b->pos += qc->token.len;
+
+    uint64_t plen = ngx_quic_parse_int(&b->pos);
+    /* draft-ietf-quic-tls-23#section-5.4.2:
+     * the Packet Number field is assumed to be 4 bytes long
+     * draft-ietf-quic-tls-23#section-5.4.3:
+     * AES-Based header protection samples 16 bytes
+     */
+    u_char *sample = b->pos + 4;
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len);
+
+        m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic SCID: %*s, len: %uz", m, buf, qc->scid.len);
+
+        m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic token: %*s, len: %uz", m, buf, qc->token.len);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic packet length: %d", plen);
+
+        m = ngx_hex_dump(buf, sample, 16) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic sample: %*s", m, buf);
+    }
+#endif
+
+// initial secret
+
+    size_t          is_len;
+    uint8_t         is[SHA256_DIGEST_LENGTH];
+    const EVP_MD   *digest;
+    static const uint8_t salt[20] =
+        "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7"
+        "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02";
+
+    digest = EVP_sha256();
+    HKDF_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, salt,
+                 sizeof(salt));
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic salt: %*s, len: %uz", m, buf, sizeof(salt));
+
+        m = ngx_hex_dump(buf, is, is_len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic initial secret: %*s, len: %uz", m, buf, is_len);
+    }
+#endif
+
+        size_t hkdfl_len;
+        uint8_t hkdfl[20];
+        uint8_t *p;
+
+        /* draft-ietf-quic-tls-23#section-5.2 */
+
+        qc->client_in.len = SHA256_DIGEST_LENGTH;
+        qc->client_in.data = ngx_pnalloc(c->pool, qc->client_in.len);
+        if (qc->client_in.data == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1;
+        hkdfl[0] = 0;
+        hkdfl[1] = qc->client_in.len;
+        hkdfl[2] = sizeof("tls13 client in") - 1;
+        p = ngx_cpymem(&hkdfl[3], "tls13 client in",
+                       sizeof("tls13 client in") - 1);
+        *p = '\0';
+
+        if (HKDF_expand(qc->client_in.data, qc->client_in.len,
+                        digest, is, is_len, hkdfl, hkdfl_len)
+            == 0)
+        {
+            ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                          "HKDF_expand(client_in) failed");
+            ngx_http_close_connection(c);
+            return;
+        }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "quic EVP key:%d tag:%d nonce:%d",
+                   EVP_AEAD_key_length(EVP_aead_aes_128_gcm()),
+                   EVP_AEAD_max_tag_len(EVP_aead_aes_128_gcm()),
+                   EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()));
+
+        /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
+
+        qc->client_in_key.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm());
+        qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len);
+        if (qc->client_in_key.data == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1;
+        hkdfl[1] = qc->client_in_key.len;
+        hkdfl[2] = sizeof("tls13 quic key") - 1;
+        p = ngx_cpymem(&hkdfl[3], "tls13 quic key",
+                       sizeof("tls13 quic key") - 1);
+        *p = '\0';
+
+        if (HKDF_expand(qc->client_in_key.data, qc->client_in_key.len,
+                        digest, qc->client_in.data, qc->client_in.len,
+                        hkdfl, hkdfl_len)
+            == 0)
+        {
+            ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                          "HKDF_expand(client_in_key) failed");
+            ngx_http_close_connection(c);
+            return;
+        }        
+
+        qc->client_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm());
+        qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len);
+        if (qc->client_in_iv.data == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1;
+        hkdfl[1] = qc->client_in_iv.len;
+        hkdfl[2] = sizeof("tls13 quic iv") - 1;
+        p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1);
+        *p = '\0';
+
+        if (HKDF_expand(qc->client_in_iv.data, qc->client_in_iv.len, digest,
+                        qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len)
+            == 0)
+        {
+            ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                          "HKDF_expand(client_in_iv) failed");
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
+
+        qc->client_in_hp.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm());
+        qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len);
+        if (qc->client_in_hp.data == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1;
+        hkdfl[1] = qc->client_in_hp.len;
+        hkdfl[2] = sizeof("tls13 quic hp") - 1;
+        p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1);
+        *p = '\0';
+
+        if (HKDF_expand(qc->client_in_hp.data, qc->client_in_hp.len, digest,
+                        qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len)
+            == 0)
+        {
+            ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                          "HKDF_expand(client_in_hp) failed");
+            ngx_http_close_connection(c);
+            return;
+        }
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, qc->client_in.data, qc->client_in.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic client initial secret: %*s, len: %uz",
+                       m, buf, qc->client_in.len);
+
+        m = ngx_hex_dump(buf, qc->client_in_key.data, qc->client_in_key.len)
+            - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic key: %*s, len: %uz",
+                       m, buf, qc->client_in_key.len);
+
+        m = ngx_hex_dump(buf, qc->client_in_iv.data, qc->client_in_iv.len)
+            - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic iv: %*s, len: %uz", m, buf, qc->client_in_iv.len);
+
+        m = ngx_hex_dump(buf, qc->client_in_hp.data, qc->client_in_hp.len)
+            - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic hp: %*s, len: %uz", m, buf, qc->client_in_hp.len);
+    }
+#endif
+
+// header protection
+
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    uint8_t mask[16];
+    int outlen;
+
+    if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL,
+                           qc->client_in_hp.data, NULL)
+        != 1)
+    {
+        EVP_CIPHER_CTX_free(ctx);
+        ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                      "EVP_EncryptInit_ex() failed");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) {
+        EVP_CIPHER_CTX_free(ctx);
+        ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                      "EVP_EncryptUpdate() failed");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    EVP_CIPHER_CTX_free(ctx);
+
+    u_char  clearflags = flags ^ (mask[0] & 0x0f);
+    ngx_int_t  pnl = (clearflags & 0x03) + 1;
+    uint64_t  pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]);
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, sample, 16) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic sample: %*s", m, buf);
+
+        m = ngx_hex_dump(buf, mask, 5) - buf;
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic mask: %*s", m, buf);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic packet number: %uL, len: %xi", pn, pnl);
+    }
+#endif
+
+// packet protection
+
+    ngx_str_t ciphertext;
+    ciphertext.data = b->pos;
+    ciphertext.len = plen - pnl;
+
+    ngx_str_t ad;
+    ad.len = b->pos - b->start;
+    ad.data = ngx_pnalloc(c->pool, ad.len);
+    if (ad.data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_memcpy(ad.data, b->start, ad.len);
+    ad.data[0] = clearflags;
+    ad.data[ad.len - pnl] = (u_char)pn;
+
+    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in_iv);
+    nonce[11] ^= pn;
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, nonce, 12) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic nonce: %*s, len: %uz", m, buf, 12);
+
+        m = ngx_hex_dump(buf, ad.data, ad.len) - buf;
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic ad: %*s, len: %uz", m, buf, ad.len);
+    }
+#endif
+
+    EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(),
+                                          qc->client_in_key.data,
+                                          qc->client_in_key.len,
+                                          EVP_AEAD_DEFAULT_TAG_LENGTH);
+    uint8_t cleartext[1600];
+    size_t cleartext_len = sizeof(cleartext);
+
+    if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext),
+                          nonce, qc->client_in_iv.len, ciphertext.data,
+                          ciphertext.len, ad.data, ad.len)
+        != 1)
+    {
+        EVP_AEAD_CTX_free(aead);
+        ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                      "EVP_AEAD_CTX_open() failed");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    EVP_AEAD_CTX_free(aead);
+
+#if (NGX_DEBUG)
+    if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
+        m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf;
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                       "quic packet: %*s%s, len: %uz",
+                       m, buf, m < 512 ? "" : "...", cleartext_len);
+    }
+#endif
+
+    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+    if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
+        != NGX_OK)
+    {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    n = SSL_do_handshake(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+    if (n == -1) {
+        sslerr = SSL_get_error(c->ssl->connection, n);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
+                       sslerr);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+                   (int) SSL_quic_read_level(c->ssl->connection),
+                   (int) SSL_quic_write_level(c->ssl->connection));
+
+    if (!SSL_provide_quic_data(c->ssl->connection,
+                               SSL_quic_read_level(c->ssl->connection),
+                               &cleartext[4], cleartext_len - 4))
+    {
+            ngx_ssl_error(NGX_LOG_INFO, rev->log, 0,
+                          "SSL_provide_quic_data() failed");
+            ngx_http_close_connection(c);
+            return;
+    }
+
+    n = SSL_do_handshake(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+    if (n == -1) {
+        sslerr = SSL_get_error(c->ssl->connection, n);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
+                       sslerr);
+
+        if (sslerr == SSL_ERROR_SSL) {
+            ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+                   (int) SSL_quic_read_level(c->ssl->connection),
+                   (int) SSL_quic_write_level(c->ssl->connection));
+
+    ngx_http_close_connection(c);
+    return;
+}
+
+
 static void
 ngx_http_ssl_handshake(ngx_event_t *rev)
 {
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -323,6 +323,7 @@ typedef struct {
     ngx_chain_t                      *free;
 
     unsigned                          ssl:1;
+    unsigned                          quic:1;
     unsigned                          proxy_protocol:1;
 } ngx_http_connection_t;