Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
1 contributor

Users who have contributed to this file

11401 lines (11193 sloc) 326 KB
Add SPDY Support.
Add HTTP3(QUIC) Support.
Add HTTP2 HPACK Encoding Support.
Add Dynamic TLS Record support.
Using: patch -p1 < nginx.patch
diff -uNr a/auto/lib/conf b/auto/lib/conf
--- a/auto/lib/conf 2019-12-24 23:00:09.000000000 +0800
+++ b/auto/lib/conf 2020-01-02 21:25:20.000000000 +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 b/auto/lib/make
--- a/auto/lib/make 2019-12-24 23:00:09.000000000 +0800
+++ b/auto/lib/make 2020-01-02 21:25:20.000000000 +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 b/auto/lib/openssl/make
--- a/auto/lib/openssl/make 2019-12-24 23:00:09.000000000 +0800
+++ b/auto/lib/openssl/make 2020-01-02 21:25:20.000000000 +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 b/auto/lib/quiche/conf
--- a/auto/lib/quiche/conf 1970-01-01 08:00:00.000000000 +0800
+++ b/auto/lib/quiche/conf 2020-01-02 21:25:20.000000000 +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 b/auto/lib/quiche/make
--- a/auto/lib/quiche/make 1970-01-01 08:00:00.000000000 +0800
+++ b/auto/lib/quiche/make 2020-01-02 21:25:20.000000000 +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 b/auto/make
--- a/auto/make 2019-12-24 23:00:09.000000000 +0800
+++ b/auto/make 2020-01-02 21:25:20.000000000 +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 b/auto/modules
--- a/auto/modules 2019-12-24 23:00:09.000000000 +0800
+++ b/auto/modules 2020-01-03 10:50:12.000000000 +0800
@@ -118,7 +118,9 @@
# ngx_http_write_filter
# ngx_http_header_filter
# ngx_http_chunked_filter
+ # ngx_http_spdy_filter
# ngx_http_v2_filter
+ # ngx_http_v3_filter
# ngx_http_range_header_filter
# ngx_http_gzip_filter
# ngx_http_postpone_filter
@@ -150,7 +152,9 @@
ngx_http_write_filter_module \
ngx_http_header_filter_module \
ngx_http_chunked_filter_module \
+ ngx_http_spdy_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 \
@@ -201,6 +205,19 @@
. auto/module
fi
+ if [ $HTTP_SPDY = YES ]; then
+ have=NGX_HTTP_SPDY . auto/have
+ USE_ZLIB=YES
+ ngx_module_name=ngx_http_spdy_filter_module
+ ngx_module_incs=
+ ngx_module_deps=
+ ngx_module_srcs=src/http/ngx_http_spdy_filter_module.c
+ ngx_module_libs=
+ ngx_module_link=$HTTP_SPDY
+
+ . auto/module
+ fi
+
if [ $HTTP_V2 = YES ]; then
ngx_module_name=ngx_http_v2_filter_module
ngx_module_incs=
@@ -212,6 +229,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=
@@ -403,6 +431,19 @@
ngx_module_type=HTTP
+ if [ $HTTP_SPDY = YES ]; then
+ have=NGX_HTTP_SPDY . auto/have
+ ngx_module_name=ngx_http_spdy_module
+ ngx_module_incs=src/http
+ ngx_module_deps="src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h"
+ ngx_module_srcs="src/http/ngx_http_spdy.c \
+ src/http/ngx_http_spdy_module.c"
+ ngx_module_libs=
+ ngx_module_link=$HTTP_SPDY
+
+ . auto/module
+ fi
+
if [ $HTTP_V2 = YES ]; then
have=NGX_HTTP_V2 . auto/have
have=NGX_HTTP_HEADERS . auto/have
@@ -423,6 +464,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 +1314,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 b/auto/options
--- a/auto/options 2019-12-24 23:00:09.000000000 +0800
+++ b/auto/options 2020-01-03 10:51:20.000000000 +0800
@@ -58,7 +58,10 @@
HTTP_CHARSET=YES
HTTP_GZIP=YES
HTTP_SSL=NO
+HTTP_SPDY=NO
HTTP_V2=NO
+HTTP_V2_HPACK_ENC=NO
+HTTP_V3=NO
HTTP_SSI=YES
HTTP_REALIP=NO
HTTP_XSLT=NO
@@ -147,6 +150,9 @@
USE_OPENSSL=NO
OPENSSL=NONE
+USE_QUICHE=NO
+QUICHE=NONE
+
USE_ZLIB=NO
ZLIB=NONE
ZLIB_OPT=
@@ -223,7 +229,10 @@
--http-scgi-temp-path=*) NGX_HTTP_SCGI_TEMP_PATH="$value" ;;
--with-http_ssl_module) HTTP_SSL=YES ;;
+ --with-http_spdy_module) HTTP_SPDY=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 +366,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"
@@ -438,7 +450,10 @@
--with-file-aio enable file AIO support
--with-http_ssl_module enable ngx_http_ssl_module
+ --with-http_spdy_module enable ngx_http_spdy_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 b/src/core/ngx_connection.h
--- a/src/core/ngx_connection.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/core/ngx_connection.h 2020-01-03 10:51:59.000000000 +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
@@ -119,6 +122,7 @@
#define NGX_LOWLEVEL_BUFFERED 0x0f
#define NGX_SSL_BUFFERED 0x01
#define NGX_HTTP_V2_BUFFERED 0x02
+#define NGX_SPDY_BUFFERED 0x04
struct ngx_connection_s {
@@ -155,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 b/src/core/ngx_core.h
--- a/src/core/ngx_core.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/core/ngx_core.h 2020-01-02 21:25:20.000000000 +0800
@@ -83,6 +83,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 b/src/core/ngx_murmurhash.c
--- a/src/core/ngx_murmurhash.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/core/ngx_murmurhash.c 2020-01-02 21:25:20.000000000 +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 b/src/core/ngx_murmurhash.h
--- a/src/core/ngx_murmurhash.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/core/ngx_murmurhash.h 2020-01-02 21:25:20.000000000 +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 b/src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/event/ngx_event_openssl.c 2020-01-02 21:25:20.000000000 +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;
@@ -2400,6 +2401,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;
@@ -2507,6 +2543,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 b/src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/event/ngx_event_openssl.h 2020-01-02 21:25:20.000000000 +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 b/src/event/ngx_event_quic.c
--- a/src/event/ngx_event_quic.c 1970-01-01 08:00:00.000000000 +0800
+++ b/src/event/ngx_event_quic.c 2020-01-02 21:25:20.000000000 +0800
@@ -0,0 +1,581 @@
+
+/*
+ * 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 (!quiche_version_is_supported(pkt_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, dcid_len) - dcid_hex, dcid_hex,
+ ngx_hex_dump(scid_hex, scid, scid_len) - 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_in_early_data(c->quic->conn) ||
+ 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);
+
+ /* The QUIC connection might have already been freed inside the write
+ * handler, in which case we are done. */
+ if (c->destroyed) {
+ return NGX_OK;
+ }
+
+ 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 (c->destroyed) {
+ 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 b/src/event/ngx_event_quic.h
--- a/src/event/ngx_event_quic.h 1970-01-01 08:00:00.000000000 +0800
+++ b/src/event/ngx_event_quic.h 2020-01-02 21:25:20.000000000 +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 b/src/event/ngx_event_udp.c
--- a/src/event/ngx_event_udp.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/event/ngx_event_udp.c 2020-01-02 21:25:20.000000000 +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 b/src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/modules/ngx_http_ssl_module.c 2020-01-03 10:59:08.000000000 +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,10 +406,10 @@
#if (NGX_DEBUG)
unsigned int i;
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_HTTP_V3)
ngx_http_connection_t *hc;
#endif
-#if (NGX_HTTP_V2 || NGX_DEBUG)
+#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection(ssl_conn);
@@ -388,9 +423,20 @@
}
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_HTTP_V3)
hc = c->data;
+#endif
+#if (NGX_HTTP_V2 && NGX_HTTP_SPDY)
+ if (hc->addr_conf->http2 && hc->addr_conf->spdy) {
+ srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE
+ NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+ srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE
+ NGX_HTTP_NPN_ADVERTISE) - 1;
+
+ } else
+#endif
+#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
srv =
(unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
@@ -398,6 +444,20 @@
} else
#endif
+#if (NGX_HTTP_SPDY)
+ if (hc->addr_conf->spdy) {
+ srv = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+ srvlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
+
+ } 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;
@@ -425,19 +485,32 @@
ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn,
const unsigned char **out, unsigned int *outlen, void *arg)
{
-#if (NGX_HTTP_V2 || NGX_DEBUG)
+#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection(ssl_conn);
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised");
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_SPDY)
{
ngx_http_connection_t *hc;
hc = c->data;
+#endif
+
+#if (NGX_HTTP_V2 && NGX_HTTP_SPDY)
+ if (hc->addr_conf->http2 && hc->addr_conf->spdy) {
+ *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE
+ NGX_SPDY_NPN_ADVERTISE
+ NGX_HTTP_NPN_ADVERTISE;
+ *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE
+ NGX_HTTP_NPN_ADVERTISE) - 1;
+ return SSL_TLSEXT_ERR_OK;
+ } else
+#endif
+#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
*out =
(unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
@@ -445,6 +518,20 @@
return SSL_TLSEXT_ERR_OK;
}
+#endif
+#if (NGX_HTTP_V2 && NGX_HTTP_SPDY)
+ else
+#endif
+#if (NGX_HTTP_SPDY)
+ if (hc->addr_conf->spdy) {
+ *out = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+ *outlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+#endif
+
+#if (NGX_HTTP_V2 || NGX_HTTP_SPDY)
}
#endif
@@ -580,6 +667,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 +739,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 +963,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 b/src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/modules/ngx_http_ssl_module.h 2020-01-02 21:25:20.000000000 +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 b/src/http/ngx_http.c
--- a/src/http/ngx_http.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http.c 2020-01-03 11:38:04.000000000 +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,12 @@
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
+#if (NGX_HTTP_SPDY)
+ ngx_uint_t spdy;
+#endif
+#if (NGX_HTTP_V3)
+ ngx_uint_t quic;
+#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@@ -1234,6 +1244,12 @@
#if (NGX_HTTP_V2)
http2 = lsopt->http2 || addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SPDY)
+ spdy = lsopt->spdy || addr[i].opt.spdy;
+#endif
+#if (NGX_HTTP_V3)
+ quic = lsopt->quic || addr[i].opt.quic;
+#endif
if (lsopt->set) {
@@ -1270,6 +1286,12 @@
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
+#if (NGX_HTTP_SPDY)
+ addr[i].opt.spdy = spdy;
+#endif
+#if (NGX_HTTP_V3)
+ addr[i].opt.quic = quic;
+#endif
return NGX_OK;
}
@@ -1313,6 +1335,18 @@
#endif
+#if (NGX_HTTP_SPDY && NGX_HTTP_SSL \
+ && !defined TLSEXT_TYPE_application_layer_protocol_negotiation \
+ && !defined TLSEXT_TYPE_next_proto_neg)
+ if (lsopt->spdy && lsopt->ssl) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "nginx was built with OpenSSL that lacks ALPN "
+ "and NPN support, SPDY is not enabled for %s",
+ lsopt->addr);
+ }
+
+#endif
+
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
@@ -1688,6 +1722,12 @@
break;
}
+#if (NGX_HTTP_V3)
+ if (addr[i].opt.quic) {
+ ls->type = SOCK_DGRAM;
+ }
+#endif
+
addr++;
last--;
}
@@ -1770,6 +1810,12 @@
ls->reuseport = addr->opt.reuseport;
#endif
+#if (NGX_HTTP_V3)
+ ls->quic = addr->opt.quic;
+
+ ls->wildcard = addr->opt.wildcard;
+#endif
+
return ls;
}
@@ -1802,7 +1848,13 @@
#if (NGX_HTTP_V2)
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SPDY)
+ addrs[i].conf.spdy = addr[i].opt.spdy;
+#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 +1920,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 b/src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http_core_module.c 2020-01-03 11:42:40.000000000 +0800
@@ -1939,6 +1939,13 @@
return NGX_DECLINED;
}
+#if (NGX_HTTP_SPDY)
+ if (r->spdy_stream) {
+ r->gzip_ok = 1;
+ return NGX_OK;
+ }
+#endif
+
ae = r->headers_in.accept_encoding;
if (ae == NULL) {
return NGX_DECLINED;
@@ -2296,6 +2303,9 @@
#if (NGX_HTTP_V2)
sr->stream = r->stream;
#endif
+#if (NGX_HTTP_SPDY)
+ sr->spdy_stream = r->spdy_stream;
+#endif
sr->method = NGX_HTTP_GET;
sr->http_version = r->http_version;
@@ -4001,11 +4011,15 @@
}
if (ngx_strcmp(value[n].data, "spdy") == 0) {
- ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
- "invalid parameter \"spdy\": "
- "ngx_http_spdy_module was superseded "
- "by ngx_http_v2_module");
+#if (NGX_HTTP_SPDY)
+ lsopt.spdy = 1;
continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"spdy\" parameter requires "
+ "ngx_http_spdy_module");
+ return NGX_CONF_ERROR;
+#endif
}
if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) {
@@ -4104,6 +4118,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 b/src/http/ngx_http_core_module.h
--- a/src/http/ngx_http_core_module.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http_core_module.h 2020-01-03 11:43:43.000000000 +0800
@@ -75,6 +75,9 @@
unsigned wildcard:1;
unsigned ssl:1;
unsigned http2:1;
+#if (NGX_HTTP_SPDY)
+ unsigned spdy:1;
+#endif
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -82,6 +85,7 @@
unsigned reuseport:1;
unsigned so_keepalive:2;
unsigned proxy_protocol:1;
+ unsigned quic:1;
int backlog;
int rcvbuf;
@@ -237,7 +241,11 @@
unsigned ssl:1;
unsigned http2:1;
+#if (NGX_HTTP_SPDY)
+ unsigned spdy:1;
+#endif
unsigned proxy_protocol:1;
+ unsigned quic:1;
};
@@ -268,6 +276,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 b/src/http/ngx_http.h
--- a/src/http/ngx_http.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http.h 2020-01-03 11:51:41.000000000 +0800
@@ -18,8 +18,12 @@
typedef struct ngx_http_cache_s ngx_http_cache_t;
typedef struct ngx_http_file_cache_s ngx_http_file_cache_t;
typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
+#if (NGX_HTTP_SPDY)
+typedef struct ngx_http_spdy_stream_s ngx_http_spdy_stream_t;
+#endif
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 +42,12 @@
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
+#if (NGX_HTTP_SPDY)
+#include <ngx_http_spdy.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 b/src/http/ngx_http_request_body.c
--- a/src/http/ngx_http_request_body.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http_request_body.c 2020-01-03 11:53:46.000000000 +0800
@@ -84,6 +84,18 @@
goto done;
}
#endif
+#if (NGX_HTTP_SPDY)
+ if (r->spdy_stream) {
+ rc = ngx_http_spdy_read_request_body(r, post_handler);
+ goto done;
+ }
+#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;
@@ -226,6 +238,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;
@@ -524,6 +548,18 @@
return NGX_OK;
}
#endif
+#if (NGX_HTTP_SPDY)
+ if (r->spdy_stream) {
+ r->spdy_stream->skip_data = 1;
+ return NGX_OK;
+ }
+#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 +845,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 b/src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http_request.c 2020-01-03 11:50:40.000000000 +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[] = {
@@ -324,6 +328,11 @@
rev->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
+#if (NGX_HTTP_SPDY)
+ if (hc->addr_conf->spdy) {
+ rev->handler = ngx_http_spdy_init;
+ }
+#endif
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
rev->handler = ngx_http_v2_init;
@@ -349,6 +358,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 +818,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 +828,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 +843,57 @@
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_SPDY \
+ && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \
+ || defined TLSEXT_TYPE_next_proto_neg))
+ {
+ unsigned int len;
+ const unsigned char *data;
+ static const ngx_str_t spdy = ngx_string(NGX_SPDY_NPN_NEGOTIATED);
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+ SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
+
+#ifdef TLSEXT_TYPE_next_proto_neg
+ if (len == 0) {
+ SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
+ }
+#endif
+
+#else /* TLSEXT_TYPE_next_proto_neg */
+ SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
+#endif
+
+ if (len == spdy.len && ngx_strncmp(data, spdy.data, spdy.len) == 0) {
+ ngx_http_spdy_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 +1100,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 +2816,20 @@
}
#endif
+#if (NGX_HTTP_SPDY)
+ if (r->spdy_stream) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+#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) {
@@ -2895,6 +3038,31 @@
}
#endif
+#if (NGX_HTTP_SPDY)
+
+ if (r->spdy_stream) {
+ if (c->error) {
+ err = 0;
+ goto closed;
+ }
+
+ return;
+ }
+
+#endif
+
+#if (NGX_HTTP_V3)
+
+ if (r->qstream) {
+ if (c->error) {
+ err = 0;
+ goto closed;
+ }
+
+ return;
+ }
+
+#endif
#if (NGX_HAVE_KQUEUE)
@@ -3563,7 +3731,22 @@
}
#endif
+#if (NGX_HTTP_SPDY)
+ if (r->spdy_stream) {
+ ngx_http_spdy_close_stream(r->spdy_stream, rc);
+ return;
+ }
+#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 +3867,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 b/src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h 2019-12-24 23:00:09.000000000 +0800
+++ b/src/http/ngx_http_request.h 2020-01-03 11:52:35.000000000 +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;
@@ -432,6 +434,9 @@
int *captures;
u_char *captures_data;
#endif
+#if (NGX_HTTP_SPDY)
+ ngx_http_spdy_stream_t *spdy_stream;
+#endif
size_t limit_rate;
size_t limit_rate_after;
@@ -445,6 +450,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_spdy.c b/src/http/ngx_http_spdy.c
--- a/src/http/ngx_http_spdy.c 1970-01-01 08:00:00.000000000 +0800
+++ b/src/http/ngx_http_spdy.c 2020-01-03 11:55:53.000000000 +0800
@@ -0,0 +1,3701 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_spdy_module.h>
+
+#include <zlib.h>
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \
+ *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0) \
+ && m[4] == c4
+
+#else
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4
+
+#endif
+
+
+#if (NGX_HAVE_NONALIGNED)
+
+#define ngx_spdy_frame_parse_uint16(p) ntohs(*(uint16_t *) (p))
+#define ngx_spdy_frame_parse_uint32(p) ntohl(*(uint32_t *) (p))
+
+#else
+
+#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1])
+#define ngx_spdy_frame_parse_uint32(p) \
+ ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
+
+#endif
+
+#define ngx_spdy_frame_parse_sid(p) \
+ (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff)
+#define ngx_spdy_frame_parse_delta(p) \
+ (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff)
+
+
+#define ngx_spdy_ctl_frame_check(h) \
+ (((h) & 0xffff0000) == ngx_spdy_ctl_frame_head(0))
+#define ngx_spdy_data_frame_check(h) \
+ (!((h) & (uint32_t) NGX_SPDY_CTL_BIT << 31))
+
+#define ngx_spdy_ctl_frame_type(h) ((h) & 0x0000ffff)
+#define ngx_spdy_frame_flags(p) ((p) >> 24)
+#define ngx_spdy_frame_length(p) ((p) & 0x00ffffff)
+#define ngx_spdy_frame_id(p) ((p) & 0x00ffffff)
+
+
+#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE 4096
+#define NGX_SPDY_CTL_FRAME_BUFFER_SIZE 16
+
+#define NGX_SPDY_PROTOCOL_ERROR 1
+#define NGX_SPDY_INVALID_STREAM 2
+#define NGX_SPDY_REFUSED_STREAM 3
+#define NGX_SPDY_UNSUPPORTED_VERSION 4
+#define NGX_SPDY_CANCEL 5
+#define NGX_SPDY_INTERNAL_ERROR 6
+#define NGX_SPDY_FLOW_CONTROL_ERROR 7
+#define NGX_SPDY_STREAM_IN_USE 8
+#define NGX_SPDY_STREAM_ALREADY_CLOSED 9
+/* deprecated 10 */
+#define NGX_SPDY_FRAME_TOO_LARGE 11
+
+#define NGX_SPDY_SETTINGS_MAX_STREAMS 4
+#define NGX_SPDY_SETTINGS_INIT_WINDOW 7
+
+#define NGX_SPDY_SETTINGS_FLAG_PERSIST 0x01
+#define NGX_SPDY_SETTINGS_FLAG_PERSISTED 0x02
+
+#define NGX_SPDY_MAX_WINDOW NGX_MAX_INT32_VALUE
+#define NGX_SPDY_CONNECTION_WINDOW 65536
+#define NGX_SPDY_INIT_STREAM_WINDOW 65536
+#define NGX_SPDY_STREAM_WINDOW NGX_SPDY_MAX_WINDOW
+
+typedef struct {
+ ngx_uint_t hash;
+ u_char len;
+ u_char header[7];
+ ngx_int_t (*handler)(ngx_http_request_t *r);
+} ngx_http_spdy_request_header_t;
+
+
+static void ngx_http_spdy_read_handler(ngx_event_t *rev);
+static void ngx_http_spdy_write_handler(ngx_event_t *wev);
+static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc);
+
+static u_char *ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end);
+static u_char *ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc,
+ u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler);
+
+static u_char *ngx_http_spdy_state_inflate_error(
+ ngx_http_spdy_connection_t *sc, int rc);
+static u_char *ngx_http_spdy_state_protocol_error(
+ ngx_http_spdy_connection_t *sc);
+static u_char *ngx_http_spdy_state_internal_error(
+ ngx_http_spdy_connection_t *sc);
+
+static ngx_int_t ngx_http_spdy_send_window_update(
+ ngx_http_spdy_connection_t *sc, ngx_uint_t sid, ngx_uint_t delta);
+static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc,
+ ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority);
+static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc);
+static ngx_int_t ngx_http_spdy_settings_frame_handler(
+ ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
+static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctl_frame(
+ ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority);
+static ngx_int_t ngx_http_spdy_ctl_frame_handler(
+ ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
+
+static ngx_http_spdy_stream_t *ngx_http_spdy_create_stream(
+ ngx_http_spdy_connection_t *sc, ngx_uint_t id, ngx_uint_t priority);
+static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id(
+ ngx_http_spdy_connection_t *sc, ngx_uint_t sid);
+#define ngx_http_spdy_streams_index_size(sscf) (sscf->streams_index_mask + 1)
+#define ngx_http_spdy_stream_index(sscf, sid) \
+ ((sid >> 1) & sscf->streams_index_mask)
+
+static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_spdy_handle_request_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_host(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_path(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r);
+static void ngx_http_spdy_run_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc,
+ ngx_http_spdy_stream_t *stream, ngx_uint_t status);
+
+static void ngx_http_spdy_close_stream_handler(ngx_event_t *ev);
+
+static void ngx_http_spdy_handle_connection_handler(ngx_event_t *rev);
+static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev);
+static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc,
+ ngx_int_t rc);
+
+static ngx_int_t ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc,
+ ssize_t delta);
+
+static void ngx_http_spdy_pool_cleanup(void *data);
+
+static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size);
+static void ngx_http_spdy_zfree(void *opaque, void *address);
+
+
+static const u_char ngx_http_spdy_dict[] = {
+ 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, /* - - - - o p t i */
+ 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, /* o n s - - - - h */
+ 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, /* e a d - - - - p */
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, /* o s t - - - - p */
+ 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, /* u t - - - - d e */
+ 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, /* l e t e - - - - */
+ 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, /* t r a c e - - - */
+ 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, /* - a c c e p t - */
+ 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */
+ 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* t - c h a r s e */
+ 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, /* t - - - - a c c */
+ 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e p t - e n c o */
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, /* d i n g - - - - */
+ 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, /* a c c e p t - l */
+ 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, /* a n g u a g e - */
+ 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */
+ 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, /* t - r a n g e s */
+ 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, /* - - - - a g e - */
+ 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, /* - - - a l l o w */
+ 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, /* - - - - a u t h */
+ 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, /* o r i z a t i o */
+ 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, /* n - - - - c a c */
+ 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, /* h e - c o n t r */
+ 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, /* o l - - - - c o */
+ 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, /* n n e c t i o n */
+ 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */
+ 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, /* e n t - b a s e */
+ 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */
+ 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e n t - e n c o */
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, /* d i n g - - - - */
+ 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, /* c o n t e n t - */
+ 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, /* l a n g u a g e */
+ 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */
+ 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, /* e n t - l e n g */
+ 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, /* t h - - - - c o */
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, /* n t e n t - l o */
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* c a t i o n - - */
+ 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */
+ 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, /* t - m d 5 - - - */
+ 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, /* - c o n t e n t */
+ 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, /* - r a n g e - - */
+ 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */
+ 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, /* t - t y p e - - */
+ 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, /* - - d a t e - - */
+ 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, /* - - e t a g - - */
+ 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, /* - - e x p e c t */
+ 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, /* - - - - e x p i */
+ 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, /* r e s - - - - f */
+ 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, /* r o m - - - - h */
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, /* o s t - - - - i */
+ 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, /* f - m a t c h - */
+ 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, /* - - - i f - m o */
+ 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, /* d i f i e d - s */
+ 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, /* i n c e - - - - */
+ 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, /* i f - n o n e - */
+ 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, /* m a t c h - - - */
+ 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, /* - i f - r a n g */
+ 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, /* e - - - - i f - */
+ 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, /* u n m o d i f i */
+ 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, /* e d - s i n c e */
+ 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, /* - - - - l a s t */
+ 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, /* - m o d i f i e */
+ 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, /* d - - - - l o c */
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, /* a t i o n - - - */
+ 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, /* - m a x - f o r */
+ 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, /* w a r d s - - - */
+ 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, /* - p r a g m a - */
+ 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, /* - - - p r o x y */
+ 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, /* - a u t h e n t */
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, /* i c a t e - - - */
+ 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, /* - p r o x y - a */
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, /* u t h o r i z a */
+ 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, /* t i o n - - - - */
+ 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, /* r a n g e - - - */
+ 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, /* - r e f e r e r */
+ 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, /* - - - - r e t r */
+ 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, /* y - a f t e r - */
+ 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, /* - - - s e r v e */
+ 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, /* r - - - - t e - */
+ 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, /* - - - t r a i l */
+ 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, /* e r - - - - t r */
+ 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, /* a n s f e r - e */
+ 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, /* n c o d i n g - */
+ 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, /* - - - u p g r a */
+ 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, /* d e - - - - u s */
+ 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, /* e r - a g e n t */
+ 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, /* - - - - v a r y */
+ 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, /* - - - - v i a - */
+ 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, /* - - - w a r n i */
+ 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, /* n g - - - - w w */
+ 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, /* w - a u t h e n */
+ 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, /* t i c a t e - - */
+ 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, /* - - m e t h o d */
+ 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, /* - - - - g e t - */
+ 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, /* - - - s t a t u */
+ 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, /* s - - - - 2 0 0 */
+ 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, /* - O K - - - - v */
+ 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* e r s i o n - - */
+ 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, /* - - H T T P - 1 */
+ 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, /* - 1 - - - - u r */
+ 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, /* l - - - - p u b */
+ 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, /* l i c - - - - s */
+ 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, /* e t - c o o k i */
+ 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, /* e - - - - k e e */
+ 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, /* p - a l i v e - */
+ 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, /* - - - o r i g i */
+ 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, /* n 1 0 0 1 0 1 2 */
+ 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, /* 0 1 2 0 2 2 0 5 */
+ 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, /* 2 0 6 3 0 0 3 0 */
+ 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, /* 2 3 0 3 3 0 4 3 */
+ 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, /* 0 5 3 0 6 3 0 7 */
+ 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, /* 4 0 2 4 0 5 4 0 */
+ 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, /* 6 4 0 7 4 0 8 4 */
+ 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, /* 0 9 4 1 0 4 1 1 */
+ 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, /* 4 1 2 4 1 3 4 1 */
+ 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, /* 4 4 1 5 4 1 6 4 */
+ 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, /* 1 7 5 0 2 5 0 4 */
+ 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, /* 5 0 5 2 0 3 - N */
+ 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, /* o n - A u t h o */
+ 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, /* r i t a t i v e */
+ 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, /* - I n f o r m a */
+ 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, /* t i o n 2 0 4 - */
+ 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, /* N o - C o n t e */
+ 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, /* n t 3 0 1 - M o */
+ 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, /* v e d - P e r m */
+ 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, /* a n e n t l y 4 */
+ 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, /* 0 0 - B a d - R */
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, /* e q u e s t 4 0 */
+ 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, /* 1 - U n a u t h */
+ 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, /* o r i z e d 4 0 */
+ 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, /* 3 - F o r b i d */
+ 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, /* d e n 4 0 4 - N */
+ 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, /* o t - F o u n d */
+ 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, /* 5 0 0 - I n t e */
+ 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, /* r n a l - S e r */
+ 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, /* v e r - E r r o */
+ 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, /* r 5 0 1 - N o t */
+ 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, /* - I m p l e m e */
+ 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, /* n t e d 5 0 3 - */
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, /* S e r v i c e - */
+ 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, /* U n a v a i l a */
+ 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, /* b l e J a n - F */
+ 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, /* e b - M a r - A */
+ 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, /* p r - M a y - J */
+ 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, /* u n - J u l - A */
+ 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, /* u g - S e p t - */
+ 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, /* O c t - N o v - */
+ 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, /* D e c - 0 0 - 0 */
+ 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, /* 0 - 0 0 - M o n */
+ 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, /* - - T u e - - W */
+ 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, /* e d - - T h u - */
+ 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, /* - F r i - - S a */
+ 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, /* t - - S u n - - */
+ 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, /* G M T c h u n k */
+ 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, /* e d - t e x t - */
+ 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, /* h t m l - i m a */
+ 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, /* g e - p n g - i */
+ 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, /* m a g e - j p g */
+ 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, /* - i m a g e - g */
+ 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* i f - a p p l i */
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */
+ 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* m l - a p p l i */
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */
+ 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, /* h t m l - x m l */
+ 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, /* - t e x t - p l */
+ 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, /* a i n - t e x t */
+ 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, /* - j a v a s c r */
+ 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, /* i p t - p u b l */
+ 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, /* i c p r i v a t */
+ 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, /* e m a x - a g e */
+ 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, /* - g z i p - d e */
+ 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, /* f l a t e - s d */
+ 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* c h c h a r s e */
+ 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, /* t - u t f - 8 c */
+ 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, /* h a r s e t - i */
+ 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, /* s o - 8 8 5 9 - */
+ 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, /* 1 - u t f - - - */
+ 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e /* - e n q - 0 - */
+};
+
+
+static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = {
+ { 0, 6, "method", ngx_http_spdy_parse_method },
+ { 0, 6, "scheme", ngx_http_spdy_parse_scheme },
+ { 0, 4, "host", ngx_http_spdy_parse_host },
+ { 0, 4, "path", ngx_http_spdy_parse_path },
+ { 0, 7, "version", ngx_http_spdy_parse_version },
+};
+
+#define NGX_SPDY_REQUEST_HEADERS \
+ (sizeof(ngx_http_spdy_request_headers) \
+ / sizeof(ngx_http_spdy_request_header_t))
+
+
+void
+ngx_http_spdy_init(ngx_event_t *rev)
+{
+ int rc;
+ ngx_connection_t *c;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_connection_t *hc;
+ ngx_http_spdy_srv_conf_t *sscf;
+ ngx_http_spdy_main_conf_t *smcf;
+ ngx_http_spdy_connection_t *sc;
+
+ c = rev->data;
+ hc = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init spdy request");
+
+ c->log->action = "processing SPDY";
+
+ smcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_spdy_module);
+
+ if (smcf->recv_buffer == NULL) {
+ smcf->recv_buffer = ngx_palloc(ngx_cycle->pool, smcf->recv_buffer_size);
+ if (smcf->recv_buffer == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+ sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t));
+ if (sc == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ sc->connection = c;
+ sc->http_connection = hc;
+
+ sc->send_window = NGX_SPDY_CONNECTION_WINDOW;
+ sc->recv_window = NGX_SPDY_CONNECTION_WINDOW;
+
+ sc->init_window = NGX_SPDY_INIT_STREAM_WINDOW;
+
+ sc->handler = hc->proxy_protocol ? ngx_http_spdy_proxy_protocol
+ : ngx_http_spdy_state_head;
+
+ sc->zstream_in.zalloc = ngx_http_spdy_zalloc;
+ sc->zstream_in.zfree = ngx_http_spdy_zfree;
+ sc->zstream_in.opaque = sc;
+
+ rc = inflateInit(&sc->zstream_in);
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "inflateInit() failed: %d", rc);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ sc->zstream_out.zalloc = ngx_http_spdy_zalloc;
+ sc->zstream_out.zfree = ngx_http_spdy_zfree;
+ sc->zstream_out.opaque = sc;
+
+ sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_spdy_module);
+
+ rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp,
+ Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "deflateInit2() failed: %d", rc);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict,
+ sizeof(ngx_http_spdy_dict));
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "deflateSetDictionary() failed: %d", rc);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log);
+ if (sc->pool == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t));
+ if (cln == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ cln->handler = ngx_http_spdy_pool_cleanup;
+ cln->data = sc;
+
+ sc->streams_index = ngx_pcalloc(sc->pool,
+ ngx_http_spdy_streams_index_size(sscf)
+ * sizeof(ngx_http_spdy_stream_t *));
+ if (sc->streams_index == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (ngx_http_spdy_send_settings(sc) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (ngx_http_spdy_send_window_update(sc, 0, NGX_SPDY_MAX_WINDOW
+ - sc->recv_window)
+ == NGX_ERROR)
+ {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ sc->recv_window = NGX_SPDY_MAX_WINDOW;
+
+ ngx_queue_init(&sc->waiting);
+ ngx_queue_init(&sc->posted);
+
+ c->data = sc;
+
+ rev->handler = ngx_http_spdy_read_handler;
+ c->write->handler = ngx_http_spdy_write_handler;
+
+ ngx_http_spdy_read_handler(rev);
+}
+
+
+static void
+ngx_http_spdy_read_handler(ngx_event_t *rev)
+{
+ u_char *p, *end;
+ size_t available;
+ ssize_t n;
+ ngx_connection_t *c;
+ ngx_http_spdy_main_conf_t *smcf;
+ ngx_http_spdy_connection_t *sc;
+
+ c = rev->data;
+ sc = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler");
+
+ sc->blocked = 1;
+
+ smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx,
+ ngx_http_spdy_module);
+
+ available = smcf->recv_buffer_size - 2 * NGX_SPDY_STATE_BUFFER_SIZE;
+
+ do {
+ p = smcf->recv_buffer;
+
+ ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE);
+ end = p + sc->buffer_used;
+
+ n = c->recv(c, end, available);
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n == 0 && (sc->incomplete || sc->processing)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client prematurely closed connection");
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ ngx_http_spdy_finalize_connection(sc,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ end += n;
+
+ sc->buffer_used = 0;
+ sc->incomplete = 0;
+
+ do {
+ p = sc->handler(sc, p, end);
+
+ if (p == NULL) {
+ return;
+ }
+
+ } while (p != end);
+
+ } while (rev->ready);
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (sc->last_out && ngx_http_spdy_send_output_queue(sc) == NGX_ERROR) {
+ ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ sc->blocked = 0;
+
+ if (sc->processing) {
+ if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+ return;
+ }
+
+ ngx_http_spdy_handle_connection(sc);
+}
+
+
+static void
+ngx_http_spdy_write_handler(ngx_event_t *wev)
+{
+ ngx_int_t rc;
+ ngx_queue_t *q;
+ ngx_connection_t *c;
+ ngx_http_spdy_stream_t *stream;
+ ngx_http_spdy_connection_t *sc;
+
+ c = wev->data;
+ sc = c->data;
+
+ if (wev->timedout) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "spdy write event timed out");
+ ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler");
+
+ sc->blocked = 1;
+
+ rc = ngx_http_spdy_send_output_queue(sc);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ while (!ngx_queue_empty(&sc->posted)) {
+ q = ngx_queue_head(&sc->posted);
+
+ ngx_queue_remove(q);
+
+ stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue);
+
+ stream->handled = 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "run spdy stream %ui", stream->id);
+
+ wev = stream->request->connection->write;
+ wev->handler(wev);
+ }
+
+ sc->blocked = 0;
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ ngx_http_spdy_handle_connection(sc);
+}
+
+
+ngx_int_t
+ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc)
+{
+ int tcp_nodelay;
+ ngx_chain_t *cl;
+ ngx_event_t *wev;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_spdy_out_frame_t *out, *frame, *fn;
+
+ c = sc->connection;
+
+ if (c->error) {
+ return NGX_ERROR;
+ }
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return NGX_OK;
+ }
+
+ cl = NULL;
+ out = NULL;
+
+ for (frame = sc->last_out; frame; frame = fn) {
+ frame->last->next = cl;
+ cl = frame->first;
+
+ fn = frame->next;
+ frame->next = out;
+ out = frame;
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "spdy frame out: %p sid:%ui prio:%ui bl:%d len:%uz",
+ out, out->stream ? out->stream->id : 0, out->priority,
+ out->blocked, out->length);
+ }
+
+ cl = c->send_chain(c, cl, 0);
+
+ if (cl == NGX_CHAIN_ERROR) {
+ goto error;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx,
+ ngx_http_core_module);
+
+ if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+ goto error;
+ }
+
+ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+ if (ngx_tcp_push(c->fd) == -1) {
+ ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed");
+ goto error;
+ }
+
+ c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+ tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0;
+
+ } else {
+ tcp_nodelay = 1;
+ }
+
+ if (tcp_nodelay
+ && clcf->tcp_nodelay
+ && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET)
+ {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay");
+
+ if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+ (const void *) &tcp_nodelay, sizeof(int))
+ == -1)
+ {
+#if (NGX_SOLARIS)
+ /* Solaris returns EINVAL if a socket has been shut down */
+ c->log_error = NGX_ERROR_IGNORE_EINVAL;
+#endif
+
+ ngx_connection_error(c, ngx_socket_errno,
+ "setsockopt(TCP_NODELAY) failed");
+
+ c->log_error = NGX_ERROR_INFO;
+ goto error;
+ }
+
+ c->tcp_nodelay = NGX_TCP_NODELAY_SET;
+ }
+
+ if (cl) {
+ ngx_add_timer(wev, clcf->send_timeout);
+
+ } else {
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+ }
+
+ for ( /* void */ ; out; out = fn) {
+ fn = out->next;
+
+ if (out->handler(sc, out) != NGX_OK) {
+ out->blocked = 1;
+ out->priority = NGX_SPDY_HIGHEST_PRIORITY;
+ break;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "spdy frame sent: %p sid:%ui bl:%d len:%uz",
+ out, out->stream ? out->stream->id : 0,
+ out->blocked, out->length);
+ }
+
+ frame = NULL;
+
+ for ( /* void */ ; out; out = fn) {
+ fn = out->next;
+ out->next = frame;
+ frame = out;
+ }
+
+ sc->last_out = frame;
+
+ return NGX_OK;
+
+error:
+
+ c->error = 1;
+
+ if (!sc->blocked) {
+ ngx_post_event(wev, &ngx_posted_events);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc)
+{
+ ngx_connection_t *c;
+ ngx_http_spdy_srv_conf_t *sscf;
+
+ if (sc->last_out || sc->processing) {
+ return;
+ }
+
+ c = sc->connection;
+
+ if (c->error) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (c->buffered) {
+ return;
+ }
+
+ sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+ ngx_http_spdy_module);
+ if (sc->incomplete) {
+ ngx_add_timer(c->read, sscf->recv_timeout);
+ return;
+ }
+
+ if (ngx_terminate || ngx_exiting) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_destroy_pool(sc->pool);
+
+ sc->pool = NULL;
+ sc->free_ctl_frames = NULL;
+ sc->free_fake_connections = NULL;
+
+#if (NGX_HTTP_SSL)
+ if (c->ssl) {
+ ngx_ssl_free_buffer(c);
+ }
+#endif
+
+ c->destroyed = 1;
+ c->idle = 1;
+ ngx_reusable_connection(c, 1);
+
+ c->write->handler = ngx_http_empty_handler;
+ c->read->handler = ngx_http_spdy_keepalive_handler;
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+ ngx_add_timer(c->read, sscf->keepalive_timeout);
+}
+
+
+static u_char *
+ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ ngx_log_t *log;
+
+ log = sc->connection->log;
+ log->action = "reading PROXY protocol";
+
+ pos = ngx_proxy_protocol_read(sc->connection, pos, end);
+
+ log->action = "processing SPDY";
+
+ if (pos == NULL) {
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ return ngx_http_spdy_state_complete(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ uint32_t head, flen;
+ ngx_uint_t type;
+
+ if (end - pos < NGX_SPDY_FRAME_HEADER_SIZE) {
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_head);
+ }
+
+ head = ngx_spdy_frame_parse_uint32(pos);
+
+ pos += sizeof(uint32_t);
+
+ flen = ngx_spdy_frame_parse_uint32(pos);
+
+ sc->flags = ngx_spdy_frame_flags(flen);
+ sc->length = ngx_spdy_frame_length(flen);
+
+ pos += sizeof(uint32_t);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "process spdy frame head:%08XD f:%Xd l:%uz",
+ head, sc->flags, sc->length);
+
+ if (ngx_spdy_ctl_frame_check(head)) {
+ type = ngx_spdy_ctl_frame_type(head);
+
+ switch (type) {
+
+ case NGX_SPDY_SYN_STREAM:
+ return ngx_http_spdy_state_syn_stream(sc, pos, end);
+
+ case NGX_SPDY_SYN_REPLY:
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent unexpected SYN_REPLY frame");
+ return ngx_http_spdy_state_protocol_error(sc);
+
+ case NGX_SPDY_RST_STREAM:
+ return ngx_http_spdy_state_rst_stream(sc, pos, end);
+
+ case NGX_SPDY_SETTINGS:
+ return ngx_http_spdy_state_settings(sc, pos, end);
+
+ case NGX_SPDY_PING:
+ return ngx_http_spdy_state_ping(sc, pos, end);
+
+ case NGX_SPDY_GOAWAY:
+ return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */
+
+ case NGX_SPDY_HEADERS:
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent unexpected HEADERS frame");
+ return ngx_http_spdy_state_protocol_error(sc);
+
+ case NGX_SPDY_WINDOW_UPDATE:
+ return ngx_http_spdy_state_window_update(sc, pos, end);
+
+ default:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "spdy control frame with unknown type %ui", type);
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+ }
+
+ if (ngx_spdy_data_frame_check(head)) {
+ sc->stream = ngx_http_spdy_get_stream_by_id(sc, head);
+ return ngx_http_spdy_state_data(sc, pos, end);
+ }
+
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent invalid frame");
+
+ return ngx_http_spdy_state_protocol_error(sc);
+}
+
+
+static u_char *
+ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ ngx_uint_t sid, prio;
+ ngx_http_spdy_stream_t *stream;
+ ngx_http_spdy_srv_conf_t *sscf;
+
+ if (end - pos < NGX_SPDY_SYN_STREAM_SIZE) {
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_syn_stream);
+ }
+
+ if (sc->length <= NGX_SPDY_SYN_STREAM_SIZE) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent SYN_STREAM frame with incorrect length %uz",
+ sc->length);
+
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ sc->length -= NGX_SPDY_SYN_STREAM_SIZE;
+
+ sid = ngx_spdy_frame_parse_sid(pos);
+ prio = pos[8] >> 5;
+
+ pos += NGX_SPDY_SYN_STREAM_SIZE;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio);
+
+ if (sid % 2 == 0 || sid <= sc->last_sid) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent SYN_STREAM frame "
+ "with invalid Stream-ID %ui", sid);
+
+ stream = ngx_http_spdy_get_stream_by_id(sc, sid);
+
+ if (stream) {
+ if (ngx_http_spdy_terminate_stream(sc, stream,
+ NGX_SPDY_PROTOCOL_ERROR)
+ != NGX_OK)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+ }
+
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ sc->last_sid = sid;
+
+ sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+ ngx_http_spdy_module);
+
+ if (sc->processing >= sscf->concurrent_streams) {
+
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "concurrent streams exceeded %ui", sc->processing);
+
+ if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM,
+ prio)
+ != NGX_OK)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+
+ stream = ngx_http_spdy_create_stream(sc, sid, prio);
+ if (stream == NULL) {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ stream->in_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0;
+
+ stream->request->request_length = NGX_SPDY_FRAME_HEADER_SIZE
+ + NGX_SPDY_SYN_STREAM_SIZE
+ + sc->length;
+
+ sc->stream = stream;
+
+ return ngx_http_spdy_state_headers(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ int z;
+ size_t size;
+ ngx_buf_t *buf;
+ ngx_int_t rc;
+ ngx_http_request_t *r;
+
+ size = end - pos;
+
+ if (size == 0) {
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_headers);
+ }
+
+ if (size > sc->length) {
+ size = sc->length;
+ }
+
+ r = sc->stream->request;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "process spdy header block %uz of %uz", size, sc->length);
+
+ buf = r->header_in;
+
+ sc->zstream_in.next_in = pos;
+ sc->zstream_in.avail_in = size;
+ sc->zstream_in.next_out = buf->last;
+
+ /* one byte is reserved for null-termination of the last header value */
+ sc->zstream_in.avail_out = buf->end - buf->last - 1;
+
+ z = inflate(&sc->zstream_in, Z_NO_FLUSH);
+
+ if (z == Z_NEED_DICT) {
+ z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict,
+ sizeof(ngx_http_spdy_dict));
+
+ if (z != Z_OK) {
+ if (z == Z_DATA_ERROR) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent SYN_STREAM frame with header "
+ "block encoded using wrong dictionary: %ul",
+ (u_long) sc->zstream_in.adler);
+
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "inflateSetDictionary() failed: %d", z);
+
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "spdy inflateSetDictionary(): %d", z);
+
+ z = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH)
+ : Z_OK;
+ }
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+ sc->zstream_in.next_in, sc->zstream_in.next_out,
+ sc->zstream_in.avail_in, sc->zstream_in.avail_out,
+ z);
+
+ if (z != Z_OK) {
+ return ngx_http_spdy_state_inflate_error(sc, z);
+ }
+
+ sc->length -= sc->zstream_in.next_in - pos;
+ pos = sc->zstream_in.next_in;
+
+ buf->last = sc->zstream_in.next_out;
+
+ if (r->headers_in.headers.part.elts == NULL) {
+
+ if (buf->last - buf->pos < NGX_SPDY_NV_NUM_SIZE) {
+
+ if (sc->length == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "premature end of spdy header block");
+
+ return ngx_http_spdy_state_headers_error(sc, pos, end);
+ }
+
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_headers);
+ }
+
+ sc->entries = ngx_spdy_frame_parse_uint32(buf->pos);
+
+ buf->pos += NGX_SPDY_NV_NUM_SIZE;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "spdy header block has %ui entries",
+ sc->entries);
+
+ if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ ngx_http_spdy_close_stream(sc->stream,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+
+ if (ngx_array_init(&r->headers_in.cookies, r->pool, 2,
+ sizeof(ngx_table_elt_t *))
+ != NGX_OK)
+ {
+ ngx_http_spdy_close_stream(sc->stream,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+ }
+
+ while (sc->entries) {
+
+ rc = ngx_http_spdy_parse_header(r);
+
+ switch (rc) {
+
+ case NGX_DONE:
+ sc->entries--;
+
+ case NGX_OK:
+ break;
+
+ case NGX_AGAIN:
+
+ if (sc->zstream_in.avail_in) {
+
+ rc = ngx_http_spdy_alloc_large_header_buffer(r);
+
+ if (rc == NGX_DECLINED) {
+ ngx_http_finalize_request(r,
+ NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+
+ if (rc != NGX_OK) {
+ ngx_http_spdy_close_stream(sc->stream,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+
+ /* null-terminate the last processed header name or value */
+ *buf->pos = '\0';
+
+ buf = r->header_in;
+
+ sc->zstream_in.next_out = buf->last;
+
+ /* one byte is reserved for null-termination */
+ sc->zstream_in.avail_out = buf->end - buf->last - 1;
+
+ z = inflate(&sc->zstream_in, Z_NO_FLUSH);
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+ sc->zstream_in.next_in, sc->zstream_in.next_out,
+ sc->zstream_in.avail_in, sc->zstream_in.avail_out,
+ z);
+
+ if (z != Z_OK) {
+ return ngx_http_spdy_state_inflate_error(sc, z);
+ }
+
+ sc->length -= sc->zstream_in.next_in - pos;
+ pos = sc->zstream_in.next_in;
+
+ buf->last = sc->zstream_in.next_out;
+
+ continue;
+ }
+
+ if (sc->length == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "premature end of spdy header block");
+
+ return ngx_http_spdy_state_headers_error(sc, pos, end);
+ }
+
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_headers);
+
+ case NGX_HTTP_PARSE_INVALID_HEADER:
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+
+ default: /* NGX_ERROR */
+ return ngx_http_spdy_state_headers_error(sc, pos, end);
+ }
+
+ /* a header line has been parsed successfully */
+
+ rc = ngx_http_spdy_handle_request_header(r);
+
+ if (rc != NGX_OK) {
+ if (rc == NGX_HTTP_PARSE_INVALID_HEADER) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+
+ if (rc != NGX_ABORT) {
+ ngx_http_spdy_close_stream(sc->stream,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+ }
+ }
+
+ if (buf->pos != buf->last || sc->zstream_in.avail_in) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "incorrect number of spdy header block entries");
+
+ return ngx_http_spdy_state_headers_error(sc, pos, end);
+ }
+
+ if (sc->length) {
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_headers);
+ }
+
+ /* null-terminate the last header value */
+ *buf->pos = '\0';
+
+ ngx_http_spdy_run_request(r);
+
+ return ngx_http_spdy_state_complete(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ int n;
+ size_t size;
+ u_char buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE];
+
+ if (sc->length == 0) {
+ return ngx_http_spdy_state_complete(sc, pos, end);
+ }
+
+ size = end - pos;
+
+ if (size == 0) {
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_headers_skip);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "spdy header block skip %uz of %uz", size, sc->length);
+
+ sc->zstream_in.next_in = pos;
+ sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length;
+
+ while (sc->zstream_in.avail_in) {
+ sc->zstream_in.next_out = buffer;
+ sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE;
+
+ n = inflate(&sc->zstream_in, Z_NO_FLUSH);
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+ sc->zstream_in.next_in, sc->zstream_in.next_out,
+ sc->zstream_in.avail_in, sc->zstream_in.avail_out,
+ n);
+
+ if (n != Z_OK) {
+ return ngx_http_spdy_state_inflate_error(sc, n);
+ }
+ }
+
+ pos = sc->zstream_in.next_in;
+
+ if (size < sc->length) {
+ sc->length -= size;
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_headers_skip);
+ }
+
+ return ngx_http_spdy_state_complete(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ ngx_http_spdy_stream_t *stream;
+
+ stream = sc->stream;
+
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent SYN_STREAM frame for stream %ui "
+ "with invalid header block", stream->id);
+
+ if (ngx_http_spdy_send_rst_stream(sc, stream->id, NGX_SPDY_PROTOCOL_ERROR,
+ stream->priority)
+ != NGX_OK)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ stream->out_closed = 1;
+
+ ngx_http_spdy_close_stream(stream, NGX_HTTP_BAD_REQUEST);
+
+ return ngx_http_spdy_state_headers_skip(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ size_t delta;
+ ngx_uint_t sid;
+ ngx_event_t *wev;
+ ngx_queue_t *q;
+ ngx_http_spdy_stream_t *stream;
+
+ if (end - pos < NGX_SPDY_WINDOW_UPDATE_SIZE) {
+ return ngx_http_spdy_state_save(sc, pos, end,
+ ngx_http_spdy_state_window_update);
+ }
+
+ if (sc->length != NGX_SPDY_WINDOW_UPDATE_SIZE) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent WINDOW_UPDATE frame "
+ "with incorrect length %uz", sc->length);
+
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ sid = ngx_spdy_frame_parse_sid(pos);
+
+ pos += NGX_SPDY_SID_SIZE;
+
+ delta = ngx_spdy_frame_parse_delta(pos);
+
+ pos += NGX_SPDY_DELTA_SIZE;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "spdy WINDOW_UPDATE sid:%ui delta:%uz", sid, delta);
+
+ if (sid) {
+ stream = ngx_http_spdy_get_stream_by_id(sc, sid);
+
+ if (stream == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "unknown spdy stream");
+
+ return ngx_http_spdy_state_complete(sc, pos, end);
+ }
+
+ if (stream->send_window > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) {
+
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client violated flow control for stream %ui: "
+ "received WINDOW_UPDATE frame with delta %uz "
+ "not allowed for window %z",
+ sid, delta, stream->send_window);
+
+ if (ngx_http_spdy_terminate_stream(sc, stream,
+ NGX_SPDY_FLOW_CONTROL_ERROR)
+ == NGX_ERROR)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ return ngx_http_spdy_state_complete(sc, pos, end);
+ }
+
+ stream->send_window += delta;
+
+ if (stream->exhausted) {
+ stream->exhausted = 0;
+
+ wev = stream->request->connection->write;
+
+ if (!wev->timer_set) {
+ wev->delayed = 0;
+ wev->handler(wev);
+ }
+ }
+
+ } else {
+ sc->send_window += delta;
+
+ if (sc->send_window > NGX_SPDY_MAX_WINDOW) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client violated connection flow control: "
+ "received WINDOW_UPDATE frame with delta %uz "
+ "not allowed for window %uz",
+ delta, sc->send_window);
+
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ while (!ngx_queue_empty(&sc->waiting)) {
+ q = ngx_queue_head(&sc->waiting);
+
+ ngx_queue_remove(q);
+
+ stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue);
+
+ stream->handled = 0;
+
+ wev = stream->request->connection->write;
+
+ if (!wev->timer_set) {
+ wev->delayed = 0;
+ wev->handler(wev);
+
+ if (sc->send_window == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ return ngx_http_spdy_state_complete(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ ngx_http_spdy_stream_t *stream;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "spdy DATA frame");
+
+ if (sc->length > sc->recv_window) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client violated connection flow control: "
+ "received DATA frame length %uz, available window %uz",
+ sc->length, sc->recv_window);
+
+ return ngx_http_spdy_state_protocol_error(sc);
+ }
+
+ sc->recv_window -= sc->length;
+
+ if (sc->recv_window < NGX_SPDY_MAX_WINDOW / 4) {
+
+ if (ngx_http_spdy_send_window_update(sc, 0,
+ NGX_SPDY_MAX_WINDOW
+ - sc->recv_window)
+ == NGX_ERROR)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ sc->recv_window = NGX_SPDY_MAX_WINDOW;
+ }
+
+ stream = sc->stream;
+
+ if (stream == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "unknown spdy stream");
+
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+
+ if (sc->length > stream->recv_window) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client violated flow control for stream %ui: "
+ "received DATA frame length %uz, available window %uz",
+ stream->id, sc->length, stream->recv_window);
+
+ if (ngx_http_spdy_terminate_stream(sc, stream,
+ NGX_SPDY_FLOW_CONTROL_ERROR)
+ == NGX_ERROR)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+
+ stream->recv_window -= sc->length;
+
+ if (stream->recv_window < NGX_SPDY_STREAM_WINDOW / 4) {
+
+ if (ngx_http_spdy_send_window_update(sc, stream->id,
+ NGX_SPDY_STREAM_WINDOW
+ - stream->recv_window)
+ == NGX_ERROR)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ stream->recv_window = NGX_SPDY_STREAM_WINDOW;
+ }
+
+ if (stream->in_closed) {
+ ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+ "client sent DATA frame for half-closed stream %ui",
+ stream->id);
+
+ if (ngx_http_spdy_terminate_stream(sc, stream,
+ NGX_SPDY_STREAM_ALREADY_CLOSED)
+ == NGX_ERROR)
+ {
+ return ngx_http_spdy_state_internal_error(sc);
+ }
+
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+
+ return ngx_http_spdy_state_read_data(sc, pos, end);
+}
+
+
+static u_char *
+ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, u_char *pos,
+ u_char *end)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *buf;
+ ngx_int_t rc;
+ ngx_temp_file_t *tf;
+ ngx_http_request_t *r;
+ ngx_http_spdy_stream_t *stream;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+
+ stream = sc->stream;
+
+ if (stream == NULL) {
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+
+ if (stream->skip_data) {
+
+ if (sc->flags & NGX_SPDY_FLAG_FIN) {
+ stream->in_closed = 1;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+ "skipping spdy DATA frame, reason: %d",
+ stream->skip_data);
+
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+
+ size = end - pos;
+
+ if (size > sc->length) {
+ size = sc->length;
+ }
+
+ r = stream->request;
+
+ if (r->request_body == NULL
+ && ngx_http_spdy_init_request_body(r) != NGX_OK)
+ {
+ stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR;
+ return ngx_http_spdy_state_skip(sc, pos, end);
+ }
+
+ rb = r->request_body;
+ tf = rb->temp_file;
+ buf = rb->buf;
+
+ if (size) {
+ rb->rest += size;
+
+ if (r->headers_in.content_length_n != -1
+ && r->headers_in.content_length_n < rb->rest)
+ {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client intended to send body data "
+ "larger than declared");
+
+ stream->skip_data = NGX_SPDY_DATA_ERROR;
+ goto error;
+
+ } else {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->client_max_body_size
+ && clcf->client_max_body_size < rb->rest)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client intended to send "
+ "too large chunked body: %O bytes", rb->rest);
+
+ stream->skip_data = NGX_SPDY_DATA_ERROR;
+ goto error;
+ }
+ }
+
+ sc->length -= size;
+
+ if (tf) {
+ buf->start = pos;
+ buf->pos = pos;
+
+ pos += size;
+
+ buf->end = pos;
+ buf->last = pos;
+
+ n = ngx_write_chain_to_temp_file(tf, rb->bufs);
+
+ /* TODO: n == 0 or not complete and level event */
+
+ if (n == NGX_ERROR) {
+ stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR;
+ goto error;