Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
5319 lines (5138 sloc) 150 KB
Add QUIC Support.
Add HTTP2 HPACK Encoding Support.
Add Dynamic TLS Record support.
Using: patch -p1 < nginx.patch
diff -uNr a/auto/lib/conf f/auto/lib/conf
--- a/auto/lib/conf 2019-09-24 23:08:48.000000000 +0800
+++ f/auto/lib/conf 2019-10-18 00:32:29.357729822 +0800
@@ -25,6 +25,10 @@
. auto/lib/openssl/conf
fi
+if [ $USE_QUICHE = YES ]; then
+ . auto/lib/quiche/conf
+fi
+
if [ $USE_ZLIB = YES ]; then
. auto/lib/zlib/conf
fi
diff -uNr a/auto/lib/make f/auto/lib/make
--- a/auto/lib/make 2019-09-24 23:08:48.000000000 +0800
+++ f/auto/lib/make 2019-10-18 00:32:29.357729822 +0800
@@ -11,6 +11,10 @@
. auto/lib/openssl/make
fi
+if [ $QUICHE != NONE -a $QUICHE != NO -a $QUICHE != YES ]; then
+ . auto/lib/quiche/make
+fi
+
if [ $ZLIB != NONE -a $ZLIB != NO -a $ZLIB != YES ]; then
. auto/lib/zlib/make
fi
diff -uNr a/auto/lib/openssl/make f/auto/lib/openssl/make
--- a/auto/lib/openssl/make 2019-09-24 23:08:48.000000000 +0800
+++ f/auto/lib/openssl/make 2019-10-18 00:32:29.358729830 +0800
@@ -49,11 +49,13 @@
cat << END >> $NGX_MAKEFILE
$OPENSSL/.openssl/include/openssl/ssl.h: $NGX_MAKEFILE
- cd $OPENSSL \\
- && if [ -f Makefile ]; then \$(MAKE) clean; fi \\
- && ./config --prefix=$ngx_prefix no-shared no-threads $OPENSSL_OPT \\
- && \$(MAKE) \\
- && \$(MAKE) install_sw LIBDIR=lib
+ mkdir -p $OPENSSL/build $OPENSSL/.openssl/lib $OPENSSL/.openssl/include/openssl \\
+ && cd $OPENSSL/build \\
+ && cmake -DCMAKE_C_FLAGS="$OPENSSL_OPT" -DCMAKE_CXX_FLAGS="$OPENSSL_OPT" .. \\
+ && \$(MAKE) VERBOSE=1 \\
+ && cd .. \\
+ && cp -r include/openssl/*.h .openssl/include/openssl \\
+ && cp build/ssl/libssl.a build/crypto/libcrypto.a .openssl/lib
END
diff -uNr a/auto/lib/quiche/conf f/auto/lib/quiche/conf
--- a/auto/lib/quiche/conf 1970-01-01 08:00:00.000000000 +0800
+++ f/auto/lib/quiche/conf 2019-10-18 00:32:29.358729830 +0800
@@ -0,0 +1,19 @@
+
+# Copyright (C) Cloudflare, Inc.
+
+
+if [ $QUICHE != NONE ]; then
+
+ have=NGX_QUIC . auto/have
+
+ QUICHE_BUILD_TARGET="release"
+
+ if [ $NGX_DEBUG = YES ]; then
+ QUICHE_BUILD_TARGET="debug"
+ fi
+
+ CORE_INCS="$CORE_INCS $QUICHE/include"
+ CORE_DEPS="$CORE_DEPS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a"
+ CORE_LIBS="$CORE_LIBS $QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a $NGX_LIBPTHREAD"
+
+fi
diff -uNr a/auto/lib/quiche/make f/auto/lib/quiche/make
--- a/auto/lib/quiche/make 1970-01-01 08:00:00.000000000 +0800
+++ f/auto/lib/quiche/make 2019-10-18 00:32:29.359729838 +0800
@@ -0,0 +1,22 @@
+
+# Copyright (C) Cloudflare, Inc.
+
+
+# Default is release build
+QUICHE_BUILD_FLAGS="--release --no-default-features"
+QUICHE_BUILD_TARGET="release"
+
+if [ $NGX_DEBUG = YES ]; then
+ QUICHE_BUILD_FLAGS="--no-default-features"
+ QUICHE_BUILD_TARGET="debug"
+fi
+
+
+cat << END >> $NGX_MAKEFILE
+
+$QUICHE/target/$QUICHE_BUILD_TARGET/libquiche.a: \\
+ $OPENSSL/.openssl/include/openssl/ssl.h \\
+ $NGX_MAKEFILE
+ cd $QUICHE && cargo build $QUICHE_BUILD_FLAGS
+
+END
diff -uNr a/auto/make f/auto/make
--- a/auto/make 2019-09-24 23:08:48.000000000 +0800
+++ f/auto/make 2019-10-18 00:32:29.359729838 +0800
@@ -7,7 +7,8 @@
mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \
$NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \
- $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \
+ $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \
+ $NGX_OBJS/src/http/modules \
$NGX_OBJS/src/http/modules/perl \
$NGX_OBJS/src/mail \
$NGX_OBJS/src/stream \
diff -uNr a/auto/modules f/auto/modules
--- a/auto/modules 2019-09-24 23:08:48.000000000 +0800
+++ f/auto/modules 2019-10-18 00:33:02.148997204 +0800
@@ -119,6 +119,7 @@
# ngx_http_header_filter
# ngx_http_chunked_filter
# ngx_http_v2_filter
+ # ngx_http_v3_filter
# ngx_http_range_header_filter
# ngx_http_gzip_filter
# ngx_http_postpone_filter
@@ -151,6 +152,7 @@
ngx_http_header_filter_module \
ngx_http_chunked_filter_module \
ngx_http_v2_filter_module \
+ ngx_http_v3_filter_module \
ngx_http_range_header_filter_module \
ngx_http_gzip_filter_module \
ngx_http_postpone_filter_module \
@@ -212,6 +214,17 @@
. auto/module
fi
+ if [ $HTTP_V3 = YES ]; then
+ ngx_module_name=ngx_http_v3_filter_module
+ ngx_module_incs=
+ ngx_module_deps=
+ ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c
+ ngx_module_libs=
+ ngx_module_link=$HTTP_V3
+
+ . auto/module
+ fi
+
if :; then
ngx_module_name=ngx_http_range_header_filter_module
ngx_module_incs=
@@ -423,6 +436,28 @@
. auto/module
fi
+ if [ $HTTP_V3 = YES ]; then
+ USE_QUICHE=YES
+ USE_OPENSSL=YES
+ have=NGX_HTTP_V3 . auto/have
+ have=NGX_HTTP_HEADERS . auto/have
+
+ ngx_module_name=ngx_http_v3_module
+ ngx_module_incs=src/http/v3
+ ngx_module_deps="src/http/v3/ngx_http_v3.h \
+ src/http/v3/ngx_http_v3_module.h"
+ ngx_module_srcs="src/http/v3/ngx_http_v3.c \
+ src/http/v3/ngx_http_v3_module.c"
+ ngx_module_libs=
+ ngx_module_link=$HTTP_V3
+
+ . auto/module
+ fi
+
+ if [ $HTTP_V2_HPACK_ENC = YES ]; then
+ have=NGX_HTTP_V2_HPACK_ENC . auto/have
+ fi
+
if :; then
ngx_module_name=ngx_http_static_module
ngx_module_incs=
@@ -1251,6 +1286,19 @@
. auto/module
fi
+
+
+if [ $USE_QUICHE = YES ]; then
+ ngx_module_type=CORE
+ ngx_module_name=ngx_quic_module
+ ngx_module_incs=
+ ngx_module_deps=src/event/ngx_event_quic.h
+ ngx_module_srcs=src/event/ngx_event_quic.c
+ ngx_module_libs=
+ ngx_module_link=YES
+
+ . auto/module
+fi
if [ $USE_PCRE = YES ]; then
diff -uNr a/auto/options f/auto/options
--- a/auto/options 2019-09-24 23:08:48.000000000 +0800
+++ f/auto/options 2019-10-18 00:34:22.667653762 +0800
@@ -59,6 +59,8 @@
HTTP_GZIP=YES
HTTP_SSL=NO
HTTP_V2=NO
+HTTP_V2_HPACK_ENC=NO
+HTTP_V3=NO
HTTP_SSI=YES
HTTP_REALIP=NO
HTTP_XSLT=NO
@@ -147,6 +149,9 @@
USE_OPENSSL=NO
OPENSSL=NONE
+USE_QUICHE=NO
+QUICHE=NONE
+
USE_ZLIB=NO
ZLIB=NONE
ZLIB_OPT=
@@ -224,6 +229,8 @@
--with-http_ssl_module) HTTP_SSL=YES ;;
--with-http_v2_module) HTTP_V2=YES ;;
+ --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;;
+ --with-http_v3_module) HTTP_V3=YES ;;
--with-http_realip_module) HTTP_REALIP=YES ;;
--with-http_addition_module) HTTP_ADDITION=YES ;;
--with-http_xslt_module) HTTP_XSLT=YES ;;
@@ -357,6 +364,9 @@
--with-openssl=*) OPENSSL="$value" ;;
--with-openssl-opt=*) OPENSSL_OPT="$value" ;;
+ --with-quiche=*) QUICHE="$value" ;;
+ --with-quiche-opt=*) QUICHE_OPT="$value" ;;
+
--with-md5=*)
NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
$0: warning: the \"--with-md5\" option is deprecated"
@@ -439,6 +449,8 @@
--with-http_ssl_module enable ngx_http_ssl_module
--with-http_v2_module enable ngx_http_v2_module
+ --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc
+ --with-http_v3_module enable ngx_http_v3_module
--with-http_realip_module enable ngx_http_realip_module
--with-http_addition_module enable ngx_http_addition_module
--with-http_xslt_module enable ngx_http_xslt_module
diff -uNr a/src/core/ngx_connection.h f/src/core/ngx_connection.h
--- a/src/core/ngx_connection.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/core/ngx_connection.h 2019-10-18 00:32:29.362729862 +0800
@@ -79,6 +79,9 @@
unsigned deferred_accept:1;
unsigned delete_deferred:1;
unsigned add_deferred:1;
+#if (NGX_QUIC)
+ unsigned quic:1;
+#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
char *accept_filter;
#endif
@@ -156,6 +159,10 @@
ngx_udp_connection_t *udp;
+#if (NGX_QUIC)
+ ngx_quic_connection_t *quic;
+#endif
+
struct sockaddr *local_sockaddr;
socklen_t local_socklen;
diff -uNr a/src/core/ngx_core.h f/src/core/ngx_core.h
--- a/src/core/ngx_core.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/core/ngx_core.h 2019-10-18 00:32:29.362729862 +0800
@@ -82,6 +82,9 @@
#if (NGX_OPENSSL)
#include <ngx_event_openssl.h>
#endif
+#if (NGX_QUIC)
+#include <ngx_event_quic.h>
+#endif
#include <ngx_process_cycle.h>
#include <ngx_conf_file.h>
#include <ngx_module.h>
diff -uNr a/src/core/ngx_murmurhash.c f/src/core/ngx_murmurhash.c
--- a/src/core/ngx_murmurhash.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/core/ngx_murmurhash.c 2019-10-18 00:33:02.149997212 +0800
@@ -50,3 +50,63 @@
return h;
}
+
+
+uint64_t
+ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed)
+{
+ uint64_t h, k;
+
+ h = seed ^ len;
+
+ while (len >= 8) {
+ k = data[0];
+ k |= data[1] << 8;
+ k |= data[2] << 16;
+ k |= data[3] << 24;
+ k |= (uint64_t)data[4] << 32;
+ k |= (uint64_t)data[5] << 40;
+ k |= (uint64_t)data[6] << 48;
+ k |= (uint64_t)data[7] << 56;
+
+ k *= 0xc6a4a7935bd1e995ull;
+ k ^= k >> 47;
+ k *= 0xc6a4a7935bd1e995ull;
+
+ h ^= k;
+ h *= 0xc6a4a7935bd1e995ull;
+
+ data += 8;
+ len -= 8;
+ }
+
+ switch (len) {
+ case 7:
+ h ^= (uint64_t)data[6] << 48;
+ /* fall through */
+ case 6:
+ h ^= (uint64_t)data[5] << 40;
+ /* fall through */
+ case 5:
+ h ^= (uint64_t)data[4] << 32;
+ /* fall through */
+ case 4:
+ h ^= data[3] << 24;
+ /* fall through */
+ case 3:
+ h ^= data[2] << 16;
+ /* fall through */
+ case 2:
+ h ^= data[1] << 8;
+ /* fall through */
+ case 1:
+ h ^= data[0];
+ h *= 0xc6a4a7935bd1e995ull;
+ }
+
+ h ^= h >> 47;
+ h *= 0xc6a4a7935bd1e995ull;
+ h ^= h >> 47;
+
+ return h;
+}
diff -uNr a/src/core/ngx_murmurhash.h f/src/core/ngx_murmurhash.h
--- a/src/core/ngx_murmurhash.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/core/ngx_murmurhash.h 2019-10-18 00:33:02.149997212 +0800
@@ -15,5 +15,7 @@
uint32_t ngx_murmur_hash2(u_char *data, size_t len);
+uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed);
+
#endif /* _NGX_MURMURHASH_H_INCLUDED_ */
diff -uNr a/src/event/ngx_event_openssl.c f/src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/event/ngx_event_openssl.c 2019-10-18 00:34:35.837761152 +0800
@@ -1507,6 +1507,7 @@
sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
sc->buffer_size = ssl->buffer_size;
+ sc->dyn_rec = ssl->dyn_rec;
sc->session_ctx = ssl->ctx;
@@ -2359,6 +2360,41 @@
for ( ;; ) {
+ /* Dynamic record resizing:
+ We want the initial records to fit into one TCP segment
+ so we don't get TCP HoL blocking due to TCP Slow Start.
+ A connection always starts with small records, but after
+ a given amount of records sent, we make the records larger
+ to reduce header overhead.
+ After a connection has idled for a given timeout, begin
+ the process from the start. The actual parameters are
+ configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */
+
+ if (c->ssl->dyn_rec.timeout > 0 ) {
+
+ if (ngx_current_msec - c->ssl->dyn_rec_last_write >
+ c->ssl->dyn_rec.timeout)
+ {
+ buf->end = buf->start + c->ssl->dyn_rec.size_lo;
+ c->ssl->dyn_rec_records_sent = 0;
+
+ } else {
+ if (c->ssl->dyn_rec_records_sent >
+ c->ssl->dyn_rec.threshold * 2)
+ {
+ buf->end = buf->start + c->ssl->buffer_size;
+
+ } else if (c->ssl->dyn_rec_records_sent >
+ c->ssl->dyn_rec.threshold)
+ {
+ buf->end = buf->start + c->ssl->dyn_rec.size_hi;
+
+ } else {
+ buf->end = buf->start + c->ssl->dyn_rec.size_lo;
+ }
+ }
+ }
+
while (in && buf->last < buf->end && send < limit) {
if (in->buf->last_buf || in->buf->flush) {
flush = 1;
@@ -2466,6 +2502,9 @@
if (n > 0) {
+ c->ssl->dyn_rec_records_sent++;
+ c->ssl->dyn_rec_last_write = ngx_current_msec;
+
if (c->ssl->saved_read_handler) {
c->read->handler = c->ssl->saved_read_handler;
diff -uNr a/src/event/ngx_event_openssl.h f/src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/event/ngx_event_openssl.h 2019-10-18 00:34:35.837761152 +0800
@@ -64,10 +64,19 @@
#endif
+typedef struct {
+ ngx_msec_t timeout;
+ ngx_uint_t threshold;
+ size_t size_lo;
+ size_t size_hi;
+} ngx_ssl_dyn_rec_t;
+
+
struct ngx_ssl_s {
SSL_CTX *ctx;
ngx_log_t *log;
size_t buffer_size;
+ ngx_ssl_dyn_rec_t dyn_rec;
};
@@ -99,6 +108,10 @@
unsigned in_early:1;
unsigned early_preread:1;
unsigned write_blocked:1;
+
+ ngx_ssl_dyn_rec_t dyn_rec;
+ ngx_msec_t dyn_rec_last_write;
+ ngx_uint_t dyn_rec_records_sent;
};
@@ -108,7 +121,7 @@
#define NGX_SSL_DFLT_BUILTIN_SCACHE -5
-#define NGX_SSL_MAX_SESSION_SIZE 4096
+#define NGX_SSL_MAX_SESSION_SIZE 16384
typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t;
diff -uNr a/src/event/ngx_event_quic.c f/src/event/ngx_event_quic.c
--- a/src/event/ngx_event_quic.c 1970-01-01 08:00:00.000000000 +0800
+++ f/src/event/ngx_event_quic.c 2019-10-18 00:32:29.363729871 +0800
@@ -0,0 +1,570 @@
+
+/*
+ * Copyright (C) Cloudflare, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/* Limit outgoing packets to 1200 bytes. This is the minimum value allowed. */
+#define MAX_DATAGRAM_SIZE 1200
+
+/* errors */
+#define NGX_QUIC_NO_ERROR 0x0
+#define NGX_QUIC_INTERNAL_ERROR 0x1
+
+
+static void ngx_quic_read_handler(ngx_event_t *ev);
+static void ngx_quic_write_handler(ngx_event_t *ev);
+
+static void ngx_quic_handshake_completed(ngx_connection_t *c);
+
+static void ngx_quic_shutdown_handler(ngx_event_t *ev);
+
+static void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status);
+static void ngx_quic_close_connection(ngx_connection_t *c);
+
+static ngx_int_t ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf,
+ size_t len);
+
+
+static ngx_command_t ngx_quic_commands[] = {
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_quic_module_ctx = {
+ ngx_string("quic"),
+ NULL,
+ NULL
+};
+
+
+ngx_module_t ngx_quic_module = {
+ NGX_MODULE_V1,
+ &ngx_quic_module_ctx, /* module context */
+ ngx_quic_commands, /* module directives */
+ NGX_CORE_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
+};
+
+
+ngx_int_t
+ngx_quic_create_conf(ngx_quic_t *quic)
+{
+ quic->config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+ if (quic->config == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, quic->log, 0, "failed to create quic config");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf, ssize_t buf_len)
+{
+ /* Check incoming packet type, if it's not Initial we shouldn't be here. */
+ if (((buf[0] & 0x30) >> 4) != 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "packet is not quic client initial");
+ return NGX_ERROR;
+ }
+
+ /* Client Initial packets must be at least 1200 bytes. */
+ if (buf_len < QUICHE_MIN_CLIENT_INITIAL_LEN) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "quic initial packet is too short");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c)
+{
+ int rc;
+ u_char *buf;
+ size_t buf_len;
+ quiche_conn *conn;
+ static uint8_t out[MAX_DATAGRAM_SIZE];
+
+ uint8_t pkt_type;
+ uint32_t pkt_version;
+
+ uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+ size_t scid_len = sizeof(scid);
+
+ uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];
+ size_t dcid_len = sizeof(dcid);
+
+ uint8_t token[1];
+ size_t token_len = sizeof(token);
+
+ ngx_quic_connection_t *qc;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init connection");
+
+ /* Extract some fields from the client's Initial packet, which was saved
+ * into c->buffer by ngx_event_recvmsg(). */
+ buf = c->buffer->pos;
+ buf_len = ngx_buf_size(c->buffer);
+
+ rc = quiche_header_info(buf, buf_len, QUICHE_MAX_CONN_ID_LEN,
+ &pkt_version, &pkt_type,
+ scid, &scid_len, dcid, &dcid_len,
+ token, &token_len);
+ if (rc < 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "failed to parse quic header: %d", rc);
+ return NGX_ERROR;
+ }
+
+ /* Version mismatch, do version negotiation. */
+ if (pkt_version != QUICHE_PROTOCOL_VERSION) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic version negotiation");
+
+ ssize_t written = quiche_negotiate_version(scid, scid_len,
+ dcid, dcid_len,
+ out, sizeof(out));
+
+ if (written < 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "failed to create quic vneg packet: %d", written);
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_send_udp_packet(c, out, written) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ return NGX_DONE;
+ }
+
+ /* Initialize source connection ID with some random bytes. */
+ RAND_bytes(scid, sizeof(scid));
+
+#if (NGX_DEBUG)
+ {
+ uint8_t dcid_hex[QUICHE_MAX_CONN_ID_LEN * 2],
+ scid_hex[QUICHE_MAX_CONN_ID_LEN * 2];
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "new quic connection dcid:%*.s new_scid:%*.s",
+ ngx_hex_dump(dcid_hex, dcid, sizeof(dcid)) - dcid_hex, dcid_hex,
+ ngx_hex_dump(scid_hex, scid, sizeof(scid)) - scid_hex, scid_hex);
+ }
+#endif
+
+ conn = quiche_conn_new_with_tls(scid, sizeof(scid), NULL, 0, quic->config,
+ c->ssl->connection, true);
+ if (conn == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create quic connection");
+ return NGX_ERROR;
+ }
+
+ qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
+ if (qc == NULL) {
+ return NGX_ERROR;
+ }
+
+ qc->handler = NULL;
+
+ qc->conn = conn;
+
+ c->quic = qc;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_handshake(ngx_connection_t *c)
+{
+ u_char *buf;
+ size_t buf_len;
+ ssize_t done;
+
+ c->log->action = "processing QUIC connection";
+
+ /* Process the client's Initial packet, which was saved into c->buffer by
+ * ngx_event_recvmsg(). */
+ buf = c->buffer->pos;
+ buf_len = ngx_buf_size(c->buffer);
+
+ done = quiche_conn_recv(c->quic->conn, buf, buf_len);
+
+ if ((done < 0) && (done != QUICHE_ERR_DONE)) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "failed to process quic packet: %d", done);
+ return NGX_ERROR;
+ }
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ c->read->handler = ngx_quic_read_handler;
+ c->write->handler = ngx_quic_write_handler;
+
+ ngx_post_event(c->write, &ngx_posted_events);
+
+ return NGX_AGAIN;
+}
+
+
+static void
+ngx_quic_read_handler(ngx_event_t *rev)
+{
+ int n;
+ static uint8_t buf[65535];
+ ngx_connection_t *c;
+
+ c = rev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic read handler");
+
+ if (rev->timedout) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic connection timed out");
+
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+
+ for (;;) {
+ n = c->recv(c, buf, sizeof(buf));
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n == NGX_ERROR) {
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+
+ ssize_t done = quiche_conn_recv(c->quic->conn, buf, n);
+
+ if (done == QUICHE_ERR_DONE) {
+ break;
+ }
+
+ if (done < 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "failed to process quic packet: %d", done);
+
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+ }
+
+ if (quiche_conn_is_established(c->quic->conn)) {
+ if (!c->ssl->handshaked) {
+ ngx_quic_handshake_completed(c);
+ }
+
+ if ((c->quic == NULL) || (c->quic->handler == NULL)) {
+ return;
+ }
+
+ /* Notify application layer that there might be stream data to read. */
+ c->quic->handler(c);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done reading");
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+
+ ngx_post_event(c->write, &ngx_posted_events);
+}
+
+
+static void
+ngx_quic_write_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+ ngx_msec_t expiry;
+ static uint8_t out[MAX_DATAGRAM_SIZE];
+
+ c = wev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic write handler");
+
+ if (wev->timedout) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired");
+
+ quiche_conn_on_timeout(c->quic->conn);
+ }
+
+ if (quiche_conn_is_closed(c->quic->conn)) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic connection is closed");
+
+ ngx_quic_finalize_connection(c, NGX_QUIC_NO_ERROR);
+ return;
+ }
+
+ for (;;) {
+ ssize_t written = quiche_conn_send(c->quic->conn, out, sizeof(out));
+
+ if (written == QUICHE_ERR_DONE) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic done writing");
+ break;
+ }
+
+ if (written < 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "failed to create quic packet: %d", written);
+
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+
+ if (ngx_quic_send_udp_packet(c, out, written) == NGX_ERROR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "failed to send quic packet");
+
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+ }
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ ngx_quic_finalize_connection(c, NGX_QUIC_INTERNAL_ERROR);
+ return;
+ }
+
+ expiry = quiche_conn_timeout_as_millis(c->quic->conn);
+ expiry = ngx_max(expiry, 1);
+
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+
+ /* quiche_conn_timeout_as_millis() will return UINT64_MAX when the timer
+ * should be unset (this would be equvalent to returning Option::None in
+ * Rust). To avoid overflow we need to explicitly check for this value. */
+ if (expiry != UINT64_MAX) {
+ ngx_add_timer(wev, expiry);
+ }
+}
+
+
+static void
+ngx_quic_handshake_completed(ngx_connection_t *c)
+{
+#if (NGX_DEBUG)
+ {
+ char buf[129], *s, *d;
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ const
+#endif
+ SSL_CIPHER *cipher;
+
+ cipher = SSL_get_current_cipher(c->ssl->connection);
+
+ if (cipher) {
+ SSL_CIPHER_description(cipher, &buf[1], 128);
+
+ for (s = &buf[1], d = buf; *s; s++) {
+ if (*s == ' ' && *d == ' ') {
+ continue;
+ }
+
+ if (*s == LF || *s == CR) {
+ continue;
+ }
+
+ *++d = *s;
+ }
+
+ if (*d != ' ') {
+ d++;
+ }
+
+ *d = '\0';
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "QUIC: %s, cipher: \"%s\"",
+ SSL_get_version(c->ssl->connection), &buf[1]);
+
+ if (SSL_session_reused(c->ssl->connection)) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic reused session");
+ }
+
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic no shared ciphers");
+ }
+ }
+#endif
+
+ ngx_del_timer(c->read);
+
+ c->ssl->handshaked = 1;
+
+ /* Notify application layer that the handshake is complete. */
+ c->ssl->handler(c);
+}
+
+
+ngx_int_t
+ngx_quic_shutdown(ngx_connection_t *c)
+{
+ if (!quiche_conn_is_closed(c->quic->conn)) {
+ /* We shouldn't free the connection state yet, as we need to wait for
+ * the draining timeout to expire. Setup event handlers such that we
+ * will try again when that happens (or when another event is
+ * triggered). */
+ c->read->handler = ngx_quic_shutdown_handler;
+ c->write->handler = ngx_quic_shutdown_handler;
+
+ /* We need to flush any remaining frames to the client (including
+ * CONNECTION_CLOSE), so invoke the write handler. This also takes
+ * care of setting up the draining timer. */
+ ngx_quic_write_handler(c->write);
+
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "free quic connection");
+
+ quiche_conn_free(c->quic->conn);
+
+ c->quic = NULL;
+ c->ssl = NULL;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_quic_shutdown_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_connection_handler_pt handler;
+
+ c = ev->data;
+ handler = c->quic->handler;
+
+ if (ev->timedout) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alarm fired");
+
+ quiche_conn_on_timeout(c->quic->conn);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic shutdown handler");
+
+ if (ngx_quic_shutdown(c) == NGX_AGAIN) {
+ return;
+ }
+
+ handler(c);
+}
+
+
+static void
+ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t status)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "finalize quic connection: %d", c->fd);
+
+ c->error = 1;
+
+ quiche_conn_close(c->quic->conn, false, status, NULL, 0);
+
+ /* Notify the application layer that the connection is in an error
+ * state and will be closed. */
+ if (c->quic->handler != NULL) {
+ c->quic->handler(c);
+ return;
+ }
+
+ ngx_quic_close_connection(c);
+}
+
+
+static void
+ngx_quic_close_connection(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "close quic connection: %d", c->fd);
+
+ if (c->quic) {
+ if (ngx_quic_shutdown(c) == NGX_AGAIN) {
+ c->quic->handler = ngx_quic_close_connection;
+ return;
+ }
+ }
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+void
+ngx_quic_cleanup_ctx(void *data)
+{
+ ngx_quic_t *quic = data;
+
+ quiche_config_free(quic->config);
+}
+
+
+static ngx_int_t
+ngx_quic_send_udp_packet(ngx_connection_t *c, uint8_t *buf, size_t len)
+{
+ ngx_buf_t out_buf = {0};
+ ngx_chain_t out_chain = {0};
+
+ /* The send_chain() API takes an ngx_chain_t parameter instead of a simple
+ * buffer, so we need to initialize the chain such that it contains only a
+ * single buffer.
+ *
+ * The c->send_chain() call is required (instead of just c->send()) because
+ * it uses the sendmsg(2) syscall (instead of sendto(2)), which allows us to
+ * specify the correct source IP address for the connection. */
+
+ out_buf.start = out_buf.pos = buf;
+ out_buf.end = out_buf.last = buf + len;
+ out_buf.memory = 1;
+ out_buf.flush = 1;
+
+ out_chain.buf = &out_buf;
+ out_chain.next = NULL;
+
+ if (c->send_chain(c, &out_chain, 0) == NGX_CHAIN_ERROR) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
diff -uNr a/src/event/ngx_event_quic.h f/src/event/ngx_event_quic.h
--- a/src/event/ngx_event_quic.h 1970-01-01 08:00:00.000000000 +0800
+++ f/src/event/ngx_event_quic.h 2019-10-18 00:32:29.364729879 +0800
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (C) Cloudflare, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
+#define _NGX_EVENT_QUIC_H_INCLUDED_
+
+
+#include <stdbool.h>
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#include <quiche.h>
+
+typedef struct ngx_quic_s ngx_quic_t;
+typedef struct ngx_quic_connection_s ngx_quic_connection_t;
+
+struct ngx_quic_s {
+ quiche_config *config;
+ ngx_log_t *log;
+};
+
+struct ngx_quic_connection_s {
+ quiche_conn *conn;
+
+ ngx_connection_handler_pt handler;
+};
+
+
+ngx_int_t ngx_quic_create_conf(ngx_quic_t *quic);
+
+ngx_int_t ngx_quic_validate_initial(ngx_event_t *ev, u_char *buf,
+ ssize_t buf_len);
+
+ngx_int_t ngx_quic_create_connection(ngx_quic_t *quic, ngx_connection_t *c);
+
+ngx_int_t ngx_quic_create_ssl_connection(ngx_ssl_t *ssl, ngx_connection_t *c,
+ ngx_uint_t flags);
+
+ngx_int_t ngx_quic_handshake(ngx_connection_t *c);
+
+ngx_int_t ngx_quic_shutdown(ngx_connection_t *c);
+
+void ngx_quic_cleanup_ctx(void *data);
+
+#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
diff -uNr a/src/event/ngx_event_udp.c f/src/event/ngx_event_udp.c
--- a/src/event/ngx_event_udp.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/event/ngx_event_udp.c 2019-10-18 00:32:29.364729879 +0800
@@ -276,6 +276,14 @@
(void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
#endif
+#if (NGX_QUIC)
+ if (ls->quic) {
+ if (ngx_quic_validate_initial(ev, buffer, n) != NGX_OK) {
+ goto next;
+ }
+ }
+#endif
+
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
diff -uNr a/src/http/modules/ngx_http_ssl_module.c f/src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/modules/ngx_http_ssl_module.c 2019-10-18 00:34:35.839761169 +0800
@@ -249,6 +249,41 @@
offsetof(ngx_http_ssl_srv_conf_t, early_data),
NULL },
+ { ngx_string("ssl_dyn_rec_enable"),
+ 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, dyn_rec_enable),
+ NULL },
+
+ { ngx_string("ssl_dyn_rec_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout),
+ NULL },
+
+ { ngx_string("ssl_dyn_rec_size_lo"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo),
+ NULL },
+
+ { ngx_string("ssl_dyn_rec_size_hi"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi),
+ NULL },
+
+ { ngx_string("ssl_dyn_rec_threshold"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold),
+ NULL },
+
ngx_null_command
};
@@ -371,7 +406,7 @@
#if (NGX_DEBUG)
unsigned int i;
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_V3)
ngx_http_connection_t *hc;
#endif
#if (NGX_HTTP_V2 || NGX_DEBUG)
@@ -388,9 +423,11 @@
}
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_V3)
hc = c->data;
+#endif
+#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
srv =
(unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
@@ -398,6 +435,13 @@
} else
#endif
+#if (NGX_HTTP_V3)
+ if (hc->addr_conf->quic) {
+ srv = (unsigned char *) QUICHE_H3_APPLICATION_PROTOCOL;
+ srvlen = sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1;
+
+ } else
+#endif
{
srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -580,6 +624,11 @@
sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
sscf->stapling = NGX_CONF_UNSET;
sscf->stapling_verify = NGX_CONF_UNSET;
+ sscf->dyn_rec_enable = NGX_CONF_UNSET;
+ sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC;
+ sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE;
+ sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE;
+ sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT;
return sscf;
}
@@ -647,6 +696,20 @@
ngx_conf_merge_str_value(conf->stapling_responder,
prev->stapling_responder, "");
+ ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0);
+ ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout,
+ 1000);
+ /* Default sizes for the dynamic record sizes are defined to fit maximal
+ TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi:
+ 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */
+ ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo,
+ 1369);
+ /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */
+ ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi,
+ 4229);
+ ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold,
+ 40);
+
conf->ssl.log = cf->log;
if (conf->enable) {
@@ -857,6 +920,28 @@
return NGX_CONF_ERROR;
}
+ if (conf->dyn_rec_enable) {
+ conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout;
+ conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold;
+
+ if (conf->buffer_size > conf->dyn_rec_size_lo) {
+ conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo;
+
+ } else {
+ conf->ssl.dyn_rec.size_lo = conf->buffer_size;
+ }
+
+ if (conf->buffer_size > conf->dyn_rec_size_hi) {
+ conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi;
+
+ } else {
+ conf->ssl.dyn_rec.size_hi = conf->buffer_size;
+ }
+
+ } else {
+ conf->ssl.dyn_rec.timeout = 0;
+ }
+
return NGX_CONF_OK;
}
diff -uNr a/src/http/modules/ngx_http_ssl_module.h f/src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/modules/ngx_http_ssl_module.h 2019-10-18 00:34:35.839761169 +0800
@@ -61,6 +61,12 @@
u_char *file;
ngx_uint_t line;
+
+ ngx_flag_t dyn_rec_enable;
+ ngx_msec_t dyn_rec_timeout;
+ size_t dyn_rec_size_lo;
+ size_t dyn_rec_size_hi;
+ ngx_uint_t dyn_rec_threshold;
} ngx_http_ssl_srv_conf_t;
diff -uNr a/src/http/ngx_http.c f/src/http/ngx_http.c
--- a/src/http/ngx_http.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http.c 2019-10-18 00:32:29.366729895 +0800
@@ -1141,6 +1141,7 @@
ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_listen_opt_t *lsopt)
{
+ int t;
in_port_t p;
ngx_uint_t i;
struct sockaddr *sa;
@@ -1159,11 +1160,13 @@
sa = lsopt->sockaddr;
p = ngx_inet_get_port(sa);
+ t = lsopt->quic ? SOCK_DGRAM : SOCK_STREAM;
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
- if (p != port[i].port || sa->sa_family != port[i].family) {
+ if (p != port[i].port || sa->sa_family != port[i].family
+ || t != port[i].type) {
continue;
}
@@ -1182,6 +1185,7 @@
port->family = sa->sa_family;
port->port = p;
port->addrs.elts = NULL;
+ port->type = t;
return ngx_http_add_address(cf, cscf, port, lsopt);
}
@@ -1199,6 +1203,9 @@
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
+#if (NGX_HTTP_V3)
+ ngx_uint_t quic;
+#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@@ -1234,6 +1241,9 @@
#if (NGX_HTTP_V2)
http2 = lsopt->http2 || addr[i].opt.http2;
#endif
+#if (NGX_HTTP_V3)
+ quic = lsopt->quic || addr[i].opt.quic;
+#endif
if (lsopt->set) {
@@ -1270,6 +1280,9 @@
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
+#if (NGX_HTTP_V3)
+ addr[i].opt.quic = quic;
+#endif
return NGX_OK;
}
@@ -1688,6 +1701,12 @@
break;
}
+#if (NGX_HTTP_V3)
+ if (addr[i].opt.quic) {
+ ls->type = SOCK_DGRAM;
+ }
+#endif
+
addr++;
last--;
}
@@ -1770,6 +1789,12 @@
ls->reuseport = addr->opt.reuseport;
#endif
+#if (NGX_HTTP_V3)
+ ls->quic = addr->opt.quic;
+
+ ls->wildcard = addr->opt.wildcard;
+#endif
+
return ls;
}
@@ -1803,6 +1828,9 @@
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+#if (NGX_HTTP_V3)
+ addrs[i].conf.quic = addr[i].opt.quic;
+#endif
if (addr[i].hash.buckets == NULL
&& (addr[i].wc_head == NULL
@@ -1868,6 +1896,9 @@
addrs6[i].conf.http2 = addr[i].opt.http2;
#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+#if (NGX_HTTP_V3)
+ addrs6[i].conf.quic = addr[i].opt.quic;
+#endif
if (addr[i].hash.buckets == NULL
&& (addr[i].wc_head == NULL
diff -uNr a/src/http/ngx_http_core_module.c f/src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http_core_module.c 2019-10-18 00:32:29.368729911 +0800
@@ -4103,6 +4103,13 @@
continue;
}
+#if (NGX_HTTP_V3)
+ if (ngx_strcmp(value[n].data, "quic") == 0) {
+ lsopt.quic = 1;
+ continue;
+ }
+#endif
+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[n]);
return NGX_CONF_ERROR;
diff -uNr a/src/http/ngx_http_core_module.h f/src/http/ngx_http_core_module.h
--- a/src/http/ngx_http_core_module.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http_core_module.h 2019-10-18 00:32:29.368729911 +0800
@@ -82,6 +82,7 @@
unsigned reuseport:1;
unsigned so_keepalive:2;
unsigned proxy_protocol:1;
+ unsigned quic:1;
int backlog;
int rcvbuf;
@@ -238,6 +239,7 @@
unsigned ssl:1;
unsigned http2:1;
unsigned proxy_protocol:1;
+ unsigned quic:1;
};
@@ -268,6 +270,7 @@
ngx_int_t family;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
+ ngx_int_t type;
} ngx_http_conf_port_t;
diff -uNr a/src/http/ngx_http.h f/src/http/ngx_http.h
--- a/src/http/ngx_http.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http.h 2019-10-18 00:32:29.366729895 +0800
@@ -20,6 +20,7 @@
typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
typedef struct ngx_http_chunked_s ngx_http_chunked_t;
typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t;
+typedef struct ngx_http_v3_stream_s ngx_http_v3_stream_t;
typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
@@ -38,6 +39,9 @@
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
+#if (NGX_HTTP_V3)
+#include <ngx_http_v3.h>
+#endif
#if (NGX_HTTP_CACHE)
#include <ngx_http_cache.h>
#endif
diff -uNr a/src/http/ngx_http_request_body.c f/src/http/ngx_http_request_body.c
--- a/src/http/ngx_http_request_body.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http_request_body.c 2019-10-18 00:32:29.371729936 +0800
@@ -85,6 +85,13 @@
}
#endif
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ rc = ngx_http_v3_read_request_body(r);
+ goto done;
+ }
+#endif
+
preread = r->header_in->last - r->header_in->pos;
if (preread) {
@@ -226,6 +233,18 @@
}
#endif
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ rc = ngx_http_v3_read_unbuffered_request_body(r);
+
+ if (rc == NGX_OK) {
+ r->reading_body = 0;
+ }
+
+ return rc;
+ }
+#endif
+
if (r->connection->read->timedout) {
r->connection->timedout = 1;
return NGX_HTTP_REQUEST_TIME_OUT;
@@ -525,6 +544,13 @@
}
#endif
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ r->qstream->skip_data = 1;
+ return NGX_OK;
+ }
+#endif
+
if (ngx_http_test_expect(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
@@ -809,6 +835,9 @@
#if (NGX_HTTP_V2)
|| r->stream != NULL
#endif
+#if (NGX_HTTP_V3)
+ || r->qstream != NULL
+#endif
)
{
return NGX_OK;
diff -uNr a/src/http/ngx_http_request.c f/src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http_request.c 2019-10-18 00:32:29.370729928 +0800
@@ -64,6 +64,10 @@
static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
#endif
+#if (NGX_HTTP_V3)
+static void ngx_http_quic_handshake(ngx_event_t *rev);
+#endif
+
static char *ngx_http_client_errors[] = {
@@ -349,6 +353,18 @@
c->log->action = "reading PROXY protocol";
}
+#if (NGX_HTTP_V3)
+ if (hc->addr_conf->quic) {
+ hc->quic = 1;
+
+ /* We already have a UDP packet in the connection buffer, so we don't
+ * need to wait for another read event to kick-off the handshake. */
+ ngx_add_timer(rev, c->listening->post_accept_timeout);
+ ngx_http_quic_handshake(rev);
+ return;
+ }
+#endif
+
if (rev->ready) {
/* the deferred accept(), iocp */
@@ -797,7 +813,7 @@
c->ssl->no_wait_shutdown = 1;
-#if (NGX_HTTP_V2 \
+#if ((NGX_HTTP_V2 || NGX_HTTP_V3) \
&& (defined TLSEXT_TYPE_application_layer_protocol_negotiation \
|| defined TLSEXT_TYPE_next_proto_neg))
{
@@ -807,7 +823,7 @@
hc = c->data;
- if (hc->addr_conf->http2) {
+ if (hc->addr_conf->http2 || hc->addr_conf->quic) {
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
@@ -822,11 +838,29 @@
SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
#endif
+ }
+
+#if (NGX_HTTP_V2)
+ if (hc->addr_conf->http2) {
if (len == 2 && data[0] == 'h' && data[1] == '2') {
ngx_http_v2_init(c->read);
return;
}
}
+#endif
+
+#if (NGX_HTTP_V3)
+ if (hc->addr_conf->quic) {
+ if (len >= 2 && data[0] == 'h' && data[1] == '3') {
+ ngx_http_v3_init(c->read);
+ return;
+ }
+
+ ngx_http_close_connection(c);
+ return;
+ }
+#endif
+
}
#endif
@@ -1033,6 +1067,68 @@
#endif
+#if (NGX_HTTP_V3)
+
+static void
+ngx_http_quic_handshake(ngx_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_connection_t *hc;
+ ngx_http_v3_srv_conf_t *qscf;
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ c = rev->data;
+ hc = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http check quic handshake");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (c->close) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "https quic handshake");
+
+ sscf = ngx_http_get_module_srv_conf(hc->conf_ctx,
+ ngx_http_ssl_module);
+
+ if (ngx_ssl_create_connection(&sscf->ssl, c, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ qscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
+
+ if (ngx_quic_create_connection(&qscf->quic, c) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ rc = ngx_quic_handshake(c);
+
+ if (rc == NGX_AGAIN) {
+
+ if (!rev->timer_set) {
+ ngx_add_timer(rev, c->listening->post_accept_timeout);
+ }
+
+ c->ssl->handler = ngx_http_ssl_handshake_handler;
+ return;
+ }
+
+ ngx_http_ssl_handshake_handler(c);
+}
+
+#endif
+
static void
ngx_http_process_request_line(ngx_event_t *rev)
@@ -2687,6 +2783,13 @@
}
#endif
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+#endif
+
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->main->count != 1) {
@@ -2896,6 +2999,19 @@
#endif
+#if (NGX_HTTP_V3)
+
+ if (r->qstream) {
+ if (c->error) {
+ err = 0;
+ goto closed;
+ }
+
+ return;
+ }
+
+#endif
+
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
@@ -3563,7 +3679,15 @@
}
#endif
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ ngx_http_v3_close_stream(r->qstream, rc);
+ return;
+ }
+#endif
+
ngx_http_free_request(r, rc);
+
ngx_http_close_connection(c);
}
@@ -3684,6 +3808,17 @@
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"close http connection: %d", c->fd);
+#if (NGX_HTTP_V3)
+
+ if (c->quic) {
+ if (ngx_quic_shutdown(c) == NGX_AGAIN) {
+ c->quic->handler = ngx_http_close_connection;
+ return;
+ }
+ }
+
+#endif
+
#if (NGX_HTTP_SSL)
if (c->ssl) {
diff -uNr a/src/http/ngx_http_request.h f/src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http_request.h 2019-10-18 00:32:29.370729928 +0800
@@ -24,6 +24,7 @@
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
#define NGX_HTTP_VERSION_20 2000
+#define NGX_HTTP_VERSION_3 3000
#define NGX_HTTP_UNKNOWN 0x0001
#define NGX_HTTP_GET 0x0002
@@ -323,6 +324,7 @@
ngx_chain_t *free;
unsigned ssl:1;
+ unsigned quic:1;
unsigned proxy_protocol:1;
} ngx_http_connection_t;
@@ -445,6 +447,7 @@
ngx_http_connection_t *http_connection;
ngx_http_v2_stream_t *stream;
+ ngx_http_v3_stream_t *qstream;
ngx_http_log_handler_pt log_handler;
diff -uNr a/src/http/ngx_http_upstream.c f/src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/ngx_http_upstream.c 2019-10-18 00:32:29.373729952 +0800
@@ -526,6 +526,13 @@
}
#endif
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ ngx_http_upstream_init_request(r);
+ return;
+ }
+#endif
+
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
@@ -1347,6 +1354,12 @@
return;
}
#endif
+
+#if (NGX_HTTP_V3)
+ if (r->qstream) {
+ return;
+ }
+#endif
#if (NGX_HAVE_KQUEUE)
diff -uNr a/src/http/v2/ngx_http_v2.c f/src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/v2/ngx_http_v2.c 2019-10-18 00:33:02.150997220 +0800
@@ -270,6 +270,8 @@
h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE;
+ h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE;
+
h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module);
h2c->concurrent_pushes = h2scf->concurrent_pushes;
@@ -2092,6 +2094,14 @@
case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING:
h2c->table_update = 1;
+
+ if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) {
+ h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE;
+ } else {
+ h2c->max_hpack_table_size = value;
+ }
+
+ h2c->indicate_resize = 1;
break;
default:
diff -uNr a/src/http/v2/ngx_http_v2_encode.c f/src/http/v2/ngx_http_v2_encode.c
--- a/src/http/v2/ngx_http_v2_encode.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/v2/ngx_http_v2_encode.c 2019-10-18 00:33:02.151997229 +0800
@@ -10,7 +10,7 @@
#include <ngx_http.h>
-static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
+u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
ngx_uint_t value);
@@ -40,7 +40,7 @@
}
-static u_char *
+u_char *
ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
{
if (value < prefix) {
diff -uNr a/src/http/v2/ngx_http_v2_filter_module.c f/src/http/v2/ngx_http_v2_filter_module.c
--- a/src/http/v2/ngx_http_v2_filter_module.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/v2/ngx_http_v2_filter_module.c 2019-10-18 00:33:02.151997229 +0800
@@ -23,10 +23,53 @@
#define ngx_http_v2_literal_size(h) \
(ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1)
+#define ngx_http_v2_indexed(i) (128 + (i))
+#define ngx_http_v2_inc_indexed(i) (64 + (i))
+
+#define NGX_HTTP_V2_ENCODE_RAW 0
+#define NGX_HTTP_V2_ENCODE_HUFF 0x80
+
+#define NGX_HTTP_V2_AUTHORITY_INDEX 1
+#define NGX_HTTP_V2_METHOD_GET_INDEX 2
+#define NGX_HTTP_V2_PATH_INDEX 4
+
+#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6
+#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7
+
+#define NGX_HTTP_V2_STATUS_INDEX 8
+#define NGX_HTTP_V2_STATUS_200_INDEX 8
+#define NGX_HTTP_V2_STATUS_204_INDEX 9
+#define NGX_HTTP_V2_STATUS_206_INDEX 10
+#define NGX_HTTP_V2_STATUS_304_INDEX 11
+#define NGX_HTTP_V2_STATUS_400_INDEX 12
+#define NGX_HTTP_V2_STATUS_404_INDEX 13
+#define NGX_HTTP_V2_STATUS_500_INDEX 14
+
+#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16
+#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17
+#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28
+#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31
+#define NGX_HTTP_V2_DATE_INDEX 33
+#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44
+#define NGX_HTTP_V2_LOCATION_INDEX 46
+#define NGX_HTTP_V2_SERVER_INDEX 54
+#define NGX_HTTP_V2_USER_AGENT_INDEX 58
+#define NGX_HTTP_V2_VARY_INDEX 59
#define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1
+static const struct {
+ u_char *name;
+ u_char const len;
+} push_header[] = {
+ { (u_char*)":authority" , 10 },
+ { (u_char*)"accept-encoding" , 15 },
+ { (u_char*)"accept-language" , 15 },
+ { (u_char*)"user-agent" , 10 }
+};
+
+
typedef struct {
ngx_str_t name;
u_char index;
@@ -155,11 +198,9 @@
#endif
static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER);
- static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)];
static size_t nginx_ver_build_len =
ngx_http_v2_literal_size(NGINX_VER_BUILD);
- static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)];
stream = r->stream;
@@ -435,7 +476,7 @@
}
tmp = ngx_palloc(r->pool, tmp_len);
- pos = ngx_pnalloc(r->pool, len);
+ pos = ngx_pnalloc(r->pool, len + 15 + 1);
if (pos == NULL || tmp == NULL) {
return NGX_ERROR;
@@ -443,11 +484,16 @@
start = pos;
- if (h2c->table_update) {
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 table size update: 0");
- *pos++ = (1 << 5) | 0;
- h2c->table_update = 0;
+ h2c = r->stream->connection;
+
+ if (h2c->indicate_resize) {
+ *pos = 32;
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5),
+ h2c->max_hpack_table_size);
+ h2c->indicate_resize = 0;
+#if (NGX_HTTP_V2_HPACK_ENC)
+ ngx_http_v2_table_resize(h2c);
+#endif
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
@@ -458,67 +504,28 @@
*pos++ = status;
} else {
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
- *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
- pos = ngx_sprintf(pos, "%03ui", r->headers_out.status);
+ ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status);
+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status",
+ sizeof(":status") - 1, pos + 8, 3, tmp);
}
if (r->headers_out.server == NULL) {
-
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"server: %s\"",
- NGINX_VER);
-
- } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"server: %s\"",
- NGINX_VER_BUILD);
-
- } else {
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"server: nginx\"");
- }
-
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
-
- if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
- if (nginx_ver[0] == '\0') {
- p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER,
- sizeof(NGINX_VER) - 1, tmp);
- nginx_ver_len = p - nginx_ver;
- }
-
- pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len);
+ pos = ngx_http_v2_write_header_str("server", NGINX_VER);
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
- if (nginx_ver_build[0] == '\0') {
- p = ngx_http_v2_write_value(nginx_ver_build,
- (u_char *) NGINX_VER_BUILD,
- sizeof(NGINX_VER_BUILD) - 1, tmp);
- nginx_ver_build_len = p - nginx_ver_build;
- }
-
- pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len);
+ pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD);
} else {
- pos = ngx_cpymem(pos, nginx, sizeof(nginx));
+ pos = ngx_http_v2_write_header_str("server", "nginx");
}
}
if (r->headers_out.date == NULL) {
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"date: %V\"",
- &ngx_cached_http_time);
-
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
- pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data,
- ngx_cached_http_time.len, tmp);
+ pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time);
}
if (r->headers_out.content_type.len) {
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
-
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
@@ -544,64 +551,36 @@
r->headers_out.content_type.data = p - len;
}
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"content-type: %V\"",
- &r->headers_out.content_type);
-
- pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data,
- r->headers_out.content_type.len, tmp);
+ pos = ngx_http_v2_write_header_tbl("content-type",
+ r->headers_out.content_type);
}
if (r->headers_out.content_length == NULL
&& r->headers_out.content_length_n >= 0)
{
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"content-length: %O\"",
- r->headers_out.content_length_n);
-
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX);
-
- p = pos;
- pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n);
- *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1);
+ p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n);
+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length",
+ sizeof("content-length") - 1, pos + 15,
+ p - (pos + 15), tmp);
}
if (r->headers_out.last_modified == NULL
&& r->headers_out.last_modified_time != -1)
{
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
-
- ngx_http_time(pos, r->headers_out.last_modified_time);
+ ngx_http_time(pos + 14, r->headers_out.last_modified_time);
len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1;
-
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"last-modified: %*s\"",
- len, pos);
-
- /*
- * Date will always be encoded using huffman in the temporary buffer,
- * so it's safe here to use src and dst pointing to the same address.
- */
- pos = ngx_http_v2_write_value(pos, pos, len, tmp);
+ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified",
+ sizeof("last-modified") - 1, pos + 14,
+ len, tmp);
}
if (r->headers_out.location && r->headers_out.location->value.len) {
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"location: %V\"",
- &r->headers_out.location->value);
-
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
- pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data,
- r->headers_out.location->value.len, tmp);
+ pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value);
}
#if (NGX_HTTP_GZIP)
if (r->gzip_vary) {
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"vary: Accept-Encoding\"");
-
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
- pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding));
+ pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding");
}
#endif
@@ -624,23 +603,10 @@
continue;
}
-#if (NGX_DEBUG)
- if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
- ngx_strlow(tmp, header[i].key.data, header[i].key.len);
-
- ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 output header: \"%*s: %V\"",
- header[i].key.len, tmp, &header[i].value);
- }
-#endif
-
- *pos++ = 0;
-
- pos = ngx_http_v2_write_name(pos, header[i].key.data,
- header[i].key.len, tmp);
+ pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data,
+ header[i].key.len, header[i].value.data,
+ header[i].value.len, tmp);
- pos = ngx_http_v2_write_value(pos, header[i].value.data,
- header[i].value.len, tmp);
}
fin = r->header_only
@@ -998,6 +964,7 @@
for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
len += binary[i].len;
+ len += push_header[i].len + 1;
}
pos = ngx_pnalloc(r->pool, len);
@@ -1007,12 +974,17 @@
start = pos;
- if (h2c->table_update) {
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
- "http2 table size update: 0");
- *pos++ = (1 << 5) | 0;
- h2c->table_update = 0;
- }
+ h2c = r->stream->connection;
+
+ if (h2c->indicate_resize) {
+ *pos = 32;
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5),
+ h2c->max_hpack_table_size);
+ h2c->indicate_resize = 0;
+#if (NGX_HTTP_V2_HPACK_ENC)
+ ngx_http_v2_table_resize(h2c);
+#endif
+ }
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":method: GET\"");
@@ -1022,8 +994,7 @@
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":path: %V\"", path);
- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
- pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp);
+ pos = ngx_http_v2_write_header_pot(":path", path);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":scheme: %V\"", &r->schema);
@@ -1048,11 +1019,15 @@
continue;
}
+ value = &(*h)->value;
+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \"%V: %V\"",
&ph[i].name, &(*h)->value);
- pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
+ pos = ngx_http_v2_write_header(h2c, pos,
+ push_header[i].name, push_header[i].len, value->data, value->len,
+ tmp);
}
frame = ngx_http_v2_create_push_frame(r, start, pos);
diff -uNr a/src/http/v2/ngx_http_v2.h f/src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/v2/ngx_http_v2.h 2019-10-18 00:33:02.151997229 +0800
@@ -52,6 +52,14 @@
#define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1)
#define NGX_HTTP_V2_DEFAULT_WINDOW 65535
+#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */
+#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128)
+#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */
+#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */
+
+#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096
+#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */
+
#define NGX_HTTP_V2_DEFAULT_WEIGHT 16
@@ -115,6 +123,46 @@
} ngx_http_v2_hpack_t;
+#if (NGX_HTTP_V2_HPACK_ENC)
+typedef struct {
+ uint64_t hash_val;
+ uint32_t index;
+ uint16_t pos;
+ uint16_t klen, vlen;
+ uint16_t size;
+ uint16_t next;
+} ngx_http_v2_hpack_enc_entry_t;
+
+
+typedef struct {
+ uint64_t hash_val;
+ uint32_t index;
+ uint16_t pos;
+ uint16_t klen;
+} ngx_http_v2_hpack_name_entry_t;
+
+
+typedef struct {
+ size_t size; /* size as defined in RFC 7541 */
+ uint32_t top; /* the last entry */
+ uint32_t pos;
+ uint16_t n_elems; /* number of elements */
+ uint16_t base; /* index of the oldest entry */
+ uint16_t last; /* index of the newest entry */
+
+ /* hash table for dynamic entries, instead using a generic hash table,
+ which would be too slow to process a significant amount of headers,
+ this table is not determenistic, and might ocasionally fail to insert
+ a value, at the cost of slightly worse compression, but significantly
+ faster performance */
+ ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ];
+ ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ];
+ u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE +
+ HPACK_ENC_MAX_ENTRY];
+} ngx_http_v2_hpack_enc_t;
+#endif
+
+
struct ngx_http_v2_connection_s {
ngx_connection_t *connection;
ngx_http_connection_t *http_connection;
@@ -136,6 +184,8 @@
size_t frame_size;
+ size_t max_hpack_table_size;
+
ngx_queue_t waiting;
ngx_http_v2_state_t state;
@@ -163,6 +213,11 @@
unsigned blocked:1;
unsigned goaway:1;
unsigned push_disabled:1;
+ unsigned indicate_resize:1;
+
+#if (NGX_HTTP_V2_HPACK_ENC)
+ ngx_http_v2_hpack_enc_t hpack_enc;
+#endif
};
@@ -206,6 +261,8 @@
ngx_array_t *cookies;
+ size_t header_limit;
+
ngx_pool_t *pool;
unsigned waiting:1;
@@ -418,4 +475,35 @@
u_char *tmp, ngx_uint_t lower);
+u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
+ u_char *tmp, ngx_uint_t lower);
+
+u_char *
+ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value);
+
+#define ngx_http_v2_write_name(dst, src, len, tmp) \
+ ngx_http_v2_string_encode(dst, src, len, tmp, 1)
+#define ngx_http_v2_write_value(dst, src, len, tmp) \
+ ngx_http_v2_string_encode(dst, src, len, tmp, 0)
+
+u_char *
+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
+ u_char *key, size_t key_len, u_char *value, size_t value_len,
+ u_char *tmp);
+
+void
+ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c);
+
+#define ngx_http_v2_write_header_str(key, value) \
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
+ (u_char *) value, sizeof(value) - 1, tmp);
+
+#define ngx_http_v2_write_header_tbl(key, val) \
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
+ val.data, val.len, tmp);
+
+#define ngx_http_v2_write_header_pot(key, val) \
+ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
+ val->data, val->len, tmp);
+
#endif /* _NGX_HTTP_V2_H_INCLUDED_ */
diff -uNr a/src/http/v2/ngx_http_v2_table.c f/src/http/v2/ngx_http_v2_table.c
--- a/src/http/v2/ngx_http_v2_table.c 2019-09-24 23:08:48.000000000 +0800
+++ f/src/http/v2/ngx_http_v2_table.c 2019-10-18 00:33:02.152997237 +0800
@@ -361,3 +361,434 @@
return NGX_OK;
}
+
+
+#if (NGX_HTTP_V2_HPACK_ENC)
+
+static ngx_int_t
+hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len);
+
+static ngx_int_t
+hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash,
+ uint8_t *key, size_t key_len);
+
+
+void
+ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c)
+{
+ ngx_http_v2_hpack_enc_entry_t *table;
+ uint64_t idx;
+
+ table = h2c->hpack_enc.htable;
+
+ while (h2c->hpack_enc.size > h2c->max_hpack_table_size) {
+ idx = h2c->hpack_enc.base;
+ h2c->hpack_enc.base = table[idx].next;
+ h2c->hpack_enc.size -= table[idx].size;
+ table[idx].hash_val = 0;
+ h2c->hpack_enc.n_elems--;
+ }
+}
+
+
+/* checks if a header is in the hpack table - if so returns the table entry,
+ otherwise encodes and inserts into the table and returns 0,
+ if failed to insert into table, returns -1 */
+static ngx_int_t
+ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c,
+ size_t key_len, size_t val_len, uint8_t *key, uint8_t *val,
+ ngx_int_t *header_idx)
+{
+ uint64_t hash_val, key_hash, idx, lru;
+ int i;
+ size_t size = key_len + val_len + 32;
+ uint8_t *storage = h2c->hpack_enc.storage;
+
+ ngx_http_v2_hpack_enc_entry_t *table;
+ ngx_http_v2_hpack_name_entry_t *name;
+
+ *header_idx = NGX_ERROR;
+ /* step 1: compute the hash value of header */
+ if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) {
+ return NGX_ERROR;
+ }
+
+ key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234);
+ hash_val = ngx_murmur_hash2_64(val, val_len, key_hash);
+
+ if (hash_val == 0) {
+ return NGX_ERROR;
+ }
+
+ /* step 2: check if full header in the table */
+ idx = hash_val;
+ i = -1;
+ while (idx) {
+ /* at most 8 locations are checked, but most will be done in 1 or 2 */
+ table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ];
+ if (table->hash_val == hash_val
+ && table->klen == key_len
+ && table->vlen == val_len
+ && ngx_memcmp(key, storage + table->pos, key_len) == 0
+ && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0)
+ {
+ return (h2c->hpack_enc.top - table->index) + 61;
+ }
+
+ if (table->hash_val == 0 && i == -1) {
+ i = idx % HPACK_ENC_HTABLE_SZ;
+ break;
+ }
+
+ idx >>= 8;
+ }
+
+ /* step 3: check if key is in one of the tables */
+ *header_idx = hpack_get_static_index(h2c, key, key_len);
+
+ if (i == -1) {
+ return NGX_ERROR;
+ }
+
+ if (*header_idx == NGX_ERROR) {
+ *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len);
+ }
+
+ /* step 4: store the new entry */
+ table = h2c->hpack_enc.htable;
+
+ if (h2c->hpack_enc.top == 0xffffffff) {
+ /* just to be on the safe side, avoid overflow */
+ ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t));
+ }
+
+ while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size)
+ || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) {
+ /* make space for the new entry first */
+ idx = h2c->hpack_enc.base;
+ h2c->hpack_enc.base = table[idx].next;
+ h2c->hpack_enc.size -= table[idx].size;
+ table[idx].hash_val = 0;
+ h2c->hpack_enc.n_elems--;
+ }
+
+ table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val,
+ .index = h2c->hpack_enc.top,
+ .pos = h2c->hpack_enc.pos,
+ .klen = key_len,
+ .vlen = val_len,
+ .size = size,
+ .next = 0};
+
+ table[h2c->hpack_enc.last].next = i;
+ if (h2c->hpack_enc.n_elems == 0) {
+ h2c->hpack_enc.base = i;
+ }
+
+ h2c->hpack_enc.last = i;
+ h2c->hpack_enc.top++;
+ h2c->hpack_enc.size += size;
+ h2c->hpack_enc.n_elems++;
+
+ /* update header name lookup */
+ if (*header_idx == NGX_ERROR ) {
+ lru = h2c->hpack_enc.top;
+
+ for (i=0; i<HPACK_ENC_DYNAMIC_KEY_TBL_SZ; i++) {
+
+ name = &h2c->hpack_enc.heads[i];
+
+ if ( name->hash_val == 0 || (name->hash_val == key_hash
+ && ngx_memcmp(storage + name->pos, key, key_len) == 0) )
+ {
+ name->hash_val = key_hash;
+ name->pos = h2c->hpack_enc.pos;
+ name->index = h2c->hpack_enc.top - 1;
+ break;
+ }
+
+ if (lru > name->index) {
+ lru = name->index;
+ idx = i;
+ }
+ }
+
+ if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) {
+ name = &h2c->hpack_enc.heads[idx];
+ name->hash_val = hash_val;
+ name->pos = h2c->hpack_enc.pos;
+ name->index = h2c->hpack_enc.top - 1;
+ }
+ }
+
+ ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len);
+ ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len);
+
+ h2c->hpack_enc.pos += size;
+ if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) {
+ h2c->hpack_enc.pos = 0;
+ }
+
+ return NGX_OK;
+}
+
+
+u_char *
+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
+ u_char *key, size_t key_len,
+ u_char *value, size_t value_len,
+ u_char *tmp)
+{
+ ngx_int_t idx, header_idx;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 output header: %*s: %*s", key_len, key, value_len,
+ value);
+
+ /* attempt to find the value in the dynamic table */
+ idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value,
+ &header_idx);
+
+ if (idx > 0) {
+ /* positive index indicates success */
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 hpack encode: Indexed Header Field: %ud", idx);
+
+ *pos = 128;
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx);
+
+ } else {
+
+ if (header_idx == NGX_ERROR) { /* if key is not present */
+
+ if (idx == NGX_ERROR) { /* if header was not added */
+ *pos++ = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 hpack encode: Literal Header Field without"
+ " Indexing — New Name");
+ } else { /* if header was added */
+ *pos++ = 64;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 hpack encode: Literal Header Field with "
+ "Incremental Indexing — New Name");
+ }
+
+ pos = ngx_http_v2_write_name(pos, key, key_len, tmp);
+
+ } else { /* if key is present */
+
+ if (idx == NGX_ERROR) {
+ *pos = 0;
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 hpack encode: Literal Header Field without"
+ " Indexing — Indexed Name: %ud", header_idx);
+ } else {
+ *pos = 64;
+ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 hpack encode: Literal Header Field with "
+ "Incremental Indexing — Indexed Name: %ud", header_idx);
+ }
+ }
+
+ pos = ngx_http_v2_write_value(pos, value, value_len, tmp);
+ }
+
+ return pos;
+}
+
+
+static ngx_int_t
+hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash,
+ uint8_t *key, size_t key_len)
+{
+ ngx_http_v2_hpack_name_entry_t *name;
+ int i;
+
+ for (i=0; i<HPACK_ENC_DYNAMIC_KEY_TBL_SZ; i++) {
+ name = &h2c->hpack_enc.heads[i];
+
+ if (name->hash_val == key_hash
+ && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0)
+ {
+ if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) {
+ return (h2c->hpack_enc.top - name->index) + 61;
+ }
+ break;
+ }
+ }
+
+ return NGX_ERROR;
+}
+
+
+/* decide if a given header is present in the static dictionary, this could be
+ done in several ways, but it seems the fastest one is "exhaustive" search */
+static ngx_int_t
+hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len)
+{
+ /* the static dictionary of response only headers,
+ although response headers can be put by origin,
+ that would be rare */
+ static const struct {
+ u_char len;
+ const u_char val[28];
+ u_char idx;
+ } server_headers[] = {
+ { 3, "age", 21},//0
+ { 3, "via", 60},
+ { 4, "date", 33},//2
+ { 4, "etag", 34},
+ { 4, "link", 45},
+ { 4, "vary", 59},
+ { 5, "allow", 22},//6
+ { 6, "server", 54},//7
+ { 7, "expires", 36},//8
+ { 7, "refresh", 52},
+ { 8, "location", 46},//10
+ {10, "set-cookie", 55},//11
+ {11, "retry-after", 53},//12
+ {12, "content-type", 31},//13
+ {13, "content-range", 30},//14
+ {13, "accept-ranges", 18},
+ {13, "cache-control", 24},
+ {13, "last-modified", 44},
+ {14, "content-length", 28},//18
+ {16, "content-encoding", 26},//19
+ {16, "content-language", 27},
+ {16, "content-location", 29},
+ {16, "www-authenticate", 61},
+ {17, "transfer-encoding", 57},//23
+ {18, "proxy-authenticate", 48},//24
+ {19, "content-disposition", 25},//25
+ {25, "strict-transport-security", 56},//26
+ {27, "access-control-allow-origin", 20},//27
+ {99, "", 99},
+ }, *header;
+
+ /* for a given length, where to start the search
+ since minimal length is 3, the table has a -3
+ offset */
+ static const int8_t start_at[] = {
+ [3-3] = 0,
+ [4-3] = 2,
+ [5-3] = 6,
+ [6-3] = 7,
+ [7-3] = 8,
+ [8-3] = 10,
+ [9-3] = -1,
+ [10-3] = 11,
+ [11-3] = 12,
+ [12-3] = 13,
+ [13-3] = 14,
+ [14-3] = 18,
+ [15-3] = -1,
+ [16-3] = 19,
+ [17-3] = 23,
+ [18-3] = 24,
+ [19-3] = 25,
+ [20-3] = -1,
+ [21-3] = -1,
+ [22-3] = -1,
+ [23-3] = -1,
+ [24-3] = -1,
+ [25-3] = 26,
+ [26-3] = -1,
+ [27-3] = 27,
+ };
+
+ uint64_t pref;
+ size_t save_len = len, i;
+ int8_t start;
+
+ /* early exit for out of bounds lengths */
+ if (len < 3 || len > 27) {
+ return NGX_ERROR;
+ }
+
+ start = start_at[len - 3];
+ if (start == -1) {
+ /* exit for non existent lengths */
+ return NGX_ERROR;
+ }
+
+ header = &server_headers[start_at[len - 3]];
+
+ /* load first 8 bytes of key, for fast comparison */
+ if (len < 8) {
+ pref = 0;
+ if (len >= 4) {
+ pref = *(uint32_t *)(val + len - 4) | 0x20202020;
+ len -= 4;
+ }
+ while (len > 0) { /* 3 iterations at most */
+ pref = (pref << 8) ^ (val[len - 1] | 0x20);
+ len--;
+ }
+ } else {
+ pref = *(uint64_t *)val | 0x2020202020202020;
+ len -= 8;
+ }
+
+ /* iterate over headers with the right length */
+ while (header->len == save_len) {
+ /* quickly compare the first 8 bytes, most tests will end here */
+ if (pref != *(uint64_t *) header->val) {
+ header++;
+ continue;
+ }
+
+ if (len == 0) {
+ /* len == 0, indicates prefix held the entire key */
+ return header->idx;
+ }
+ /* for longer keys compare the rest */
+ i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */
+
+ while (i + 8 <= save_len) { /* 3 iterations at most */
+ if ( *(uint64_t *)&header->val[i]
+ != (*(uint64_t *) &val[i]| 0x2020202020202020) )
+ {
+ header++;
+ i = 0;
+ break;
+ }
+ i += 8;
+ }
+
+ if (i == 0) {
+ continue;
+ }
+
+ /* found the corresponding entry in the static dictionary */
+ return header->idx;
+ }
+
+ return NGX_ERROR;
+}
+
+#else
+
+u_char *
+ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
+ u_char *key, size_t key_len,
+ u_char *value, size_t value_len,
+ u_char *tmp)
+{
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+ "http2 output header: %*s: %*s", key_len, key, value_len,
+ value);
+
+ *pos++ = 64;
+ pos = ngx_http_v2_write_name(pos, key, key_len, tmp);
+ pos = ngx_http_v2_write_value(pos, value, value_len, tmp);
+
+ return pos;
+}
+
+#endif
diff -uNr a/src/http/v3/ngx_http_v3.c f/src/http/v3/ngx_http_v3.c
--- a/src/http/v3/ngx_http_v3.c 1970-01-01 08:00:00.000000000 +0800
+++ f/src/http/v3/ngx_http_v3.c 2019-10-18 00:32:29.374729960 +0800
@@ -0,0 +1,2090 @@
+
+/*
+ * Copyright (C) Cloudflare, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_v3_module.h>
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t offset;
+ ngx_uint_t hash;
+ ngx_http_header_t *hh;
+} ngx_http_v3_parse_header_t;
+
+
+/* errors */
+#define NGX_HTTP_V3_NO_ERROR 0x0
+#define NGX_HTTP_V3_INTERNAL_ERROR 0x3
+
+
+static void ngx_http_v3_handler(ngx_connection_t *c);
+
+static ngx_http_v3_stream_t *ngx_http_v3_stream_lookup(
+ ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id);
+static ngx_http_v3_stream_t *ngx_http_v3_create_stream(
+ ngx_http_v3_connection_t *h3c);
+static void ngx_http_v3_close_stream_handler(ngx_event_t *ev);
+
+static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r,
+ ngx_http_v3_header_t *header);
+static ngx_int_t ngx_http_v3_pseudo_header(ngx_http_request_t *r,
+ ngx_http_v3_header_t *header);
+static ngx_int_t ngx_http_v3_parse_path(ngx_http_request_t *r,
+ ngx_str_t *value);
+static ngx_int_t ngx_http_v3_parse_method(ngx_http_request_t *r,
+ ngx_str_t *value);
+static ngx_int_t ngx_http_v3_parse_scheme(ngx_http_request_t *r,
+ ngx_str_t *value);
+static ngx_int_t ngx_http_v3_parse_authority(ngx_http_request_t *r,
+ ngx_str_t *value);
+static ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r,
+ ngx_http_v3_parse_header_t *header, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r,
+ ngx_http_v3_header_t *header);
+static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_construct_request_line(ngx_http_request_t *r);
+
+static void ngx_http_v3_run_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_process_request_body(ngx_http_request_t *r,
+ ngx_uint_t do_read, ngx_uint_t last);
+static ngx_int_t ngx_http_v3_filter_request_body(ngx_http_request_t *r);
+static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r);
+
+static ngx_chain_t *ngx_http_v3_send_chain(ngx_connection_t *fc,
+ ngx_chain_t *in, off_t limit);
+
+static void ngx_http_v3_finalize_connection(ngx_http_v3_connection_t *h3c,
+ ngx_uint_t status);
+
+static void ngx_http_v3_pool_cleanup(void *data);
+
+
+static ngx_http_v3_parse_header_t ngx_http_v3_parse_headers[] = {
+ { ngx_string("host"),
+ offsetof(ngx_http_headers_in_t, host), 0, NULL },
+
+ { ngx_string("accept-encoding"),
+ offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL },
+
+ { ngx_string("accept-language"),
+ offsetof(ngx_http_headers_in_t, accept_language), 0, NULL },
+
+ { ngx_string("user-agent"),
+ offsetof(ngx_http_headers_in_t, user_agent), 0, NULL },
+
+ { ngx_null_string, 0, 0, NULL }
+};
+
+
+void
+ngx_http_v3_init(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_connection_t *hc;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+
+ c = rev->data;
+ hc = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http3 connection");
+
+ c->log->action = "processing HTTP/3 connection";
+
+ h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
+ if (h3c == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
+
+ h3c->h3 = quiche_h3_conn_new_with_transport(c->quic->conn, h3scf->http3);
+ if (h3c->h3 == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ h3c->http_connection = hc;
+
+ h3c->connection = c;
+
+ h3c->pool = c->pool;
+
+ c->data = h3c;
+
+ c->quic->handler = ngx_http_v3_handler;
+
+ cln = ngx_pool_cleanup_add(c->pool, 0);
+ if (cln == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ cln->handler = ngx_http_v3_pool_cleanup;
+ cln->data = h3c;
+
+ ngx_rbtree_init(&h3c->streams, &h3c->streams_sentinel,
+ ngx_rbtree_insert_value);
+}
+
+
+static int
+ngx_http_v3_for_each_header(uint8_t *name, size_t name_len,
+ uint8_t *value, size_t value_len, void *argp)
+{
+ ngx_int_t rc;
+ ngx_table_elt_t *h;
+ ngx_http_header_t *hh;
+ ngx_http_request_t *r;
+ ngx_http_v3_header_t header;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_main_conf_t *cmcf;
+
+ static ngx_str_t cookie = ngx_string("cookie");
+
+ r = argp;
+
+ /* Duplicate the header name because we don't own it. */
+ header.name.data = ngx_pnalloc(r->pool, name_len);
+ if (header.name.data == NULL) {
+ return NGX_ERROR;
+ }
+ header.name.len = name_len;
+
+ ngx_memcpy(header.name.data, name, name_len);
+
+ /* Duplicate the header value because we don't own it. Some of the
+ * functions that process headers require a NULL-terminated string,
+ * so allocate enough memory for that. */
+ header.value.data = ngx_pcalloc(r->pool, value_len + 1);
+ if (header.value.data == NULL) {
+ return NGX_ERROR;
+ }
+ header.value.len = value_len;
+
+ ngx_memcpy(header.value.data, value, value_len);
+
+ if (ngx_http_v3_validate_header(r, &header) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* Check for pseudo-header. */
+ if (header.name.data[0] == ':') {
+ rc = ngx_http_v3_pseudo_header(r, &header);
+
+ if (rc == NGX_OK) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 header: \":%V: %V\"",
+ &header.name, &header.value);
+
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (r->invalid_header) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (cscf->ignore_invalid_headers) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid header: \"%V\"", &header.name);
+
+ return NGX_ERROR;
+ }
+ }
+
+ /* Handle Cookie header separately. Not sure why, but the HTTP/2 code does
+ * the same. */
+ if (header.name.len == cookie.len
+ && ngx_memcmp(header.name.data, cookie.data, cookie.len) == 0)
+ {
+ if (ngx_http_v3_cookie(r, &header) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->key.len = header.name.len;
+ h->key.data = header.name.data;
+
+ /*
+ * TODO Optimization: precalculate hash
+ * and handler for indexed headers.
+ */
+ h->hash = ngx_hash_key(h->key.data, h->key.len);
+
+ h->value.len = header.value.len;
+ h->value.data = header.value.data;
+
+ h->lowcase_key = h->key.data;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 header: \"%V: %V\"",
+ &header.name, &header.value);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_v3_process_headers(ngx_connection_t *c, quiche_h3_event *ev,
+ int64_t stream_id)
+{
+ int rc;
+ ngx_http_v3_stream_t *stream;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process headers");
+
+ h3c = c->data;
+
+ h3scf = ngx_http_get_module_srv_conf(h3c->http_connection->conf_ctx,
+ ngx_http_v3_module);
+
+ if (h3c->connection->requests >= h3scf->max_requests) {
+ ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_NO_ERROR);
+ return;
+ }
+
+ /* Create a new stream to handle the incoming request. */
+ stream = ngx_http_v3_create_stream(h3c);
+ if (stream == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create HTTP/3 stream");
+
+ ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR);
+ return;
+ }
+
+ stream->id = stream_id;
+
+ stream->node.key = stream_id;
+
+ ngx_rbtree_insert(&h3c->streams, &stream->node);
+
+ /* Populate ngx_http_request_t from raw HTTP/3 headers. */
+ rc = quiche_h3_event_for_each_header(ev,
+ ngx_http_v3_for_each_header, stream->request);
+
+ if (rc != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "received invalid HTTP/3 headers");
+
+ ngx_http_v3_finalize_connection(h3c, NGX_HTTP_V3_INTERNAL_ERROR);
+ return;
+ }
+
+ ngx_http_v3_run_request(stream->request);
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_data(ngx_connection_t *c, int64_t stream_id)
+{
+ int rc;
+ ngx_http_request_t *r;
+ ngx_http_v3_stream_t *stream;
+ ngx_http_v3_connection_t *h3c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 process data");
+
+ h3c = c->data;
+
+ stream = ngx_http_v3_stream_lookup(h3c, stream_id);
+
+ if (stream == NULL) {
+
+ return NGX_OK;
+ }
+
+ if (stream->skip_data) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "skipping http3 DATA frame");
+
+ return NGX_OK;
+ }
+
+ r = stream->request;
+
+ if (!r->request_body) {
+ return NGX_AGAIN;
+ }
+
+ rc = ngx_http_v3_process_request_body(r, 1, stream->in_closed);
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (rc != NGX_OK) {
+ stream->skip_data = 1;
+ ngx_http_finalize_request(r, rc);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_v3_process_blocked_streams(ngx_http_v3_connection_t *h3c)
+{
+ ngx_event_t *wev;
+ quiche_stream_iter *writable;
+ ngx_http_v3_stream_t *stream;
+ uint64_t stream_id;
+
+ writable = quiche_conn_writable(h3c->connection->quic->conn);
+
+ while (quiche_stream_iter_next(writable, &stream_id)) {
+ stream = ngx_http_v3_stream_lookup(h3c, stream_id);
+
+ if (stream == NULL) {
+ continue;
+ }
+
+ if (!stream->blocked) {
+ continue;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0,
+ "http3 stream unblocked %ui", stream->id);
+
+ stream->blocked = 0;
+
+ wev = stream->request->connection->write;
+
+ wev->active = 0;
+ wev->ready = 1;
+
+ if (!wev->delayed) {
+ wev->handler(wev);
+ }
+ }
+
+ quiche_stream_iter_free(writable);
+}
+
+
+static void
+ngx_http_v3_handler(ngx_connection_t *c)
+{
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_stream_t *stream;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 handler");
+
+ h3c = c->data;
+
+ if (c->error) {
+ ngx_http_v3_finalize_connection(h3c, 0);
+ return;
+ }
+
+ ngx_http_v3_process_blocked_streams(h3c);
+
+ while (!c->error) {
+ quiche_h3_event *ev;
+
+ int64_t stream_id = quiche_h3_conn_poll(h3c->h3, c->quic->conn, &ev);
+ if (stream_id < 0) {
+ break;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0,
+ "http3 event stream:%ui ev:%ui", stream_id,
+ quiche_h3_event_type(ev));
+
+ switch (quiche_h3_event_type(ev)) {
+ case QUICHE_H3_EVENT_HEADERS: {
+ ngx_http_v3_process_headers(c, ev, stream_id);
+ break;
+ }
+
+ case QUICHE_H3_EVENT_DATA: {
+ if (ngx_http_v3_process_data(c, stream_id) == NGX_AGAIN) {
+ quiche_h3_event_free(ev);
+ return;
+ }
+
+ break;
+ }
+
+ case QUICHE_H3_EVENT_FINISHED: {
+ /* Lookup stream. If there isn't one, it means it has already
+ * been closed, so ignore the event. */
+ stream = ngx_http_v3_stream_lookup(h3c, stream_id);
+
+ if (stream != NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 finished");
+
+ stream->in_closed = 1;
+
+ /* Flush request body that was buffered. */
+ if (stream->request->request_body) {
+ ngx_http_v3_process_request_body(stream->request, 0, 1);
+ }
+ }
+
+ break;
+ }
+ }
+
+ quiche_h3_event_free(ev);
+ }
+}
+
+
+static ngx_http_v3_stream_t *
+ngx_http_v3_create_stream(ngx_http_v3_connection_t *h3c)
+{
+ ngx_log_t *log;
+ ngx_event_t *rev, *wev;
+ ngx_connection_t *fc;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_request_t *r;
+ ngx_http_v3_stream_t *stream;
+ ngx_http_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h3c->connection->log, 0,
+ "http3 create stream");
+
+ fc = h3c->free_fake_connections;
+
+ if (fc) {
+ h3c->free_fake_connections = fc->data;
+
+ rev = fc->read;
+ wev = fc->write;
+ log = fc->log;
+ ctx = log->data;
+
+ } else {
+ fc = ngx_palloc(h3c->pool, sizeof(ngx_connection_t));
+ if (fc == NULL) {
+ return NULL;
+ }
+
+ rev = ngx_palloc(h3c->pool, sizeof(ngx_event_t));
+ if (rev == NULL) {
+ return NULL;
+ }
+
+ wev = ngx_palloc(h3c->pool, sizeof(ngx_event_t));
+ if (wev == NULL) {
+ return NULL;
+ }
+
+ log = ngx_palloc(h3c->pool, sizeof(ngx_log_t));
+ if (log == NULL) {
+ return NULL;
+ }
+
+ ctx = ngx_palloc(h3c->pool, sizeof(ngx_http_log_ctx_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ ctx->connection = fc;
+ ctx->request = NULL;
+ ctx->current_request = NULL;
+ }
+
+ ngx_memcpy(log, h3c->connection->log, sizeof(ngx_log_t));
+
+ log->data = ctx;
+
+ ngx_memzero(rev, sizeof(ngx_event_t));
+
+ rev->data = fc;
+ rev->ready = 1;
+ rev->handler = ngx_http_v3_close_stream_handler;
+ rev->log = log;
+
+ ngx_memcpy(wev, rev, sizeof(ngx_event_t));
+
+ wev->write = 1;
+
+ ngx_memcpy(fc, h3c->connection, sizeof(ngx_connection_t));
+
+ fc->data = h3c->http_connection;
+ fc->quic = h3c->connection->quic;
+ fc->read = rev;
+ fc->write = wev;
+ fc->sent = 0;
+ fc->buffer = NULL;
+ fc->log = log;
+ fc->buffered = 0;
+ fc->sndlowat = 1;
+ fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
+
+ fc->send_chain = ngx_http_v3_send_chain;
+ fc->need_last_buf = 1;
+
+ r = ngx_http_create_request(fc);
+ if (r == NULL) {
+ return NULL;
+ }
+
+ ngx_str_set(&r->http_protocol, "HTTP/3");
+
+ r->http_version = NGX_HTTP_VERSION_3;
+ r->valid_location = 1;
+
+ fc->data = r;
+ h3c->connection->requests++;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ r->header_in = ngx_create_temp_buf(r->pool,
+ cscf->client_header_buffer_size);
+ if (r->header_in == NULL) {
+ ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NULL;
+ }
+
+ if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NULL;
+ }
+
+ r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+ stream = ngx_pcalloc(h3c->pool, sizeof(ngx_http_v3_stream_t));
+ if (stream == NULL) {
+ ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NULL;
+ }
+
+ r->qstream = stream;
+
+ stream->request = r;
+ stream->connection = h3c;
+
+ h3c->processing++;
+
+ return stream;
+}
+
+
+static ngx_http_v3_stream_t *
+ngx_http_v3_stream_lookup(ngx_http_v3_connection_t *h3c, ngx_uint_t stream_id)
+{
+ ngx_rbtree_node_t *node, *sentinel;
+
+ node = h3c->streams.root;
+ sentinel = h3c->streams.sentinel;
+
+ while (node != sentinel) {
+
+ if (stream_id < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (stream_id > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* stream_id == node->key */
+
+ return (ngx_http_v3_stream_t *) node;
+ }
+
+ /* not found */
+
+ return NULL;
+}
+
+
+/* The following functions are copied from the HTTP/2 module, and adapted to
+ * work independently. In theory we could refactor the HTTP/2 module to expose
+ * these functions, but that would be fairly invasive and likely cause more
+ * merge conflicts in the future. */
+
+
+static ngx_int_t
+ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_http_v3_header_t *header)
+{
+ u_char ch;
+ ngx_uint_t i;
+ ngx_http_core_srv_conf_t *cscf;
+
+ if (header->name.len == 0) {
+ return NGX_ERROR;
+ }
+
+ r->invalid_header = 0;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ for (i = (header->name.data[0] == ':'); i != header->name.len; i++) {
+ ch = header->name.data[i];
+
+ if ((ch >= 'a' && ch <= 'z')
+ || (ch == '-')
+ || (ch >= '0' && ch <= '9')
+ || (ch == '_' && cscf->underscores_in_headers))
+ {
+ continue;
+ }
+
+ if (ch == '\0' || ch == LF || ch == CR || ch == ':'
+ || (ch >= 'A' && ch <= 'Z'))
+ {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid header name: \"%V\"",
+ &header->name);
+
+ return NGX_ERROR;
+ }
+
+ r->invalid_header = 1;
+ }
+
+ for (i = 0; i != header->value.len; i++) {
+ ch = header->value.data[i];
+
+ if (ch == '\0' || ch == LF || ch == CR) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent header \"%V\" with "
+ "invalid value: \"%V\"",
+ &header->name, &header->value);
+
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_pseudo_header(ngx_http_request_t *r, ngx_http_v3_header_t *header)
+{
+ header->name.len--;
+ header->name.data++;
+
+ switch (header->name.len) {
+ case 4:
+ if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1)
+ == 0)
+ {
+ return ngx_http_v3_parse_path(r, &header->value);
+ }
+
+ break;
+
+ case 6:
+ if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1)
+ == 0)
+ {
+ return ngx_http_v3_parse_method(r, &header->value);
+ }
+
+ if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1)
+ == 0)
+ {
+ return ngx_http_v3_parse_scheme(r, &header->value);
+ }
+
+ break;
+
+ case 9:
+ if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1)
+ == 0)
+ {
+ return ngx_http_v3_parse_authority(r, &header->value);
+ }
+
+ break;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent unknown pseudo-header \":%V\"",
+ &header->name);
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_path(ngx_http_request_t *r, ngx_str_t *value)
+{
+ if (r->unparsed_uri.len) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate :path header");
+
+ return NGX_DECLINED;
+ }
+
+ if (value->len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent empty :path header");
+
+ return NGX_DECLINED;
+ }
+
+ r->uri_start = value->data;
+ r->uri_end = value->data + value->len;
+
+ if (ngx_http_parse_uri(r) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid :path header: \"%V\"", value);
+
+ return NGX_DECLINED;
+ }
+
+ if (ngx_http_process_request_uri(r) != NGX_OK) {
+ /*
+ * request has been finalized already
+ * in ngx_http_process_request_uri()
+ */
+ return NGX_ABORT;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_method(ngx_http_request_t *r, ngx_str_t *value)
+{
+ size_t k, len;
+ ngx_uint_t n;
+ const u_char *p, *m;
+
+ /*
+ * This array takes less than 256 sequential bytes,
+ * and if typical CPU cache line size is 64 bytes,
+ * it is prefetched for 4 load operations.
+ */
+ static const struct {
+ u_char len;
+ const u_char method[11];
+ uint32_t value;
+ } tests[] = {
+ { 3, "GET", NGX_HTTP_GET },
+ { 4, "POST", NGX_HTTP_POST },
+ { 4, "HEAD", NGX_HTTP_HEAD },
+ { 7, "OPTIONS", NGX_HTTP_OPTIONS },
+ { 8, "PROPFIND", NGX_HTTP_PROPFIND },
+ { 3, "PUT", NGX_HTTP_PUT },
+ { 5, "MKCOL", NGX_HTTP_MKCOL },
+ { 6, "DELETE", NGX_HTTP_DELETE },
+ { 4, "COPY", NGX_HTTP_COPY },
+ { 4, "MOVE", NGX_HTTP_MOVE },
+ { 9, "PROPPATCH", NGX_HTTP_PROPPATCH },
+ { 4, "LOCK", NGX_HTTP_LOCK },
+ { 6, "UNLOCK", NGX_HTTP_UNLOCK },
+ { 5, "PATCH", NGX_HTTP_PATCH },
+ { 5, "TRACE", NGX_HTTP_TRACE }
+ }, *test;
+
+ if (r->method_name.len) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate :method header");
+
+ return NGX_DECLINED;
+ }
+
+ if (value->len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent empty :method header");
+
+ return NGX_DECLINED;
+ }
+
+ r->method_name.len = value->len;
+ r->method_name.data = value->data;
+
+ len = r->method_name.len;
+ n = sizeof(tests) / sizeof(tests[0]);
+ test = tests;
+
+ do {
+ if (len == test->len) {
+ p = r->method_name.data;
+ m = test->method;
+ k = len;
+
+ do {
+ if (*p++ != *m++) {
+ goto next;
+ }
+ } while (--k);
+
+ r->method = test->value;
+ return NGX_OK;
+ }
+
+ next:
+ test++;
+
+ } while (--n);
+
+ p = r->method_name.data;
+
+ do {
+ if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid method: \"%V\"",
+ &r->method_name);
+
+ return NGX_DECLINED;
+ }
+
+ p++;
+
+ } while (--len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_scheme(ngx_http_request_t *r, ngx_str_t *value)
+{
+ if (r->schema_start) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate :scheme header");
+
+ return NGX_DECLINED;
+ }
+
+ if (value->len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent empty :scheme header");
+
+ return NGX_DECLINED;
+ }
+
+ r->schema_start = value->data;
+ r->schema_end = value->data + value->len;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_authority(ngx_http_request_t *r, ngx_str_t *value)
+{
+ return ngx_http_v3_parse_header(r, &ngx_http_v3_parse_headers[0], value);
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_header(ngx_http_request_t *r,
+ ngx_http_v3_parse_header_t *header, ngx_str_t *value)
+{
+ ngx_table_elt_t *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->key.len = header->name.len;
+ h->key.data = header->name.data;
+ h->lowcase_key = header->name.data;
+
+ if (header->hh == NULL) {
+ header->hash = ngx_hash_key(header->name.data, header->name.len);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash,
+ h->lowcase_key, h->key.len);
+ if (header->hh == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ h->hash = header->hash;
+
+ h->value.len = value->len;
+ h->value.data = value->data;
+
+ if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) {
+ /* header handler has already finalized request */
+ return NGX_ABORT;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_construct_request_line(ngx_http_request_t *r)
+{
+ u_char *p;
+
+ static const u_char ending[] = " HTTP/3";
+
+ if (r->method_name.len == 0
+ || r->schema_start == NULL
+ || r->unparsed_uri.len == 0)
+ {
+ if (r->method_name.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent no :method header");
+
+ } else if (r->schema_start == NULL) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent no :scheme header");
+
+ } else {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent no :path header");
+ }
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ r->request_line.len = r->method_name.len + 1
+ + r->unparsed_uri.len
+ + sizeof(ending) - 1;
+
+ p = ngx_pnalloc(r->pool, r->request_line.len + 1);
+ if (p == NULL) {
+ ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ r->request_line.data = p;
+
+ p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
+
+ *p++ = ' ';
+
+ p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);
+
+ ngx_memcpy(p, ending, sizeof(ending));
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 request line: \"%V\"", &r->request_line);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_cookie(ngx_http_request_t *r, ngx_http_v3_header_t *header)
+{
+ ngx_str_t *val;
+ ngx_array_t *cookies;
+
+ cookies = r->qstream->cookies;
+
+ if (cookies == NULL) {
+ cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t));
+ if (cookies == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->qstream->cookies = cookies;
+ }
+
+ val = ngx_array_push(cookies);
+ if (val == NULL) {
+ return NGX_ERROR;
+ }
+
+ val->len = header->value.len;
+ val->data = header->value.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_construct_cookie_header(ngx_http_request_t *r)
+{
+ u_char *buf, *p, *end;
+ size_t len;
+ ngx_str_t *vals;
+ ngx_uint_t i;
+ ngx_array_t *cookies;
+ ngx_table_elt_t *h;
+ ngx_http_header_t *hh;
+ ngx_http_core_main_conf_t *cmcf;
+
+ static ngx_str_t cookie = ngx_string("cookie");
+
+ cookies = r->qstream->cookies;
+
+ if (cookies == NULL) {
+ return NGX_OK;
+ }
+
+ vals = cookies->elts;
+
+ i = 0;
+ len = 0;
+
+ do {
+ len += vals[i].len + 2;
+ } while (++i != cookies->nelts);
+
+ len -= 2;
+
+ buf = ngx_pnalloc(r->pool, len + 1);
+ if (buf == NULL) {
+ ngx_http_v3_close_stream(r->qstream, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ p = buf;
+ end = buf + len;
+
+ for (i = 0; /* void */ ; i++) {
+
+ p = ngx_cpymem(p, vals[i].data, vals[i].len);
+
+ if (p == end) {