diff --git a/README.md b/README.md index 98ecd21..ca52095 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Here is the basic patch content. | openssl-equal-pre2.patch | **_Not support_** draft **28**. | | openssl-equal-pre7.patch
openssl-equal-pre8.patch | TLS 1.3 cipher settings **_can not_** be changed on _nginx_. | | openssl-equal-pre7_ciphers.patch
openssl-equal-pre8_ciphers.patch | TLS 1.3 cipher settings **_can_** be changed on _nginx_. | +| nginx_hpack_push.patch | _Patch both_ the HPACK patch and the **PUSH ERROR**. | +| nginx_hpack_push_fix.patch | _Patch only_ the **PUSH ERROR** of the hpack patch. (If the HPACK patch has already been completed) | **The "_ciphers" patch file is a temporary change to the TLS 1.3 configuration.** diff --git a/nginx_hpack_push.patch b/nginx_hpack_push.patch index 02da832..0d65f97 100644 --- a/nginx_hpack_push.patch +++ b/nginx_hpack_push.patch @@ -1,29 +1,596 @@ +diff --git a/auto/modules b/auto/modules +index 73a9bae9..67fe2cb3 100644 +--- a/auto/modules ++++ b/auto/modules +@@ -438,6 +438,10 @@ if [ $HTTP = YES ]; then + . 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= +diff --git a/auto/options b/auto/options +index 59f0449d..4abd20ac 100644 +--- a/auto/options ++++ b/auto/options +@@ -59,6 +59,7 @@ HTTP_CHARSET=YES + HTTP_GZIP=YES + HTTP_SSL=NO + HTTP_V2=NO ++HTTP_V2_HPACK_ENC=NO + HTTP_SSI=YES + HTTP_POSTPONE=NO + HTTP_REALIP=NO +@@ -223,6 +224,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" + + --with-http_ssl_module) HTTP_SSL=YES ;; + --with-http_v2_module) HTTP_V2=YES ;; ++ --with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;; + --with-http_realip_module) HTTP_REALIP=YES ;; + --with-http_addition_module) HTTP_ADDITION=YES ;; + --with-http_xslt_module) HTTP_XSLT=YES ;; +@@ -434,6 +436,7 @@ cat << END + + --with-http_ssl_module enable ngx_http_ssl_module + --with-http_v2_module enable ngx_http_v2_module ++ --with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc + --with-http_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 --git a/src/core/ngx_murmurhash.c b/src/core/ngx_murmurhash.c +index 5ade658d..4932f20d 100644 +--- a/src/core/ngx_murmurhash.c ++++ b/src/core/ngx_murmurhash.c +@@ -50,3 +50,63 @@ ngx_murmur_hash2(u_char *data, size_t len) + + 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 --git a/src/core/ngx_murmurhash.h b/src/core/ngx_murmurhash.h +index 54e867d3..322b3df9 100644 +--- a/src/core/ngx_murmurhash.h ++++ b/src/core/ngx_murmurhash.h +@@ -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 --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c +index 77ebb847..303d157f 100644 +--- a/src/http/v2/ngx_http_v2.c ++++ b/src/http/v2/ngx_http_v2.c +@@ -272,6 +272,8 @@ ngx_http_v2_init(ngx_event_t *rev) + + h2c->table_update = 1; + ++ h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE; ++ + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + h2c->concurrent_pushes = h2scf->concurrent_pushes; +@@ -2075,6 +2077,17 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, + h2c->concurrent_pushes = ngx_min(value, h2scf->concurrent_pushes); + break; + ++ case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: ++ ++ if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { ++ h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE; ++ } else { ++ h2c->max_hpack_table_size = value; ++ } ++ ++ h2c->indicate_resize = 1; ++ break; ++ + default: + break; + } diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h -index ae826af..41086a6 100644 +index ebd0e77c..bb89a634 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h -@@ -490,4 +490,8 @@ ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); - ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ - val.data, val.len, tmp); +@@ -52,6 +52,14 @@ + #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) + #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 -+#define ngx_http_v2_write_header_pot(key, val) \ ++#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */ ++#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128) ++#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */ ++#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */ ++ ++#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096 ++#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */ ++ + #define NGX_HTTP_V2_DEFAULT_WEIGHT 16 + + +@@ -115,6 +123,46 @@ typedef struct { + } ngx_http_v2_hpack_t; + + ++#if (NGX_HTTP_V2_HPACK_ENC) ++typedef struct { ++ uint64_t hash_val; ++ uint32_t index; ++ uint16_t pos; ++ uint16_t klen, vlen; ++ uint16_t size; ++ uint16_t next; ++} ngx_http_v2_hpack_enc_entry_t; ++ ++ ++typedef struct { ++ uint64_t hash_val; ++ uint32_t index; ++ uint16_t pos; ++ uint16_t klen; ++} ngx_http_v2_hpack_name_entry_t; ++ ++ ++typedef struct { ++ size_t size; /* size as defined in RFC 7541 */ ++ uint32_t top; /* the last entry */ ++ uint32_t pos; ++ uint16_t n_elems; /* number of elements */ ++ uint16_t base; /* index of the oldest entry */ ++ uint16_t last; /* index of the newest entry */ ++ ++ /* hash table for dynamic entries, instead using a generic hash table, ++ which would be too slow to process a significant amount of headers, ++ this table is not determenistic, and might ocasionally fail to insert ++ a value, at the cost of slightly worse compression, but significantly ++ faster performance */ ++ ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ]; ++ ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ]; ++ u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE + ++ HPACK_ENC_MAX_ENTRY]; ++} ngx_http_v2_hpack_enc_t; ++#endif ++ ++ + struct ngx_http_v2_connection_s { + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; +@@ -130,6 +178,8 @@ struct ngx_http_v2_connection_s { + + size_t frame_size; + ++ size_t max_hpack_table_size; ++ + ngx_queue_t waiting; + + ngx_http_v2_state_t state; +@@ -157,6 +207,11 @@ struct ngx_http_v2_connection_s { + unsigned blocked:1; + unsigned goaway:1; + unsigned push_disabled:1; ++ unsigned indicate_resize:1; ++ ++#if (NGX_HTTP_V2_HPACK_ENC) ++ ngx_http_v2_hpack_enc_t hpack_enc; ++#endif + }; + + +@@ -198,6 +253,8 @@ struct ngx_http_v2_stream_s { + + ngx_array_t *cookies; + ++ size_t header_limit; ++ + ngx_pool_t *pool; + + unsigned waiting:1; +@@ -410,4 +467,31 @@ u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + + ++u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, ++ u_char *tmp, ngx_uint_t lower); ++ ++u_char * ++ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value); ++ ++#define ngx_http_v2_write_name(dst, src, len, tmp) \ ++ ngx_http_v2_string_encode(dst, src, len, tmp, 1) ++#define ngx_http_v2_write_value(dst, src, len, tmp) \ ++ ngx_http_v2_string_encode(dst, src, len, tmp, 0) ++ ++u_char * ++ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, ++ u_char *key, size_t key_len, u_char *value, size_t value_len, ++ u_char *tmp); ++ ++void ++ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c); ++ ++#define ngx_http_v2_write_header_str(key, value) \ + ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ -+ val->data, val->len, tmp); ++ (u_char *) value, sizeof(value) - 1, tmp); ++ ++#define ngx_http_v2_write_header_tbl(key, val) \ ++ ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \ ++ val.data, val.len, tmp); + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ +diff --git a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c +index ac792084..d1fb7217 100644 +--- a/src/http/v2/ngx_http_v2_encode.c ++++ b/src/http/v2/ngx_http_v2_encode.c +@@ -10,7 +10,7 @@ + #include + + +-static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ++u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, + ngx_uint_t value); + + +@@ -40,7 +40,7 @@ ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, + } + + +-static u_char * ++u_char * + ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) + { + if (value < prefix) { diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c -index 6a210d4..bd9a02f 100644 +index 029e8ece..d293f06c 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c -@@ -955,6 +955,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, +@@ -23,10 +23,53 @@ + #define ngx_http_v2_literal_size(h) \ + (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) + ++#define ngx_http_v2_indexed(i) (128 + (i)) ++#define ngx_http_v2_inc_indexed(i) (64 + (i)) ++ ++#define NGX_HTTP_V2_ENCODE_RAW 0 ++#define NGX_HTTP_V2_ENCODE_HUFF 0x80 ++ ++#define NGX_HTTP_V2_AUTHORITY_INDEX 1 ++#define NGX_HTTP_V2_METHOD_GET_INDEX 2 ++#define NGX_HTTP_V2_PATH_INDEX 4 ++ ++#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 ++#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 ++ ++#define NGX_HTTP_V2_STATUS_INDEX 8 ++#define NGX_HTTP_V2_STATUS_200_INDEX 8 ++#define NGX_HTTP_V2_STATUS_204_INDEX 9 ++#define NGX_HTTP_V2_STATUS_206_INDEX 10 ++#define NGX_HTTP_V2_STATUS_304_INDEX 11 ++#define NGX_HTTP_V2_STATUS_400_INDEX 12 ++#define NGX_HTTP_V2_STATUS_404_INDEX 13 ++#define NGX_HTTP_V2_STATUS_500_INDEX 14 ++ ++#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 ++#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 ++#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 ++#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 ++#define NGX_HTTP_V2_DATE_INDEX 33 ++#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 ++#define NGX_HTTP_V2_LOCATION_INDEX 46 ++#define NGX_HTTP_V2_SERVER_INDEX 54 ++#define NGX_HTTP_V2_USER_AGENT_INDEX 58 ++#define NGX_HTTP_V2_VARY_INDEX 59 + + #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 + + ++static const struct { ++ u_char *name; ++ u_char const len; ++} push_header[] = { ++ { (u_char*)":authority" , 10 }, ++ { (u_char*)"accept-encoding" , 15 }, ++ { (u_char*)"accept-language" , 15 }, ++ { (u_char*)"user-agent" , 10 } ++}; ++ ++ + typedef struct { + ngx_str_t name; + u_char index; +@@ -155,11 +198,9 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) + #endif + + static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); +- static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; + + static size_t nginx_ver_build_len = + ngx_http_v2_literal_size(NGINX_VER_BUILD); +- static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; + + stream = r->stream; + +@@ -435,7 +476,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) + } + + tmp = ngx_palloc(r->pool, tmp_len); +- pos = ngx_pnalloc(r->pool, len); ++ pos = ngx_pnalloc(r->pool, len + 15 + 1); + + if (pos == NULL || tmp == NULL) { + return NGX_ERROR; +@@ -443,11 +484,16 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) + + start = pos; + +- if (h2c->table_update) { +- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 table size update: 0"); +- *pos++ = (1 << 5) | 0; +- h2c->table_update = 0; ++ h2c = r->stream->connection; ++ ++ if (h2c->indicate_resize) { ++ *pos = 32; ++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), ++ h2c->max_hpack_table_size); ++ h2c->indicate_resize = 0; ++#if (NGX_HTTP_V2_HPACK_ENC) ++ ngx_http_v2_table_resize(h2c); ++#endif + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +@@ -458,67 +504,28 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) + *pos++ = status; + + } else { +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); +- *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; +- pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); ++ ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status); ++ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status", ++ sizeof(":status") - 1, pos + 8, 3, tmp); + } + + if (r->headers_out.server == NULL) { +- +- if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { +- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"server: %s\"", +- NGINX_VER); +- +- } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { +- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"server: %s\"", +- NGINX_VER_BUILD); +- +- } else { +- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"server: nginx\""); +- } +- +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); +- + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { +- if (nginx_ver[0] == '\0') { +- p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, +- sizeof(NGINX_VER) - 1, tmp); +- nginx_ver_len = p - nginx_ver; +- } +- +- pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); ++ pos = ngx_http_v2_write_header_str("server", NGINX_VER); + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { +- if (nginx_ver_build[0] == '\0') { +- p = ngx_http_v2_write_value(nginx_ver_build, +- (u_char *) NGINX_VER_BUILD, +- sizeof(NGINX_VER_BUILD) - 1, tmp); +- nginx_ver_build_len = p - nginx_ver_build; +- } +- +- pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); ++ pos = ngx_http_v2_write_header_str("server", NGINX_VER_BUILD); + + } else { +- pos = ngx_cpymem(pos, nginx, sizeof(nginx)); ++ pos = ngx_http_v2_write_header_str("server", "nginx"); + } + } + + if (r->headers_out.date == NULL) { +- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"date: %V\"", +- &ngx_cached_http_time); +- +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); +- pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, +- ngx_cached_http_time.len, tmp); ++ pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time); + } + + if (r->headers_out.content_type.len) { +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); +- + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { +@@ -544,64 +551,36 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) + r->headers_out.content_type.data = p - len; + } + +- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"content-type: %V\"", +- &r->headers_out.content_type); +- +- pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, +- r->headers_out.content_type.len, tmp); ++ pos = ngx_http_v2_write_header_tbl("content-type", ++ r->headers_out.content_type); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { +- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"content-length: %O\"", +- r->headers_out.content_length_n); +- +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); +- +- p = pos; +- pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); +- *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); ++ p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n); ++ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length", ++ sizeof("content-length") - 1, pos + 15, ++ p - (pos + 15), tmp); + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); +- +- ngx_http_time(pos, r->headers_out.last_modified_time); ++ ngx_http_time(pos + 14, r->headers_out.last_modified_time); + len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; +- +- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"last-modified: %*s\"", +- len, pos); +- +- /* +- * Date will always be encoded using huffman in the temporary buffer, +- * so it's safe here to use src and dst pointing to the same address. +- */ +- pos = ngx_http_v2_write_value(pos, pos, len, tmp); ++ pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified", ++ sizeof("last-modified") - 1, pos + 14, ++ len, tmp); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { +- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"location: %V\"", +- &r->headers_out.location->value); +- +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); +- pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, +- r->headers_out.location->value.len, tmp); ++ pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value); + } + + #if (NGX_HTTP_GZIP) + if (r->gzip_vary) { +- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"vary: Accept-Encoding\""); +- +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); +- pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); ++ pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding"); + } + #endif + +@@ -624,23 +603,10 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) + continue; + } + +-#if (NGX_DEBUG) +- if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { +- ngx_strlow(tmp, header[i].key.data, header[i].key.len); +- +- ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 output header: \"%*s: %V\"", +- header[i].key.len, tmp, &header[i].value); +- } +-#endif +- +- *pos++ = 0; ++ pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data, ++ header[i].key.len, header[i].value.data, ++ header[i].value.len, tmp); + +- pos = ngx_http_v2_write_name(pos, header[i].key.data, +- header[i].key.len, tmp); +- +- pos = ngx_http_v2_write_value(pos, header[i].value.data, +- header[i].value.len, tmp); + } + + fin = r->header_only +@@ -998,6 +964,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { len += binary[i].len; -+ len += sizeof(ph[i].name); ++ len += push_header[i].len + 1; } pos = ngx_pnalloc(r->pool, len); -@@ -964,12 +965,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, +@@ -1007,12 +974,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, start = pos; @@ -47,7 +614,7 @@ index 6a210d4..bd9a02f 100644 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":method: GET\""); -@@ -979,8 +985,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, +@@ -1022,8 +994,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push header: \":path: %V\"", path); @@ -57,7 +624,7 @@ index 6a210d4..bd9a02f 100644 #if (NGX_HTTP_SSL) if (fc->ssl) { -@@ -1003,11 +1008,26 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, +@@ -1046,11 +1017,15 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, continue; } @@ -68,20 +635,448 @@ index 6a210d4..bd9a02f 100644 &ph[i].name, &(*h)->value); - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); -+ switch(i) { -+ case 0: -+ pos = ngx_http_v2_write_header_pot(":authority", value); -+ break; -+ case 1: -+ pos = ngx_http_v2_write_header_pot("accept-encoding", value); -+ break; -+ case 2: -+ pos = ngx_http_v2_write_header_pot("accept-language", value); -+ break; -+ case 3: -+ pos = ngx_http_v2_write_header_pot("user-agent", value); -+ break; -+ } ++ pos = ngx_http_v2_write_header(h2c, pos, ++ push_header[i].name, push_header[i].len, value->data, value->len, ++ tmp); } frame = ngx_http_v2_create_push_frame(r, start, pos); +diff --git a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c +index 7d49803f..b9ee2048 100644 +--- a/src/http/v2/ngx_http_v2_table.c ++++ b/src/http/v2/ngx_http_v2_table.c +@@ -361,3 +361,434 @@ ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size) + + return NGX_OK; + } ++ ++ ++#if (NGX_HTTP_V2_HPACK_ENC) ++ ++static ngx_int_t ++hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len); ++ ++static ngx_int_t ++hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, ++ uint8_t *key, size_t key_len); ++ ++ ++void ++ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c) ++{ ++ ngx_http_v2_hpack_enc_entry_t *table; ++ uint64_t idx; ++ ++ table = h2c->hpack_enc.htable; ++ ++ while (h2c->hpack_enc.size > h2c->max_hpack_table_size) { ++ idx = h2c->hpack_enc.base; ++ h2c->hpack_enc.base = table[idx].next; ++ h2c->hpack_enc.size -= table[idx].size; ++ table[idx].hash_val = 0; ++ h2c->hpack_enc.n_elems--; ++ } ++} ++ ++ ++/* checks if a header is in the hpack table - if so returns the table entry, ++ otherwise encodes and inserts into the table and returns 0, ++ if failed to insert into table, returns -1 */ ++static ngx_int_t ++ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c, ++ size_t key_len, size_t val_len, uint8_t *key, uint8_t *val, ++ ngx_int_t *header_idx) ++{ ++ uint64_t hash_val, key_hash, idx, lru; ++ int i; ++ size_t size = key_len + val_len + 32; ++ uint8_t *storage = h2c->hpack_enc.storage; ++ ++ ngx_http_v2_hpack_enc_entry_t *table; ++ ngx_http_v2_hpack_name_entry_t *name; ++ ++ *header_idx = NGX_ERROR; ++ /* step 1: compute the hash value of header */ ++ if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) { ++ return NGX_ERROR; ++ } ++ ++ key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234); ++ hash_val = ngx_murmur_hash2_64(val, val_len, key_hash); ++ ++ if (hash_val == 0) { ++ return NGX_ERROR; ++ } ++ ++ /* step 2: check if full header in the table */ ++ idx = hash_val; ++ i = -1; ++ while (idx) { ++ /* at most 8 locations are checked, but most will be done in 1 or 2 */ ++ table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ]; ++ if (table->hash_val == hash_val ++ && table->klen == key_len ++ && table->vlen == val_len ++ && ngx_memcmp(key, storage + table->pos, key_len) == 0 ++ && ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0) ++ { ++ return (h2c->hpack_enc.top - table->index) + 61; ++ } ++ ++ if (table->hash_val == 0 && i == -1) { ++ i = idx % HPACK_ENC_HTABLE_SZ; ++ break; ++ } ++ ++ idx >>= 8; ++ } ++ ++ /* step 3: check if key is in one of the tables */ ++ *header_idx = hpack_get_static_index(h2c, key, key_len); ++ ++ if (i == -1) { ++ return NGX_ERROR; ++ } ++ ++ if (*header_idx == NGX_ERROR) { ++ *header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len); ++ } ++ ++ /* step 4: store the new entry */ ++ table = h2c->hpack_enc.htable; ++ ++ if (h2c->hpack_enc.top == 0xffffffff) { ++ /* just to be on the safe side, avoid overflow */ ++ ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t)); ++ } ++ ++ while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size) ++ || h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) { ++ /* make space for the new entry first */ ++ idx = h2c->hpack_enc.base; ++ h2c->hpack_enc.base = table[idx].next; ++ h2c->hpack_enc.size -= table[idx].size; ++ table[idx].hash_val = 0; ++ h2c->hpack_enc.n_elems--; ++ } ++ ++ table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val, ++ .index = h2c->hpack_enc.top, ++ .pos = h2c->hpack_enc.pos, ++ .klen = key_len, ++ .vlen = val_len, ++ .size = size, ++ .next = 0}; ++ ++ table[h2c->hpack_enc.last].next = i; ++ if (h2c->hpack_enc.n_elems == 0) { ++ h2c->hpack_enc.base = i; ++ } ++ ++ h2c->hpack_enc.last = i; ++ h2c->hpack_enc.top++; ++ h2c->hpack_enc.size += size; ++ h2c->hpack_enc.n_elems++; ++ ++ /* update header name lookup */ ++ if (*header_idx == NGX_ERROR ) { ++ lru = h2c->hpack_enc.top; ++ ++ for (i=0; ihpack_enc.heads[i]; ++ ++ if ( name->hash_val == 0 || (name->hash_val == key_hash ++ && ngx_memcmp(storage + name->pos, key, key_len) == 0) ) ++ { ++ name->hash_val = key_hash; ++ name->pos = h2c->hpack_enc.pos; ++ name->index = h2c->hpack_enc.top - 1; ++ break; ++ } ++ ++ if (lru > name->index) { ++ lru = name->index; ++ idx = i; ++ } ++ } ++ ++ if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) { ++ name = &h2c->hpack_enc.heads[idx]; ++ name->hash_val = hash_val; ++ name->pos = h2c->hpack_enc.pos; ++ name->index = h2c->hpack_enc.top - 1; ++ } ++ } ++ ++ ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len); ++ ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len); ++ ++ h2c->hpack_enc.pos += size; ++ if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) { ++ h2c->hpack_enc.pos = 0; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++u_char * ++ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, ++ u_char *key, size_t key_len, ++ u_char *value, size_t value_len, ++ u_char *tmp) ++{ ++ ngx_int_t idx, header_idx; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 output header: %*s: %*s", key_len, key, value_len, ++ value); ++ ++ /* attempt to find the value in the dynamic table */ ++ idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value, ++ &header_idx); ++ ++ if (idx > 0) { ++ /* positive index indicates success */ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 hpack encode: Indexed Header Field: %ud", idx); ++ ++ *pos = 128; ++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx); ++ ++ } else { ++ ++ if (header_idx == NGX_ERROR) { /* if key is not present */ ++ ++ if (idx == NGX_ERROR) { /* if header was not added */ ++ *pos++ = 0; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 hpack encode: Literal Header Field without" ++ " Indexing — New Name"); ++ } else { /* if header was added */ ++ *pos++ = 64; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 hpack encode: Literal Header Field with " ++ "Incremental Indexing — New Name"); ++ } ++ ++ pos = ngx_http_v2_write_name(pos, key, key_len, tmp); ++ ++ } else { /* if key is present */ ++ ++ if (idx == NGX_ERROR) { ++ *pos = 0; ++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 hpack encode: Literal Header Field without" ++ " Indexing — Indexed Name: %ud", header_idx); ++ } else { ++ *pos = 64; ++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 hpack encode: Literal Header Field with " ++ "Incremental Indexing — Indexed Name: %ud", header_idx); ++ } ++ } ++ ++ pos = ngx_http_v2_write_value(pos, value, value_len, tmp); ++ } ++ ++ return pos; ++} ++ ++ ++static ngx_int_t ++hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash, ++ uint8_t *key, size_t key_len) ++{ ++ ngx_http_v2_hpack_name_entry_t *name; ++ int i; ++ ++ for (i=0; ihpack_enc.heads[i]; ++ ++ if (name->hash_val == key_hash ++ && ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0) ++ { ++ if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) { ++ return (h2c->hpack_enc.top - name->index) + 61; ++ } ++ break; ++ } ++ } ++ ++ return NGX_ERROR; ++} ++ ++ ++/* decide if a given header is present in the static dictionary, this could be ++ done in several ways, but it seems the fastest one is "exhaustive" search */ ++static ngx_int_t ++hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len) ++{ ++ /* the static dictionary of response only headers, ++ although response headers can be put by origin, ++ that would be rare */ ++ static const struct { ++ u_char len; ++ const u_char val[28]; ++ u_char idx; ++ } server_headers[] = { ++ { 3, "age", 21},//0 ++ { 3, "via", 60}, ++ { 4, "date", 33},//2 ++ { 4, "etag", 34}, ++ { 4, "link", 45}, ++ { 4, "vary", 59}, ++ { 5, "allow", 22},//6 ++ { 6, "server", 54},//7 ++ { 7, "expires", 36},//8 ++ { 7, "refresh", 52}, ++ { 8, "location", 46},//10 ++ {10, "set-cookie", 55},//11 ++ {11, "retry-after", 53},//12 ++ {12, "content-type", 31},//13 ++ {13, "content-range", 30},//14 ++ {13, "accept-ranges", 18}, ++ {13, "cache-control", 24}, ++ {13, "last-modified", 44}, ++ {14, "content-length", 28},//18 ++ {16, "content-encoding", 26},//19 ++ {16, "content-language", 27}, ++ {16, "content-location", 29}, ++ {16, "www-authenticate", 61}, ++ {17, "transfer-encoding", 57},//23 ++ {18, "proxy-authenticate", 48},//24 ++ {19, "content-disposition", 25},//25 ++ {25, "strict-transport-security", 56},//26 ++ {27, "access-control-allow-origin", 20},//27 ++ {99, "", 99}, ++ }, *header; ++ ++ /* for a given length, where to start the search ++ since minimal length is 3, the table has a -3 ++ offset */ ++ static const int8_t start_at[] = { ++ [3-3] = 0, ++ [4-3] = 2, ++ [5-3] = 6, ++ [6-3] = 7, ++ [7-3] = 8, ++ [8-3] = 10, ++ [9-3] = -1, ++ [10-3] = 11, ++ [11-3] = 12, ++ [12-3] = 13, ++ [13-3] = 14, ++ [14-3] = 18, ++ [15-3] = -1, ++ [16-3] = 19, ++ [17-3] = 23, ++ [18-3] = 24, ++ [19-3] = 25, ++ [20-3] = -1, ++ [21-3] = -1, ++ [22-3] = -1, ++ [23-3] = -1, ++ [24-3] = -1, ++ [25-3] = 26, ++ [26-3] = -1, ++ [27-3] = 27, ++ }; ++ ++ uint64_t pref; ++ size_t save_len = len, i; ++ int8_t start; ++ ++ /* early exit for out of bounds lengths */ ++ if (len < 3 || len > 27) { ++ return NGX_ERROR; ++ } ++ ++ start = start_at[len - 3]; ++ if (start == -1) { ++ /* exit for non existent lengths */ ++ return NGX_ERROR; ++ } ++ ++ header = &server_headers[start_at[len - 3]]; ++ ++ /* load first 8 bytes of key, for fast comparison */ ++ if (len < 8) { ++ pref = 0; ++ if (len >= 4) { ++ pref = *(uint32_t *)(val + len - 4) | 0x20202020; ++ len -= 4; ++ } ++ while (len > 0) { /* 3 iterations at most */ ++ pref = (pref << 8) ^ (val[len - 1] | 0x20); ++ len--; ++ } ++ } else { ++ pref = *(uint64_t *)val | 0x2020202020202020; ++ len -= 8; ++ } ++ ++ /* iterate over headers with the right length */ ++ while (header->len == save_len) { ++ /* quickly compare the first 8 bytes, most tests will end here */ ++ if (pref != *(uint64_t *) header->val) { ++ header++; ++ continue; ++ } ++ ++ if (len == 0) { ++ /* len == 0, indicates prefix held the entire key */ ++ return header->idx; ++ } ++ /* for longer keys compare the rest */ ++ i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */ ++ ++ while (i + 8 <= save_len) { /* 3 iterations at most */ ++ if ( *(uint64_t *)&header->val[i] ++ != (*(uint64_t *) &val[i]| 0x2020202020202020) ) ++ { ++ header++; ++ i = 0; ++ break; ++ } ++ i += 8; ++ } ++ ++ if (i == 0) { ++ continue; ++ } ++ ++ /* found the corresponding entry in the static dictionary */ ++ return header->idx; ++ } ++ ++ return NGX_ERROR; ++} ++ ++#else ++ ++u_char * ++ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos, ++ u_char *key, size_t key_len, ++ u_char *value, size_t value_len, ++ u_char *tmp) ++{ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, ++ "http2 output header: %*s: %*s", key_len, key, value_len, ++ value); ++ ++ *pos++ = 64; ++ pos = ngx_http_v2_write_name(pos, key, key_len, tmp); ++ pos = ngx_http_v2_write_value(pos, value, value_len, tmp); ++ ++ return pos; ++} ++ ++#endif diff --git a/nginx_hpack_push_fix.patch b/nginx_hpack_push_fix.patch new file mode 100644 index 0000000..0798695 --- /dev/null +++ b/nginx_hpack_push_fix.patch @@ -0,0 +1,81 @@ +diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c +index 6a210d4..8a59424 100644 +--- a/src/http/v2/ngx_http_v2_filter_module.c ++++ b/src/http/v2/ngx_http_v2_filter_module.c +@@ -59,6 +59,17 @@ + #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 + + ++static const struct { ++ u_char *name; ++ u_char const len; ++} push_header[] = { ++ { (u_char*)":authority" , 10 }, ++ { (u_char*)"accept-encoding" , 15 }, ++ { (u_char*)"accept-language" , 15 }, ++ { (u_char*)"user-agent" , 10 } ++}; ++ ++ + typedef struct { + ngx_str_t name; + u_char index; +@@ -955,6 +966,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, + + for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { + len += binary[i].len; ++ len += push_header[i].len + 1; + } + + pos = ngx_pnalloc(r->pool, len); +@@ -964,12 +976,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, + + start = pos; + +- if (h2c->table_update) { +- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, +- "http2 table size update: 0"); +- *pos++ = (1 << 5) | 0; +- h2c->table_update = 0; +- } ++ h2c = r->stream->connection; ++ ++ if (h2c->indicate_resize) { ++ *pos = 32; ++ pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5), ++ h2c->max_hpack_table_size); ++ h2c->indicate_resize = 0; ++#if (NGX_HTTP_V2_HPACK_ENC) ++ ngx_http_v2_table_resize(h2c); ++#endif ++ } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 push header: \":method: GET\""); +@@ -979,8 +996,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 push header: \":path: %V\"", path); + +- *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); +- pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); ++ pos = ngx_http_v2_write_header_pot(":path", path); + + #if (NGX_HTTP_SSL) + if (fc->ssl) { +@@ -1003,11 +1019,15 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, + continue; + } + ++ value = &(*h)->value; ++ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 push header: \"%V: %V\"", + &ph[i].name, &(*h)->value); + +- pos = ngx_cpymem(pos, binary[i].data, binary[i].len); ++ pos = ngx_http_v2_write_header(h2c, pos, ++ push_header[i].name, push_header[i].len, value->data, value->len, ++ tmp); + } + + frame = ngx_http_v2_create_push_frame(r, start, pos);