From: Stefan Eissing Date: Tue, 20 Oct 2015 13:42:23 +0000 (+0000) Subject: new ap_is_allowed_protocol() for testing configured protocols, added H2Upgrade on... X-Git-Tag: 2.5.0-alpha~2707 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=18e2dd1225e1464962fbdf222530a06cdcb69fb5;p=apache new ap_is_allowed_protocol() for testing configured protocols, added H2Upgrade on/off directive, changed H2Direct default back to on when h2c is in Protocols git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1709587 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index 9f45957cae..b355865ff4 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -56,7 +56,7 @@ H2Direct H2 Direct Protocol Switch H2Direct on|off - H2Direct off + H2Direct on for http:, off for https: requests server config virtual host @@ -64,23 +64,21 @@

- This directive toggles the usage of the HTTP/2 Direct Mode. For https, this + This directive toggles the usage of the HTTP/2 Direct Mode. This should be used inside a VirtualHost - section to enable direct HTTP/2 communication for that virtual host. For - http, this directive only works in server configurations. + section to enable direct HTTP/2 communication for that virtual host.

Direct communication means that if the first bytes received by the server on a connection match the HTTP/2 preamble, the HTTP/2 protocol is switched to immediately without further negotiation. This mode is defined in RFC 7540 for the cleartext (h2c) case. Its - use on TLS connections is not allowed by the standard. + use on TLS connections not mandated by the standard.

- Since this detection requires that the client will send data on - new connection immediately, direct HTTP/2 mode is disabled by - default. + This mode only has an effect when h2 or h2c is enabled via + the Protocols.

Example @@ -89,6 +87,47 @@
+ + + H2Upgrade + H2 Upgrade Protocol Switch + H2Upgrade on|off + H2Upgrade on for http:, off for https: requests + + server config + virtual host + + + +

+ This directive toggles the usage of the HTTP/1.1 Upgrade method + for switching to HTTP/2. This + should be used inside a + VirtualHost + section to enable Upgrades to HTTP/2 for that virtual host. +

+

+ This method of switching protocols is defined in HTTP/1.1 and + uses the "Upgrade" header (thus the name) to announce willingness + to use another protocol. This may happen on any request of a + HTTP/1.1 connection. +

+

+ This method of protocol switching is enabled by default on cleartext + (http:) connections and disabled on TLS (https:), as mandated + by RFC 7540. +

+

+ This mode only has an effect when h2 or h2c is enabled via + the Protocols. +

