Skip to content

Commit

Permalink
* new configuration directive:
Browse files Browse the repository at this point in the history
    ```H2Padding [ 'prefer' | 'enforce' ] numbits```
    to control padding of HTTP/2 payload frames. 'numbits' is a number from 0-8,
    where 0 disables padding and 1-8 is the power of 2 that frame lengths are
    rounded to. The default is 4 bits, e.g. frames are padded to be a multiple
    of 16 (2^4).
    While 'enforce' always applies this to all payload frames, the default 'prefer'
    option caps frame length at H2TLSWarmUpSize when in effect.
  • Loading branch information
Stefan Eissing committed Mar 4, 2019
1 parent febb1a3 commit a9f5d5c
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 13 deletions.
11 changes: 11 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
v1.14.0
--------------------------------------------------------------------------------
* new configuration directive:
```H2Padding [ 'prefer' | 'enforce' ] numbits```
to control padding of HTTP/2 payload frames. 'numbits' is a number from 0-8,
where 0 disables padding and 1-8 is the power of 2 that frame lengths are
rounded to. The default is 4 bits, e.g. frames are padded to be a multiple
of 16 (2^4).
While 'enforce' always applies this to all payload frames, the default 'prefer'
option caps frame length at H2TLSWarmUpSize when in effect.

v1.13.1
--------------------------------------------------------------------------------
* mod_http2: cleanup of the now dead h2_req_engine support, no functional change.
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#

AC_PREREQ([2.69])
AC_INIT([mod_http2], [1.13.1], [[email protected]])
AC_INIT([mod_http2], [1.14.0], [[email protected]])

LT_PREREQ([2.2.6])
LT_INIT()
Expand Down
2 changes: 2 additions & 0 deletions mod-h2.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@
B2E5D7871AAF1F4D001FD280 /* ChangeLog */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ChangeLog; sourceTree = "<group>"; };
B2E5D7881AAF1F4D001FD280 /* COPYING */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = COPYING; sourceTree = "<group>"; };
B2E5D7891AAF1F4D001FD280 /* NEWS */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = NEWS; sourceTree = "<group>"; };
B2FDA580222D495E007BD19C /* test_104_padding.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = test_104_padding.py; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXGroup section */
Expand Down Expand Up @@ -494,6 +495,7 @@
B2D2D5DF21395D6900D8F6B7 /* test_101_ssl_reneg.py */,
B297A7BC213D6AAD0096F0D7 /* test_102_require.py */,
B297A7BD213D6C830096F0D7 /* test_103_upgrade.py */,
B2FDA580222D495E007BD19C /* test_104_padding.py */,
B297A7BE213D7A5B0096F0D7 /* test_200_header_invalid.py */,
B297A7C0213EA9BF0096F0D7 /* test_201_header_conditional.py */,
B297A7C2213EB14B0096F0D7 /* test_202_trailer.py */,
Expand Down
6 changes: 3 additions & 3 deletions mod_http2/h2.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ extern const char *H2_MAGIC_TOKEN;
#define H2_HEADER_PATH_LEN 5
#define H2_CRLF "\r\n"

/* Max data size to write so it fits inside a TLS record */
#define H2_DATA_CHUNK_SIZE ((16*1024) - 100 - 9)

/* Size of the frame header itself in HTTP/2 */
#define H2_FRAME_HDR_LEN 9

/* Max data size to write so it fits inside a TLS record */
#define H2_DATA_CHUNK_SIZE ((16*1024) - 100 - H2_FRAME_HDR_LEN)

/* Maximum number of padding bytes in a frame, rfc7540 */
#define H2_MAX_PADLEN 256
/* Initial default window size, RFC 7540 ch. 6.5.2 */
Expand Down
49 changes: 49 additions & 0 deletions mod_http2/h2_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ typedef struct h2_config {
int copy_files; /* if files shall be copied vs setaside on output */
apr_array_header_t *push_list;/* list of h2_push_res configurations */
int early_hints; /* support status code 103 */
int padding_bits;
int padding_always;
} h2_config;

typedef struct h2_dir_config {
Expand Down Expand Up @@ -111,6 +113,8 @@ static h2_config defconf = {
0, /* copy files across threads */
NULL, /* push list */
0, /* early hints, http status 103 */
4, /* padding bits */
0, /* padding always */
};

static h2_dir_config defdconf = {
Expand Down Expand Up @@ -153,6 +157,8 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
conf->copy_files = DEF_VAL;
conf->push_list = NULL;
conf->early_hints = DEF_VAL;
conf->padding_bits = DEF_VAL;
conf->padding_always = DEF_VAL;
return conf;
}

Expand Down Expand Up @@ -194,6 +200,8 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
n->push_list = add->push_list? add->push_list : base->push_list;
}
n->early_hints = H2_CONFIG_GET(add, base, early_hints);
n->padding_bits = H2_CONFIG_GET(add, base, padding_bits);
n->padding_always = H2_CONFIG_GET(add, base, padding_always);
return n;
}

