Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ message KeepaliveSettings {
[(validate.rules).duration = {gte {nanos: 1000000}}];
}

// [#next-free-field: 18]
// [#next-free-field: 19]
message Http2ProtocolOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http2ProtocolOptions";
Expand Down Expand Up @@ -662,6 +662,11 @@ message Http2ProtocolOptions {

// Configure the maximum amount of metadata than can be handled per stream. Defaults to 1 MB.
google.protobuf.UInt64Value max_metadata_size = 17;

// Disables encoding the headers using huffman encoding.
// This can be useful in cases where the cpu spent encoding the headers isn't
// worth the network bandwidth saved e.g. for localhost.
bool disable_huffman = 18;
}

// [#not-implemented-hide:]
Expand Down
305 changes: 305 additions & 0 deletions bazel/foreign_cc/nghttp2_huffman.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h
index 2ef49b8d..9bcd3ca9 100644
--- a/lib/includes/nghttp2/nghttp2.h
+++ b/lib/includes/nghttp2/nghttp2.h
@@ -3156,6 +3156,15 @@ NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
size_t val);

+/**
+ * @function
+ *
+ * This option sets whether nghttp2 will disable huffman encoding of headers
+ * to the receiver.
+*/
+NGHTTP2_EXTERN void
+nghttp2_option_set_disable_huffman_encoding(nghttp2_option *option, int val);
+
/**
* @function
*
diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c
index 55fc2cc6..04f3d411 100644
--- a/lib/nghttp2_hd.c
+++ b/lib/nghttp2_hd.c
@@ -719,10 +719,23 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,

deflater->deflate_hd_table_bufsize_max = max_deflate_dynamic_table_size;
deflater->min_hd_table_bufsize_max = UINT32_MAX;
+ deflater->disable_huffman = 0;

return 0;
}

+int nghttp2_hd_deflate_init3(nghttp2_hd_deflater *deflater,
+ size_t max_deflate_dynamic_table_size,
+ nghttp2_mem *mem) {
+ int rv = nghttp2_hd_deflate_init2(
+ deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem);
+ if (rv == 0) {
+ deflater->disable_huffman = 1;
+ }
+ return rv;
+}
+
+
int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) {
int rv;

@@ -1015,6 +1028,51 @@ static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len) {
return rv;
}

+/*
+ * Effectively the same as emit_string but with huffman pieces clobbered.
+ *
+ * While the code could be further hand optimized, the expectation is that
+ * the compiler will do constant propagation, dead code elimination, etc.
+ */
+static int emit_string_nohuffman(nghttp2_bufs *bufs, const uint8_t *str,
+ size_t len) {
+ int rv;
+ uint8_t sb[16];
+ uint8_t *bufp;
+ size_t blocklen;
+ const size_t enclen = len;
+ const int huffman = 0;
+
+ blocklen = count_encoded_length(enclen, 7);
+
+ DEBUGF("deflatehd: emit string str=%.*s, length=%zu, huffman=%d, "
+ "encoded_length=%zu\n",
+ (int)len, (const char *)str, len, huffman, enclen);
+
+ if (sizeof(sb) < blocklen) {
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ bufp = sb;
+ *bufp = huffman ? 1 << 7 : 0;
+ encode_length(bufp, enclen, 7);
+
+ rv = nghttp2_bufs_add(bufs, sb, blocklen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (huffman) {
+ rv = nghttp2_hd_huff_encode(bufs, str, len);
+ } else {
+ assert(enclen == len);
+ rv = nghttp2_bufs_add(bufs, str, len);
+ }
+
+ return rv;
+}
+
+
static uint8_t pack_first_byte(int indexing_mode) {
switch (indexing_mode) {
case NGHTTP2_HD_WITH_INDEXING:
@@ -1099,6 +1157,77 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
return 0;
}

+static int emit_indname_block_nohuffman(nghttp2_bufs *bufs, size_t idx,
+ const nghttp2_nv *nv,
+ int indexing_mode) {
+ int rv;
+ uint8_t *bufp;
+ size_t blocklen;
+ uint8_t sb[16];
+ size_t prefixlen;
+
+ if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) {
+ prefixlen = 6;
+ } else {
+ prefixlen = 4;
+ }
+
+ DEBUGF("deflatehd: emit indname index=%zu, valuelen=%zu, indexing_mode=%d\n",
+ idx, nv->valuelen, indexing_mode);
+
+ blocklen = count_encoded_length(idx + 1, prefixlen);
+
+ if (sizeof(sb) < blocklen) {
+ return NGHTTP2_ERR_HEADER_COMP;
+ }
+
+ bufp = sb;
+
+ *bufp = pack_first_byte(indexing_mode);
+
+ encode_length(bufp, idx + 1, prefixlen);
+
+ rv = nghttp2_bufs_add(bufs, sb, blocklen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ rv = emit_string_nohuffman(bufs, nv->value, nv->valuelen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+static int emit_newname_block_nohuffman(nghttp2_bufs *bufs,
+ const nghttp2_nv *nv,
+ int indexing_mode) {
+ int rv;
+
+ DEBUGF(
+ "deflatehd: emit newname namelen=%zu, valuelen=%zu, indexing_mode=%d\n",
+ nv->namelen, nv->valuelen, indexing_mode);
+
+ rv = nghttp2_bufs_addb(bufs, pack_first_byte(indexing_mode));
+ if (rv != 0) {
+ return rv;
+ }
+
+ rv = emit_string_nohuffman(bufs, nv->name, nv->namelen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ rv = emit_string_nohuffman(bufs, nv->value, nv->valuelen);
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+
static int add_hd_table_incremental(nghttp2_hd_context *context,
nghttp2_hd_nv *nv, nghttp2_hd_map *map,
uint32_t hash) {
@@ -1424,10 +1553,19 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
return NGHTTP2_ERR_HEADER_COMP;
}
}
- if (idx == -1) {
- rv = emit_newname_block(bufs, nv, indexing_mode);
+
+ if (deflater->disable_huffman) {
+ if (idx == -1) {
+ rv = emit_newname_block_nohuffman(bufs, nv, indexing_mode);
+ } else {
+ rv = emit_indname_block_nohuffman(bufs, (size_t)idx, nv, indexing_mode);
+ }
} else {
- rv = emit_indname_block(bufs, (size_t)idx, nv, indexing_mode);
+ if (idx == -1) {
+ rv = emit_newname_block(bufs, nv, indexing_mode);
+ } else {
+ rv = emit_indname_block(bufs, (size_t)idx, nv, indexing_mode);
+ }
}
if (rv != 0) {
return rv;
diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h
index 38a31a83..27b0f722 100644
--- a/lib/nghttp2_hd.h
+++ b/lib/nghttp2_hd.h
@@ -227,6 +227,8 @@ struct nghttp2_hd_deflater {
/* If nonzero, send header table size using encoding context update
in the next deflate process */
uint8_t notify_table_size_change;
+ /* Whether the deflater should not huffman encode header */
+ uint8_t disable_huffman;
};

struct nghttp2_hd_inflater {
@@ -306,6 +308,16 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
size_t max_deflate_dynamic_table_size,
nghttp2_mem *mem);

+/*
+ * Initializes |deflater| for deflating name/values pairs.
+ *
+ * This is `nghttp2_hd_deflate_init2` with the addition of tracking that
+ * huffman encoding should be disabled for this deflater.
+ */
+int nghttp2_hd_deflate_init3(nghttp2_hd_deflater *deflater,
+ size_t max_deflate_dynamic_table_size,
+ nghttp2_mem *mem);
+
/*
* Deallocates any resources allocated for |deflater|.
*/
diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c
index 02a24eee..38ed503e 100644
--- a/lib/nghttp2_option.c
+++ b/lib/nghttp2_option.c
@@ -116,6 +116,12 @@ void nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
option->max_deflate_dynamic_table_size = val;
}

+void nghttp2_option_set_disable_huffman_encoding(nghttp2_option *option,
+ int val) {
+ option->opt_set_mask |= NGHTTP2_OPT_DISABLE_HUFFMAN;
+ option->disable_huffman = val;
+}
+
void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) {
option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS;
option->no_closed_streams = val;
diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h
index c89cb97f..74141d89 100644
--- a/lib/nghttp2_option.h
+++ b/lib/nghttp2_option.h
@@ -72,6 +72,7 @@ typedef enum {
NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14,
NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15,
NGHTTP2_OPT_MAX_CONTINUATIONS = 1 << 16,
+ NGHTTP2_OPT_DISABLE_HUFFMAN = 1 << 30,
} nghttp2_option_flag;

/**
@@ -91,6 +92,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE
*/
size_t max_deflate_dynamic_table_size;
+ /**
+ * NGHTTP2_OPT_DISABLE_HUFFMAN
+ */
+ int disable_huffman;
/**
* NGHTTP2_OPT_MAX_OUTBOUND_ACK
*/
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
index df33a89e..48e17f6c 100644
--- a/lib/nghttp2_session.c
+++ b/lib/nghttp2_session.c
@@ -441,6 +441,7 @@ static int session_new(nghttp2_session **session_ptr,
size_t max_deflate_dynamic_table_size =
NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE;
size_t i;
+ int deflater_inited = 0;

if (mem == NULL) {
mem = nghttp2_mem_default();
@@ -585,10 +586,19 @@ static int session_new(nghttp2_session **session_ptr,
if (option->opt_set_mask & NGHTTP2_OPT_MAX_CONTINUATIONS) {
(*session_ptr)->max_continuations = option->max_continuations;
}
+
+ if (option->opt_set_mask & NGHTTP2_OPT_DISABLE_HUFFMAN && option->disable_huffman) {
+ rv = nghttp2_hd_deflate_init3(&(*session_ptr)->hd_deflater,
+ max_deflate_dynamic_table_size, mem);
+ deflater_inited = 1;
+ }
+ }
+
+ if (!deflater_inited) {
+ rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
+ max_deflate_dynamic_table_size, mem);
}

- rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
- max_deflate_dynamic_table_size, mem);
if (rv != 0) {
goto fail_hd_deflater;
}
5 changes: 4 additions & 1 deletion bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,10 @@ def _com_github_nghttp2_nghttp2():
# This patch cannot be picked up due to ABI rules. Discussion at;
# https://github.com/nghttp2/nghttp2/pull/1395
# https://github.com/envoyproxy/envoy/pull/8572#discussion_r334067786
patches = ["@envoy//bazel/foreign_cc:nghttp2.patch"],
patches = [
"@envoy//bazel/foreign_cc:nghttp2.patch",
"@envoy//bazel/foreign_cc:nghttp2_huffman.patch",
],
)
native.bind(
name = "nghttp2",
Expand Down
5 changes: 5 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ new_features:
- area: http2
change: |
Added new parameter to the ``sendGoAwayAndClose`` to support gracefully closing of HTTP/2 connection.
- area: http2
change: |
Added :ref:`disable_huffman <envoy_v3_api_field_config.core.v3.Http2ProtocolOptions.disable_huffman>` disable huffman
encoding of headers from envoy to the receiver. This is useful in scenarios where the bandwidth saved from huffman
encoding is not worth the cpu cost. e.g. for localhost, sidecar traffic.
- area: logging
change: |
Added support for the not-equal operator in access log filter rules, in
Expand Down
11 changes: 11 additions & 0 deletions source/common/http/http2/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,13 @@ ConnectionImpl::Http2Options::Http2Options(
og_options_.max_header_field_size = max_headers_kb * 1024;
og_options_.allow_extended_connect = http2_options.allow_connect();
og_options_.allow_different_host_and_authority = true;
if (http2_options.disable_huffman()) {
if (http2_options.has_hpack_table_size() && http2_options.hpack_table_size().value() == 0) {
og_options_.compression_option = http2::adapter::OgHttp2Session::Options::DISABLE_HUFFMAN;
} else {
og_options_.compression_option = http2::adapter::OgHttp2Session::Options::DISABLE_COMPRESSION;
}
}

#ifdef ENVOY_ENABLE_UHV
// UHV - disable header validations in oghttp2
Expand Down Expand Up @@ -2067,6 +2074,10 @@ ConnectionImpl::Http2Options::Http2Options(
http2_options.hpack_table_size().value());
}

if (http2_options.disable_huffman()) {
nghttp2_option_set_disable_huffman_encoding(options_, 1);
}

if (http2_options.allow_metadata()) {
nghttp2_option_set_user_recv_extension_type(options_, METADATA_FRAME_TYPE);
} else {
Expand Down
Loading
Loading