+ Example + + H2Upgrade on + + +
+
H2MaxSessionStreams diff --git a/include/http_protocol.h b/include/http_protocol.h index 64ed01362c..f9110385cd 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -833,6 +833,23 @@ AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r, */ AP_DECLARE(const char *) ap_get_protocol(conn_rec *c); +/** + * Check if the given protocol is an allowed choice on the given + * combination of connection, request and server. + * + * When server is NULL, it is taken from request_rec, unless + * request_rec is NULL. Then it is taken from the connection base + * server. + * + * @param c The current connection + * @param r The current request or NULL + * @param s The server/virtual host selected or NULL + * @param protocol the protocol to switch to + * @return != 0 iff protocol is allowed + */ +AP_DECLARE(int) ap_is_allowed_protocol(conn_rec *c, request_rec *r, + server_rec *s, const char *protocol); + /** @see ap_bucket_type_error */ typedef struct ap_bucket_error ap_bucket_error; diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index 45741ca712..a8744f0a5f 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -47,9 +47,10 @@ static h2_config defconf = { NULL, /* no alt-svcs */ -1, /* alt-svc max age */ 0, /* serialize headers */ - 0, /* h2 direct mode */ + -1, /* h2 direct mode */ -1, /* # session extra files */ 1, /* modern TLS only */ + -1, /* HTTP/1 Upgrade support */ }; static int files_per_session = 0; @@ -102,6 +103,7 @@ static void *h2_config_create(apr_pool_t *pool, conf->h2_direct = DEF_VAL; conf->session_extra_files = DEF_VAL; conf->modern_tls_only = DEF_VAL; + conf->h2_upgrade = DEF_VAL; return conf; } @@ -129,18 +131,19 @@ void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) strcat(name, "]"); n->name = name; - n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams); - n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size); - n->min_workers = H2_CONFIG_GET(add, base, min_workers); - n->max_workers = H2_CONFIG_GET(add, base, max_workers); + n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams); + n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size); + n->min_workers = H2_CONFIG_GET(add, base, min_workers); + n->max_workers = H2_CONFIG_GET(add, base, max_workers); n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs); - n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size); - n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs; - n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age); - n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers); - n->h2_direct = H2_CONFIG_GET(add, base, h2_direct); - n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files); - n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only); + n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size); + n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs; + n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age); + n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers); + n->h2_direct = H2_CONFIG_GET(add, base, h2_direct); + n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files); + n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only); + n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade); return n; } @@ -167,6 +170,8 @@ int h2_config_geti(h2_config *conf, h2_config_var_t var) return H2_CONFIG_GET(conf, &defconf, serialize_headers); case H2_CONF_MODERN_TLS_ONLY: return H2_CONFIG_GET(conf, &defconf, modern_tls_only); + case H2_CONF_UPGRADE: + return H2_CONFIG_GET(conf, &defconf, h2_upgrade); case H2_CONF_DIRECT: return H2_CONFIG_GET(conf, &defconf, h2_direct); case H2_CONF_SESSION_FILES: @@ -354,6 +359,23 @@ static const char *h2_conf_set_modern_tls_only(cmd_parms *parms, return "value must be On or Off"; } +static const char *h2_conf_set_upgrade(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + if (!strcasecmp(value, "On")) { + cfg->h2_upgrade = 1; + return NULL; + } + else if (!strcasecmp(value, "Off")) { + cfg->h2_upgrade = 0; + return NULL; + } + + (void)arg; + return "value must be On or Off"; +} + #define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) @@ -378,6 +400,8 @@ const command_rec h2_cmds[] = { RSRC_CONF, "on to enable header serialization for compatibility"), AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL, RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"), + AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL, + RSRC_CONF, "on to allow HTTP/1 Upgrades to h2/h2c"), AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL, RSRC_CONF, "on to enable direct HTTP/2 mode"), AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL, diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h index 83d899f5ab..781ca902d3 100644 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@ -35,6 +35,7 @@ typedef enum { H2_CONF_DIRECT, H2_CONF_SESSION_FILES, H2_CONF_MODERN_TLS_ONLY, + H2_CONF_UPGRADE, } h2_config_var_t; /* Apache httpd module configuration for h2. */ @@ -53,6 +54,7 @@ typedef struct h2_config { int h2_direct; /* if mod_h2 is active directly */ int session_extra_files; /* # of extra files a session may keep open */ int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */ + int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */ } h2_config; diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index a1dcb2b81e..383d2ab443 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -503,6 +503,32 @@ int h2_is_acceptable_connection(conn_rec *c, int require_all) return 1; } +int h2_allows_h2_direct(conn_rec *c) +{ + h2_config *cfg = h2_config_get(c); + int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT); + + if (h2_direct < 0) { + if (h2_h2_is_tls(c)) { + /* disabled by default on TLS */ + h2_direct = 0; + } + else { + /* enabled if "Protocols h2c" is configured */ + h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c"); + } + } + return !!h2_direct; +} + +int h2_allows_h2_upgrade(conn_rec *c) +{ + h2_config *cfg = h2_config_get(c); + int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE); + + return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c)); +} + /******************************************************************************* * Register various hooks */ @@ -543,9 +569,6 @@ int h2_h2_remove_timeout(conn_rec* c) int h2_h2_process_conn(conn_rec* c) { h2_ctx *ctx = h2_ctx_get(c); - h2_config *cfg = h2_config_get(c); - apr_bucket_brigade* temp = NULL; - int is_tls = h2_h2_is_tls(c); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn"); if (h2_ctx_is_task(ctx)) { @@ -555,75 +578,77 @@ int h2_h2_process_conn(conn_rec* c) /* If we have not already switched to a h2* protocol and the connection * is on "http/1.1" - * -> sniff for the magic PRIamble. On TLS, this might trigger the ALPN. + * -> on TLS, try to trigger ALPN + * -> sniff for the magic PRIamble if enabled */ - if (!h2_ctx_protocol_get(c) - && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { + if (h2_ctx_protocol_get(c)) { + /* we have a protocol selected */ + } + else if (!strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { + apr_bucket_brigade* temp = NULL; apr_status_t status = APR_SUCCESS; - + int is_tls = h2_h2_is_tls(c); + + /* we are still on HTTP/1.1, trigger ALPN if TLS */ + /* TODO: this part should be in mod_ssl */ if (is_tls) { /* trigger the TLS handshake */ temp = apr_brigade_create(c->pool, c->bucket_alloc); status = ap_get_brigade(c->input_filters, temp, AP_MODE_INIT, APR_BLOCK_READ, 0); + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, + "h2_h2, failed to trigger ALPN"); + return DECLINED; + } } - if (status == APR_SUCCESS) { - if (h2_ctx_protocol_get(c) - || strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { - /* h2 or another protocol has been selected. */ + if (h2_ctx_protocol_get(c)) { + /* NOW, something has been negotiated */ + } + else if (!strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c)) + && h2_allows_h2_direct(c) + && h2_is_acceptable_connection(c, 1)) { + /* ALPN might have been triggered, but we're still on + * http/1.1. Check the actual bytes read for the H2 Magic + * Token, *if* H2Direct mode is enabled here *and* + * connection is in a fully acceptable state. + */ + char *s = NULL; + apr_size_t slen; + + if (!temp) { + temp = apr_brigade_create(c->pool, c->bucket_alloc); + } + + status = ap_get_brigade(c->input_filters, temp, + AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); + + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, + "h2_h2, error reading 24 bytes speculative"); + return DECLINED; + } + + apr_brigade_pflatten(temp, &s, &slen, c->pool); + if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "h2_h2, direct mode detected"); + h2_ctx_protocol_set(ctx, is_tls? "h2" : "h2c"); } else { - /* ALPN might have been triggered, but we're still on - * http/1.1. Check the actual bytes read for the H2 Magic - * Token, *if* H2Direct mode is enabled here. - */ - if (h2_config_geti(cfg, H2_CONF_DIRECT) > 0) { - char *s = NULL; - apr_size_t slen; - - /* - * Verify that all connection requirements are met. - */ - if (h2_is_acceptable_connection(c, 1)) { - if (!temp) { - temp = apr_brigade_create(c->pool, c->bucket_alloc); - } - status = ap_get_brigade(c->input_filters, temp, - AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); - if (status == APR_SUCCESS) { - apr_brigade_pflatten(temp, &s, &slen, c->pool); - if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, - "h2_h2, direct mode detected"); - h2_ctx_protocol_set(ctx, is_tls? "h2" : "h2c"); - } - else { - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, - "h2_h2, not detected in %d bytes: %s", - (int)slen, s); - } - } - else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, - "h2_h2, error reading 24 bytes speculative"); - } - } - else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, - "h2_h2, passed on direct mode, connection" - " does not meet requirements"); - } - } + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_h2, not detected in %d bytes: %s", + (int)slen, s); + } + + if (temp) { + apr_brigade_destroy(temp); } } else { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, - "h2_h2, failed to init connection"); - } - - if (temp) { - apr_brigade_destroy(temp); + /* the connection is not HTTP/1.1 or not for us, don't touch it */ + return DECLINED; } } diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h index 6a7c416c90..1b3bbf4a97 100644 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@ -54,15 +54,30 @@ int h2_tls_disable(conn_rec *c); void h2_h2_register_hooks(void); /** - * Check if the given connection fulfills the (security) requirements - * defined in the configuration. + * Check if the given connection fulfills the requirements as configured. * @param c the connection * @param require_all != 0 iff any missing connection properties make * the test fail. For example, a cipher might not have been selected while * the handshake is still ongoing. - * @return != 0 iff security requirements are met + * @return != 0 iff connection requirements are met */ int h2_is_acceptable_connection(conn_rec *c, int require_all); +/** + * Check if the "direct" HTTP/2 mode of protocol handling is enabled + * for the given connection. + * @param c the connection to check + * @return != 0 iff direct mode is enabled + */ +int h2_allows_h2_direct(conn_rec *c); + +/** + * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled + * for the given connection. + * @param c the connection to check + * @return != 0 iff Upgrade switching is enabled + */ +int h2_allows_h2_upgrade(conn_rec *c); + #endif /* defined(__mod_h2__h2_h2__) */ diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c index 0dd43f3295..97f07f891d 100644 --- a/modules/http2/h2_switch.c +++ b/modules/http2/h2_switch.c @@ -70,11 +70,16 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r, } if (r) { - const char *p; /* So far, this indicates an HTTP/1 Upgrade header initiated * protocol switch. For that, the HTTP2-Settings header needs * to be present and valid for the connection. */ + const char *p; + + if (!h2_allows_h2_upgrade(c)) { + return DECLINED; + } + p = apr_table_get(r->headers_in, "HTTP2-Settings"); if (!p) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, diff --git a/server/core.c b/server/core.c index d340d5e3de..6ffeb19240 100644 --- a/server/core.c +++ b/server/core.c @@ -5341,8 +5341,7 @@ static int core_upgrade_handler(request_rec *r) } if (offers && offers->nelts > 0) { - const char *protocol = ap_select_protocol(c, r, r->server, - offers); + const char *protocol = ap_select_protocol(c, r, NULL, offers); if (protocol && strcmp(protocol, ap_get_protocol(c))) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02909) "Upgrade selects '%s'", protocol); diff --git a/server/protocol.c b/server/protocol.c index 26ce24a8f2..bace4bea3d 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -1982,10 +1982,15 @@ AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r, const apr_array_header_t *choices) { apr_pool_t *pool = r? r->pool : c->pool; - core_server_config *conf = ap_get_core_module_config(s->module_config); + core_server_config *conf; const char *protocol = NULL, *existing; apr_array_header_t *proposals; + if (!s) { + s = (r? r->server : c->base_server); + } + conf = ap_get_core_module_config(s->module_config); + if (APLOGcdebug(c)) { const char *p = apr_array_pstrcat(pool, conf->protocols, ','); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, @@ -2091,6 +2096,22 @@ AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r, } } +AP_DECLARE(int) ap_is_allowed_protocol(conn_rec *c, request_rec *r, + server_rec *s, const char *protocol) +{ + core_server_config *conf; + + if (!s) { + s = (r? r->server : c->base_server); + } + conf = ap_get_core_module_config(s->module_config); + + if (conf->protocols->nelts > 0) { + return ap_array_str_contains(conf->protocols, protocol); + } + return !strcmp(AP_PROTOCOL_HTTP1, protocol); +} + AP_IMPLEMENT_HOOK_VOID(pre_read_request, (request_rec *r, conn_rec *c),