Expand Down Expand Up @@ -275,6 +283,10 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
return H2_CONFIG_GET(conf, &defconf, copy_files);
case H2_CONF_EARLY_HINTS:
return H2_CONFIG_GET(conf, &defconf, early_hints);
case H2_CONF_PADDING_BITS:
return H2_CONFIG_GET(conf, &defconf, padding_bits);
case H2_CONF_PADDING_ALWAYS:
return H2_CONFIG_GET(conf, &defconf, padding_always);
default:
return DEF_VAL;
}
Expand Down Expand Up @@ -334,6 +346,12 @@ static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
case H2_CONF_EARLY_HINTS:
H2_CONFIG_SET(conf, early_hints, val);
break;
case H2_CONF_PADDING_BITS:
H2_CONFIG_SET(conf, padding_bits, val);
break;
case H2_CONF_PADDING_ALWAYS:
H2_CONFIG_SET(conf, padding_always, val);
break;
default:
break;
}
Expand Down Expand Up @@ -873,6 +891,35 @@ static const char *h2_conf_set_early_hints(cmd_parms *cmd,
return NULL;
}

static const char *h2_conf_set_padding(cmd_parms *cmd, void *dirconf,
const char *v1, const char *v2)
{
int val;

if (v2) {
if (v2 && !strcasecmp(v1, "prefer")) {
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PADDING_ALWAYS, 0);
}
else if (v2 && !strcasecmp(v1, "enforce")) {
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PADDING_ALWAYS, 1);
}
else {
return "[ 'enforce' | 'prefer' ] numbits";
}
v1 = v2;
}
val = (int)apr_atoi64(v1);
if (val < 0) {
return "number of bits must be >= 0";
}
if (val > 8) {
return "number of bits must be <= 8";
}
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PADDING_BITS, val);
return NULL;
}


void h2_get_num_workers(server_rec *s, int *minw, int *maxw)
{
int threads_per_child = 0;
Expand Down Expand Up @@ -941,6 +988,8 @@ const command_rec h2_cmds[] = {
OR_FILEINFO|OR_AUTHCFG, "add a resource to be pushed in this location/on this server."),
AP_INIT_TAKE1("H2EarlyHints", h2_conf_set_early_hints, NULL,
RSRC_CONF, "on to enable interim status 103 responses"),
AP_INIT_TAKE12("H2Padding", h2_conf_set_padding, NULL,
RSRC_CONF, "set payload padding"),
AP_END_CMD
};

Expand Down
2 changes: 2 additions & 0 deletions mod_http2/h2_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ typedef enum {
H2_CONF_PUSH_DIARY_SIZE,
H2_CONF_COPY_FILES,
H2_CONF_EARLY_HINTS,
H2_CONF_PADDING_BITS,
H2_CONF_PADDING_ALWAYS,
} h2_config_var_t;

struct apr_hash_t;
Expand Down
2 changes: 1 addition & 1 deletion mod_http2/h2_conn_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
* - TLS overhead (60-100)
* which seems to create less TCP packets overall
*/
#define WRITE_SIZE_MAX (TLS_DATA_MAX - 100)
#define WRITE_SIZE_MAX (TLS_DATA_MAX)

#define BUF_REMAIN ((apr_size_t)(bmax-off))

Expand Down
57 changes: 53 additions & 4 deletions mod_http2/h2_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,7 @@ static int on_send_data_cb(nghttp2_session *ngh2,
return NGHTTP2_ERR_WOULDBLOCK;
}

if (frame->data.padlen > H2_MAX_PADLEN) {
return NGHTTP2_ERR_PROTO;
}
ap_assert(frame->data.padlen <= (H2_MAX_PADLEN+1));
padlen = (unsigned char)frame->data.padlen;

stream = get_stream(session, stream_id);
Expand All @@ -513,8 +511,9 @@ static int on_send_data_cb(nghttp2_session *ngh2,
H2_STRM_MSG(stream, "send_data_cb for %ld bytes"),
(long)length);

status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
status = h2_conn_io_write(&session->io, (const char *)framehd, H2_FRAME_HDR_LEN);
if (padlen && status == APR_SUCCESS) {
--padlen;
status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
}

Expand Down Expand Up @@ -622,6 +621,50 @@ static int on_invalid_header_cb(nghttp2_session *ngh2,
}
#endif

