]> granicus.if.org Git - apache/commitdiff
new ap_is_allowed_protocol() for testing configured protocols, added H2Upgrade on...
authorStefan Eissing <icing@apache.org>
Tue, 20 Oct 2015 13:42:23 +0000 (13:42 +0000)
committerStefan Eissing <icing@apache.org>
Tue, 20 Oct 2015 13:42:23 +0000 (13:42 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1709587 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/mod/mod_http2.xml
include/http_protocol.h
modules/http2/h2_config.c
modules/http2/h2_config.h
modules/http2/h2_h2.c
modules/http2/h2_h2.h
modules/http2/h2_switch.c
server/core.c
server/protocol.c

index 9f45957caeb3e895bbbb0f594df6f5bf00822fa9..b355865ff4f0b9d5bf6ea0f8e8e8f71a86fa4180 100644 (file)
@@ -56,7 +56,7 @@
         <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>
index 64ed01362cc3f234cc10a4338b8dc7ada260f685..f9110385cd5ffd6682013d3bade78ad18a574f7a 100644 (file)
@@ -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;
 
index 45741ca712dc1911c89b923eb15ac67887d86cbd..a8744f0a5f9c1494f229fae6e73f9c7357d24d10 100644 (file)
@@ -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,
index 83d899f5ab38cfc41b0bb9c0f160a9c7345292dd..781ca902d34e573c8289ecd8d884ebc552355208 100644 (file)
@@ -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;
 
 
index a1dcb2b81e9f1f8c42c5faee3581824a7da608a9..383d2ab4434644c867c8e013297c29b2ee6ae4fb 100644 (file)
@@ -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;
         }
     }
 
index 6a7c416c90d2422c105ac8f1ff7dba4bf12ba988..1b3bbf4a97e3e8ba0fb58a082eac31f1ff173304 100644 (file)
@@ -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__) */
index 0dd43f32952ee793b49e9c33c01e3384cfbb2468..97f07f891d71eeb1381a5517fbd004034f981f3a 100644 (file)
@@ -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,
index d340d5e3defbb602c04b5682a8a5b22d26a411f2..6ffeb19240c774e23d0eaae4b095c13a6a3fb75e 100644 (file)
@@ -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);
index 26ce24a8f243337393c7ed3835cdee4e23ce4840..bace4bea3dbda2f2205263394078d3a1e0a50d76 100644 (file)
@@ -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),