<name>H2Direct</name>
<description>H2 Direct Protocol Switch</description>
<syntax>H2Direct on|off</syntax>
- <default>H2Direct off</default>
+ <default>H2Direct on for http:, off for https: requests</default>
<contextlist>
<context>server config</context>
<context>virtual host</context>
<usage>
<p>
- 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
<directive module="core" type="section">VirtualHost</directive>
- 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.
</p>
<p>
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.
</p>
<p>
- 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 <directive module="core" type="section">Protocols</directive>.
</p>
<example><title>Example</title>
<highlight language="config">
</example>
</usage>
</directivesynopsis>
+
+ <directivesynopsis>
+ <name>H2Upgrade</name>
+ <description>H2 Upgrade Protocol Switch</description>
+ <syntax>H2Upgrade on|off</syntax>
+ <default>H2Upgrade on for http:, off for https: requests</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+
+ <usage>
+ <p>
+ This directive toggles the usage of the HTTP/1.1 Upgrade method
+ for switching to HTTP/2. This
+ should be used inside a
+ <directive module="core" type="section">VirtualHost</directive>
+ section to enable Upgrades to HTTP/2 for that virtual host.
+ </p>
+ <p>
+ 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.
+ </p>
+ <p>
+ This method of protocol switching is enabled by default on cleartext
+ (http:) connections and disabled on TLS (https:), as mandated
+ by RFC 7540.
+ </p>
+ <p>
+ This mode only has an effect when h2 or h2c is enabled via
+ the <directive module="core" type="section">Protocols</directive>.
+ </p>
+ <example><title>Example</title>
+ <highlight language="config">
+ H2Upgrade on
+ </highlight>
+ </example>
+ </usage>
+ </directivesynopsis>
<directivesynopsis>
<name>H2MaxSessionStreams</name>
*/
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;
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;
conf->h2_direct = DEF_VAL;
conf->session_extra_files = DEF_VAL;
conf->modern_tls_only = DEF_VAL;
+ conf->h2_upgrade = DEF_VAL;
return conf;
}
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;
}
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:
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)
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,
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. */
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;
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
*/
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)) {
/* 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;
}
}
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__) */
}
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,
}
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);
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,
}
}
+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),