static ssize_t select_padding_cb(nghttp2_session *ngh2,
const nghttp2_frame *frame,
size_t max_payloadlen, void *user_data)
{
h2_session *session = user_data;
ssize_t frame_len = frame->hd.length + H2_FRAME_HDR_LEN; /* the total length without padding */
ssize_t padded_len = frame_len;

/* Determine # of padding bytes to append to frame. We use different
* paddings for payload and meta frames, as the latter ones are usually shorter
* and may not contain as sensitive data.
* Up to 255 bytes of padding data are possible in HTTP/2.
* As a security feature, the usefulness of padding h2 frames depends on the overall
* protocol stack. See chapter 10.7 of RFC 7540.
* The implementation here is a fixed size approach:
* - specify the number of bits the frame length shall be rounded to. E.g. 2 bits
* makes the effective frame lengths multiples of 4.
* - unless padding is configured to happen always:
* - When a "write size" limit is in place, cap paddings to it. E.g. when a cap
* of 1300 bytes is in place, do not exceed this by padding.
*/
if (session->padding_mask) {
padded_len = H2MIN(max_payloadlen + H2_FRAME_HDR_LEN,
((frame_len + session->padding_mask)
& ~session->padding_mask));
}

if (padded_len != frame_len) {
if (!session->padding_always && session->io.write_size
&& (padded_len > session->io.write_size)
&& (frame_len <= session->io.write_size)) {
padded_len = session->io.write_size;
}
if (APLOGctrace2(session->c)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
"select padding from [%d, %d]: %d (frame length: 0x%04x, write size: %d)",
(int)frame_len, (int)max_payloadlen+H2_FRAME_HDR_LEN,
(int)(padded_len - frame_len), (int)padded_len, (int)session->io.write_size);
}
return padded_len - H2_FRAME_HDR_LEN;
}
return frame->hd.length;
}

#define NGH2_SET_CALLBACK(callbacks, name, fn)\
nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)

Expand All @@ -647,6 +690,7 @@ static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
#ifdef H2_NG2_INVALID_HEADER_CB
NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb);
#endif
NGH2_SET_CALLBACK(*pcb, select_padding, select_padding_cb);
return APR_SUCCESS;
}

Expand Down Expand Up @@ -862,6 +906,11 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
ap_add_input_filter("H2_IN", session->cin, r, c);

h2_conn_io_init(&session->io, c, s);
session->padding_mask = h2_config_sgeti(s, H2_CONF_PADDING_BITS);
if (session->padding_mask) {
session->padding_mask = (0x01 << session->padding_mask) - 1;
}
session->padding_always = h2_config_sgeti(s, H2_CONF_PADDING_ALWAYS);
session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);

status = init_callbacks(c, &callbacks);
Expand Down
2 changes: 2 additions & 0 deletions mod_http2/h2_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ typedef struct h2_session {
struct h2_workers *workers; /* for executing stream tasks */
struct h2_filter_cin *cin; /* connection input filter context */
h2_conn_io io; /* io on httpd conn filters */
int padding_mask; /* padding bit mask for frame length */
int padding_always; /* padding has precedence over I/O optimizations */
struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */

h2_session_state state; /* state session is in */
Expand Down
2 changes: 1 addition & 1 deletion mod_http2/h2_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
* is requested. But we can reduce the size in case the master
* connection operates in smaller chunks. (TSL warmup) */
if (stream->session->io.write_size > 0) {
max_chunk = stream->session->io.write_size - 9; /* header bits */
max_chunk = stream->session->io.write_size - H2_FRAME_HDR_LEN;
}
requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;

Expand Down
4 changes: 2 additions & 2 deletions mod_http2/h2_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
* @macro
* Version number of the http2 module as c string
*/
#define MOD_HTTP2_VERSION "1.13.1-git"
#define MOD_HTTP2_VERSION "1.14.0-git"

/**
* @macro
* Numerical representation of the version number of the http2 module
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
#define MOD_HTTP2_VERSION_NUM 0x010d01
#define MOD_HTTP2_VERSION_NUM 0x010e00


#endif /* mod_h2_h2_version_h */
20 changes: 19 additions & 1 deletion test/e2e/TestNghttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def get_stream( cls, streams, sid ) :
"id" : sid,
"body" : ""
},
"paddings" : [],
"promises" : []
}
return streams[sid] if sid in streams else None
Expand Down Expand Up @@ -118,7 +119,14 @@ def parse_output( self, text ):
body += m.group(1)
blen = int(m.group(2))
if s:
print "stream %d: %d DATA bytes added" % (s["id"], blen)
print "stream %d: %d DATA bytes added" % (s["id"], blen)
padlen = 0
if len(lines) > lidx + 2:
mpad = re.match(r' +\(padlen=(\d+)\)', lines[lidx+2])
if mpad:
padlen = int(mpad.group(1))
s["paddings"].append(padlen)
blen -= padlen
s["response"]["body"] += body[-blen:]
body = ""
skip_indents = True
Expand Down Expand Up @@ -178,6 +186,7 @@ def parse_output( self, text ):
output["streams"] = streams
if main_stream in streams:
output["response"] = streams[main_stream]["response"]
output["paddings"] = streams[main_stream]["paddings"]
return output

def _raw( self, url, timeout, options ) :
Expand Down Expand Up @@ -214,6 +223,15 @@ def assets( self, url, timeout=5, options=None ) :
r["assets"] = assets
return r

def post_data( self, url, data, timeout=5, options=None ) :
reqbody = ("%s/nghttp.req.body" % self.TMP_DIR)
with open(reqbody, 'wb') as f:
f.write(data)
if not options:
options = []
options.extend([ "--data=%s" % reqbody ])
return self._raw( url, timeout, options )

def post_name( self, url, name, timeout=5, options=None ) :
reqbody = ("%s/nghttp.req.body" % self.TMP_DIR)
with open(reqbody, 'w') as f:
Expand Down
Loading

0 comments on commit a9f5d5c

Please sign in to comment.