You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2023/06/28 11:22:50 UTC

svn commit: r1910656 - in /httpd/httpd/trunk: changes-entries/h2_proxyrequests.txt docs/manual/mod/mod_http2.xml modules/http2/h2_config.c modules/http2/h2_config.h modules/http2/h2_request.c test/modules/http2/test_503_proxy_fwd.py

Author: icing
Date: Wed Jun 28 11:22:49 2023
New Revision: 1910656

URL: http://svn.apache.org/viewvc?rev=1910656&view=rev
Log:
  *) mod_http2: new directive `H2ProxyRequests on|off` to enable handling
     of HTTP/2 requests in a forward proxy configuration.
     General forward proxying is enabled via `ProxyRequests`. If the
     HTTP/2 protocol is also enabled for such a server/host, this new
     directive is needed in addition.


Added:
    httpd/httpd/trunk/changes-entries/h2_proxyrequests.txt
    httpd/httpd/trunk/test/modules/http2/test_503_proxy_fwd.py
Modified:
    httpd/httpd/trunk/docs/manual/mod/mod_http2.xml
    httpd/httpd/trunk/modules/http2/h2_config.c
    httpd/httpd/trunk/modules/http2/h2_config.h
    httpd/httpd/trunk/modules/http2/h2_request.c

Added: httpd/httpd/trunk/changes-entries/h2_proxyrequests.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/h2_proxyrequests.txt?rev=1910656&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/h2_proxyrequests.txt (added)
+++ httpd/httpd/trunk/changes-entries/h2_proxyrequests.txt Wed Jun 28 11:22:49 2023
@@ -0,0 +1,6 @@
+  *) mod_http2: new directive `H2ProxyRequests on|off` to enable handling
+     of HTTP/2 requests in a forward proxy configuration.
+     General forward proxying is enabled via `ProxyRequests`. If the
+     HTTP/2 protocol is also enabled for such a server/host, this new
+     directive is needed in addition.
+     [Stefan Eissing]

Modified: httpd/httpd/trunk/docs/manual/mod/mod_http2.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_http2.xml?rev=1910656&r1=1910655&r2=1910656&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_http2.xml (original)
+++ httpd/httpd/trunk/docs/manual/mod/mod_http2.xml Wed Jun 28 11:22:49 2023
@@ -1120,4 +1120,28 @@ H2EarlyHint Link "</my.css>;rel=pr
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>H2ProxyRequests</name>
+        <description>En-/Disable forward proxy requests via HTTP/2</description>
+        <syntax>H2ProxyRequests  on|off</syntax>
+        <default>H2ProxyRequests off</default>
+        <contextlist>
+            <context>server config</context>
+            <context>virtual host</context>
+        </contextlist>
+        <compatibility>Available in version 2.5.1 and later.</compatibility>
+
+        <usage>
+            <p>
+                Use <directive>H2ProxyRequests</directive> to enable or disable
+                handling of HTTP/2 requests in a forward proxy configuration.
+            </p><p>
+                Similar to <directive module="proxy">ProxyRequests</directive>, this
+                triggers the needed treatment of requests when HTTP/2 is enabled
+                in a forward proxy configuration. Both directive should be enabled.
+            </p><p>
+            </p>
+        </usage>
+    </directivesynopsis>
+
 </modulesynopsis>

Modified: httpd/httpd/trunk/modules/http2/h2_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.c?rev=1910656&r1=1910655&r2=1910656&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.c Wed Jun 28 11:22:49 2023
@@ -77,6 +77,7 @@ typedef struct h2_config {
     int output_buffered;
     apr_interval_time_t stream_timeout;/* beam timeout */
     int max_data_frame_len;          /* max # bytes in a single h2 DATA frame */
+    int proxy_requests;              /* act as forward proxy */
     int h2_websockets;               /* if mod_h2 negotiating WebSockets */
 } h2_config;
 
@@ -116,6 +117,7 @@ static h2_config defconf = {
     1,                      /* stream output buffered */
     -1,                     /* beam timeout */
     0,                      /* max DATA frame len, 0 == no extra limit */
+    0,                      /* forward proxy */
     0,                      /* WebSockets negotiation, enabled */
 };
 
@@ -163,6 +165,7 @@ void *h2_config_create_svr(apr_pool_t *p
     conf->output_buffered      = DEF_VAL;
     conf->stream_timeout       = DEF_VAL;
     conf->max_data_frame_len   = DEF_VAL;
+    conf->proxy_requests       = DEF_VAL;
     conf->h2_websockets        = DEF_VAL;
     return conf;
 }
@@ -213,6 +216,7 @@ static void *h2_config_merge(apr_pool_t
     n->padding_always       = H2_CONFIG_GET(add, base, padding_always);
     n->stream_timeout       = H2_CONFIG_GET(add, base, stream_timeout);
     n->max_data_frame_len   = H2_CONFIG_GET(add, base, max_data_frame_len);
+    n->proxy_requests       = H2_CONFIG_GET(add, base, proxy_requests);
     n->h2_websockets        = H2_CONFIG_GET(add, base, h2_websockets);
     return n;
 }
@@ -305,6 +309,8 @@ static apr_int64_t h2_srv_config_geti64(
             return H2_CONFIG_GET(conf, &defconf, stream_timeout);
         case H2_CONF_MAX_DATA_FRAME_LEN:
             return H2_CONFIG_GET(conf, &defconf, max_data_frame_len);
+        case H2_CONF_PROXY_REQUESTS:
+            return H2_CONFIG_GET(conf, &defconf, proxy_requests);
         case H2_CONF_WEBSOCKETS:
             return H2_CONFIG_GET(conf, &defconf, h2_websockets);
         default:
@@ -369,6 +375,8 @@ static void h2_srv_config_seti(h2_config
         case H2_CONF_MAX_DATA_FRAME_LEN:
             H2_CONFIG_SET(conf, max_data_frame_len, val);
             break;
+        case H2_CONF_PROXY_REQUESTS:
+            H2_CONFIG_SET(conf, proxy_requests, val);
         case H2_CONF_WEBSOCKETS:
             H2_CONFIG_SET(conf, h2_websockets, val);
             break;
@@ -981,6 +989,20 @@ static const char *h2_conf_set_stream_ti
     return NULL;
 }
 
+static const char *h2_conf_set_proxy_requests(cmd_parms *cmd,
+                                              void *dirconf, const char *value)
+{
+    if (!strcasecmp(value, "On")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 1);
+        return NULL;
+    }
+    else if (!strcasecmp(value, "Off")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 0);
+        return NULL;
+    }
+    return "value must be On or Off";
+}
+
 void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
                            apr_time_t *pidle_limit)
 {
@@ -1050,6 +1072,8 @@ const command_rec h2_cmds[] = {
                   RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
     AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
                    OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
+    AP_INIT_TAKE1("H2ProxyRequests", h2_conf_set_proxy_requests, NULL,
+                  OR_FILEINFO, "Enables forward proxy requests via HTTP/2"),
     AP_INIT_TAKE1("H2WebSockets", h2_conf_set_websockets, NULL,
                   RSRC_CONF, "off to disable WebSockets over HTTP/2"),
     AP_END_CMD

Modified: httpd/httpd/trunk/modules/http2/h2_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.h?rev=1910656&r1=1910655&r2=1910656&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.h Wed Jun 28 11:22:49 2023
@@ -44,6 +44,7 @@ typedef enum {
     H2_CONF_OUTPUT_BUFFER,
     H2_CONF_STREAM_TIMEOUT,
     H2_CONF_MAX_DATA_FRAME_LEN,
+    H2_CONF_PROXY_REQUESTS,
     H2_CONF_WEBSOCKETS,
 } h2_config_var_t;
 

Modified: httpd/httpd/trunk/modules/http2/h2_request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_request.c?rev=1910656&r1=1910655&r2=1910656&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_request.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_request.c Wed Jun 28 11:22:49 2023
@@ -38,6 +38,7 @@
 
 #include "h2_private.h"
 #include "h2_config.h"
+#include "h2_conn_ctx.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #include "h2_util.h"
@@ -292,8 +293,14 @@ apr_bucket *h2_request_create_bucket(con
     if (!ap_cstr_casecmp("CONNECT", req->method))  {
         uri = req->authority;
     }
-    else if (req->scheme && (ap_cstr_casecmp(req->scheme, "http") &&
-                             ap_cstr_casecmp(req->scheme, "https")))  {
+    else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+        /* Forward proxying: always absolute uris */
+        uri = apr_psprintf(r->pool, "%s://%s%s",
+                           req->scheme, req->authority,
+                           req->path ? req->path : "");
+    }
+    else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+             && ap_cstr_casecmp(req->scheme, "https")) {
         /* Client sent a non-http ':scheme', use an absolute URI */
         uri = apr_psprintf(r->pool, "%s://%s%s",
                            req->scheme, req->authority, req->path ? req->path : "");
@@ -397,6 +404,30 @@ request_rec *h2_create_request_rec(const
             goto die;
         }
     }
+    else if (req->protocol) {
+      ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10460)
+                    "':protocol: %s' header present in %s request",
+                    req->protocol, req->method);
+      access_status = HTTP_BAD_REQUEST;
+      goto die;
+    }
+    else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+        if (!req->scheme) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO()
+                          "H2ProxyRequests on, but request misses :scheme");
+            access_status = HTTP_BAD_REQUEST;
+            goto die;
+        }
+        if (!req->authority) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO()
+                          "H2ProxyRequests on, but request misses :authority");
+            access_status = HTTP_BAD_REQUEST;
+            goto die;
+        }
+        r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
+                                      req->method, req->scheme, req->authority,
+                                      req->path ? req->path : "");
+    }
     else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
              && ap_cstr_casecmp(req->scheme, "https")) {
         /* Client sent a ':scheme' pseudo header for something else

Added: httpd/httpd/trunk/test/modules/http2/test_503_proxy_fwd.py
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/modules/http2/test_503_proxy_fwd.py?rev=1910656&view=auto
==============================================================================
--- httpd/httpd/trunk/test/modules/http2/test_503_proxy_fwd.py (added)
+++ httpd/httpd/trunk/test/modules/http2/test_503_proxy_fwd.py Wed Jun 28 11:22:49 2023
@@ -0,0 +1,79 @@
+import pytest
+
+from .env import H2Conf, H2TestEnv
+
+
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
+class TestProxyFwd:
+
+    @classmethod
+    def config_fwd_proxy(cls, env, h2_enabled=False):
+        conf = H2Conf(env, extras={
+            'base': [
+                f'Listen {env.proxy_port}',
+                'Protocols h2c http/1.1',
+                'LogLevel proxy_http2:trace2 proxy:trace2',
+            ],
+        })
+        conf.add_vhost_cgi(proxy_self=False, h2proxy_self=False)
+        conf.start_vhost(domains=[f"test1.{env.http_tld}"],
+                         port=env.proxy_port, with_ssl=True)
+        conf.add([
+            'Protocols h2c http/1.1',
+            'ProxyRequests on',
+            f'H2ProxyRequests {"on" if h2_enabled else "off"}',
+        ])
+        conf.end_vhost()
+        conf.install()
+        assert env.apache_restart() == 0
+
+    @pytest.fixture(autouse=True, scope='class')
+    def _class_scope(cls, env):
+        cls.config_fwd_proxy(env)
+
+    # test the HTTP/1.1 setup working
+    def test_h2_503_01_proxy_fwd_h1(self, env):
+        url = f'http://localhost:{env.http_port}/hello.py'
+        proxy_host = f'test1.{env.http_tld}'
+        options = [
+            '--proxy', f'https://{proxy_host}:{env.proxy_port}',
+            '--resolve', f'{proxy_host}:{env.proxy_port}:127.0.0.1',
+            '--proxy-cacert', f'{env.get_ca_pem_file(proxy_host)}',
+        ]
+        r = env.curl_get(url, 5, options=options)
+        assert r.exit_code == 0, f'{r}'
+        assert r.response['status'] == 200
+        assert r.json['port'] == f'{env.http_port}'
+
+    def test_h2_503_02_fwd_proxy_h2_off(self, env):
+        if not env.curl_is_at_least('8.1.0'):
+            pytest.skip(f'need at least curl v8.1.0 for this')
+        url = f'http://localhost:{env.http_port}/hello.py'
+        proxy_host = f'test1.{env.http_tld}'
+        options = [
+            '--proxy-http2', '-v',
+            '--proxy', f'https://{proxy_host}:{env.proxy_port}',
+            '--resolve', f'{proxy_host}:{env.proxy_port}:127.0.0.1',
+            '--proxy-cacert', f'{env.get_ca_pem_file(proxy_host)}',
+        ]
+        r = env.curl_get(url, 5, options=options)
+        assert r.exit_code == 0, f'{r}'
+        assert r.response['status'] == 404
+
+    # test the HTTP/2 setup working
+    def test_h2_503_03_proxy_fwd_h2_on(self, env):
+        if not env.curl_is_at_least('8.1.0'):
+            pytest.skip(f'need at least curl v8.1.0 for this')
+        self.config_fwd_proxy(env, h2_enabled=True)
+        url = f'http://localhost:{env.http_port}/hello.py'
+        proxy_host = f'test1.{env.http_tld}'
+        options = [
+            '--proxy-http2', '-v',
+            '--proxy', f'https://{proxy_host}:{env.proxy_port}',
+            '--resolve', f'{proxy_host}:{env.proxy_port}:127.0.0.1',
+            '--proxy-cacert', f'{env.get_ca_pem_file(proxy_host)}',
+        ]
+        r = env.curl_get(url, 5, options=options)
+        assert r.exit_code == 0, f'{r}'
+        assert r.response['status'] == 200
+        assert r.json['port'] == f'{env.http_port}'