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/21 12:14:08 UTC

svn commit: r1910535 - in /httpd/httpd/trunk: modules/http2/ test/modules/http2/

Author: icing
Date: Wed Jun 21 12:14:08 2023
New Revision: 1910535

URL: http://svn.apache.org/viewvc?rev=1910535&view=rev
Log:
  *) mod_http2: adding checks for websocket support on platform and
     server versions. Give error message accordingly when trying to
     enable websockets in unsupported configurations.
     Add test and code to check the, finally selected, server of
     a request_rec for websocket support or 501 the request.


Modified:
    httpd/httpd/trunk/modules/http2/h2.h
    httpd/httpd/trunk/modules/http2/h2_c2.c
    httpd/httpd/trunk/modules/http2/h2_c2_filter.c
    httpd/httpd/trunk/modules/http2/h2_config.c
    httpd/httpd/trunk/modules/http2/h2_request.c
    httpd/httpd/trunk/modules/http2/h2_stream.c
    httpd/httpd/trunk/modules/http2/h2_ws.c
    httpd/httpd/trunk/test/modules/http2/test_800_websockets.py

Modified: httpd/httpd/trunk/modules/http2/h2.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2.h?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2.h (original)
+++ httpd/httpd/trunk/modules/http2/h2.h Wed Jun 21 12:14:08 2023
@@ -33,6 +33,18 @@ struct h2_stream;
 #define H2_USE_PIPES            (APR_FILES_AS_SOCKETS && APR_VERSION_AT_LEAST(1,6,0))
 #endif
 
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 15)
+#define H2_USE_POLLFD_FROM_CONN 1
+#else
+#define H2_USE_POLLFD_FROM_CONN 0
+#endif
+
+#if H2_USE_POLLFD_FROM_CONN && H2_USE_PIPES
+#define H2_USE_WEBSOCKETS       1
+#else
+#define H2_USE_WEBSOCKETS       0
+#endif
+
 /**
  * The magic PRIamble of RFC 7540 that is always sent when starting
  * a h2 communication.

Modified: httpd/httpd/trunk/modules/http2/h2_c2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c2.c?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c2.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c2.c Wed Jun 21 12:14:08 2023
@@ -559,6 +559,7 @@ static int c2_hook_pre_connection(conn_r
     return OK;
 }
 
+#if H2_USE_POLLFD_FROM_CONN
 static apr_status_t c2_get_pollfd_from_conn(conn_rec *c,
                                             struct apr_pollfd_t *pfd,
                                             apr_interval_time_t *ptimeout)
@@ -583,6 +584,7 @@ static apr_status_t c2_get_pollfd_from_c
     }
     return APR_ENOTIMPL;
 }
+#endif
 
 void h2_c2_register_hooks(void)
 {
@@ -598,12 +600,11 @@ void h2_c2_register_hooks(void)
     ap_hook_post_read_request(c2_post_read_request, NULL, NULL,
                               APR_HOOK_REALLY_FIRST);
     ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
-#if AP_MODULE_MAGIC_AT_LEAST(20211221, 15)
+#if H2_USE_POLLFD_FROM_CONN
     ap_hook_get_pollfd_from_conn(c2_get_pollfd_from_conn, NULL, NULL,
                                  APR_HOOK_MIDDLE);
 #endif
 
-
     c2_net_in_filter_handle =
         ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
                                  NULL, AP_FTYPE_NETWORK);
@@ -788,7 +789,7 @@ static apr_status_t c2_process(h2_conn_c
         cs->state = CONN_STATE_WRITE_COMPLETION;
 
 cleanup:
-    return APR_SUCCESS;
+    return rv;
 }
 
 conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,

Modified: httpd/httpd/trunk/modules/http2/h2_c2_filter.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_c2_filter.c?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_c2_filter.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_c2_filter.c Wed Jun 21 12:14:08 2023
@@ -120,20 +120,28 @@ apr_status_t h2_c2_filter_request_in(ap_
                 return APR_EGENERAL;
         }
 
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                      "h2_c2_filter_request_in(%s): adding request bucket",
+                      conn_ctx->id);
+        b = h2_request_create_bucket(req, f->r);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+
         if (req->http_status != H2_HTTP_STATUS_UNSET) {
             /* error was encountered preparing this request */
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                          "h2_c2_filter_request_in(%s): adding error bucket %d",
+                          conn_ctx->id, req->http_status);
             b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
                                        f->c->bucket_alloc);
             APR_BRIGADE_INSERT_TAIL(bb, b);
             return APR_SUCCESS;
         }
 
-        b = h2_request_create_bucket(req, f->r);
-        APR_BRIGADE_INSERT_TAIL(bb, b);
         if (!conn_ctx->beam_in) {
             b = apr_bucket_eos_create(f->c->bucket_alloc);
             APR_BRIGADE_INSERT_TAIL(bb, b);
         }
+
         return APR_SUCCESS;
     }
 

Modified: httpd/httpd/trunk/modules/http2/h2_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.c?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.c Wed Jun 21 12:14:08 2023
@@ -694,11 +694,13 @@ static const char *h2_conf_set_websocket
                                           void *dirconf, const char *value)
 {
     if (!strcasecmp(value, "On")) {
-#if H2_USE_PIPES
+#if H2_USE_WEBSOCKETS
         CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
         return NULL;
-#else
+#elif !H2_USE_PIPES
         return "HTTP/2 WebSockets are not supported on this platform";
+#else
+        return "HTTP/2 WebSockets are not supported in this server version";
 #endif
     }
     else if (!strcasecmp(value, "Off")) {

Modified: httpd/httpd/trunk/modules/http2/h2_request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_request.c?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_request.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_request.c Wed Jun 21 12:14:08 2023
@@ -287,13 +287,14 @@ apr_bucket *h2_request_create_bucket(con
     apr_table_t *headers = apr_table_clone(r->pool, req->headers);
     const char *uri = req->path;
 
+    AP_DEBUG_ASSERT(req->method);
     AP_DEBUG_ASSERT(req->authority);
-    if (req->scheme && (ap_cstr_casecmp(req->scheme,
-                        ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
-                        || !ap_cstr_casecmp("CONNECT", req->method))) {
-        /* Client sent a non-matching ':scheme' pseudo header or CONNECT.
-         * In this case, we use an absolute URI.
-         */
+    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")))  {
+        /* 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 : "");
     }
@@ -379,33 +380,25 @@ request_rec *h2_create_request_rec(const
     AP_DEBUG_ASSERT(req->authority);
     if (is_connect) {
       /* CONNECT MUST NOT have scheme or path */
-      if (req->scheme) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
-                      "':scheme: %s' header present in CONNECT request",
-                      req->scheme);
-        access_status = HTTP_BAD_REQUEST;
-        goto die;
-      }
-      if (req->path) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
-                      "':path: %s' header present in CONNECT request",
-                      req->path);
-        access_status = HTTP_BAD_REQUEST;
-        goto die;
-      }
-      r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
-                                    req->method, req->authority);
-    }
-    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;
+        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+                                      req->method, req->authority);
+        if (req->scheme) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
+                          "':scheme: %s' header present in CONNECT request",
+                          req->scheme);
+            access_status = HTTP_BAD_REQUEST;
+            goto die;
+        }
+        else if (req->path) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
+                          "':path: %s' header present in CONNECT request",
+                          req->path);
+            access_status = HTTP_BAD_REQUEST;
+            goto die;
+        }
     }
-    else if (req->scheme &&
-             ap_cstr_casecmp(req->scheme, ap_ssl_conn_is_ssl(c->master? c->master : c)?
-                             "https" : "http")) {
+    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
          * than what we have on this connection. Make an absolute URI. */
         r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",

Modified: httpd/httpd/trunk/modules/http2/h2_stream.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_stream.c?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_stream.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_stream.c Wed Jun 21 12:14:08 2023
@@ -900,11 +900,23 @@ apr_status_t h2_stream_end_headers(h2_st
      *      of CONNECT requests (see [RFC7230], Section 5.3)).
      */
     if (!ap_cstr_casecmp(req->method, "CONNECT")) {
-        if (req->protocol && !strcmp("websocket", req->protocol)) {
-            if (!req->scheme || !req->path) {
-                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
-                              H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
-                              "without :scheme or :path, sending 400 answer"));
+        if (req->protocol) {
+            if (!strcmp("websocket", req->protocol)) {
+                if (!req->scheme || !req->path) {
+                    ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+                                  H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
+                                  "without :scheme or :path, sending 400 answer"));
+                    set_error_response(stream, HTTP_BAD_REQUEST);
+                    goto cleanup;
+                }
+            }
+            else {
+                /* do not know that protocol */
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1, APLOGNO(10460)
+                              "':protocol: %s' header present in %s request",
+                              req->protocol, req->method);
+                set_error_response(stream, HTTP_NOT_IMPLEMENTED);
+                goto cleanup;
             }
         }
         else if (req->scheme || req->path) {

Modified: httpd/httpd/trunk/modules/http2/h2_ws.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_ws.c?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_ws.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_ws.c Wed Jun 21 12:14:08 2023
@@ -43,6 +43,8 @@
 #include "h2_request.h"
 #include "h2_ws.h"
 
+#if H2_USE_WEBSOCKETS
+
 static ap_filter_rec_t *c2_ws_out_filter_handle;
 
 struct ws_filter_ctx {
@@ -318,9 +320,41 @@ static apr_status_t h2_c2_ws_filter_out(
     return ap_pass_brigade(f->next, bb);
 }
 
+static int ws_post_read(request_rec *r)
+{
+
+    if (r->connection->master) {
+        h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+        if (conn_ctx && conn_ctx->is_upgrade &&
+            !h2_config_sgeti(r->server, H2_CONF_WEBSOCKETS)) {
+            return HTTP_NOT_IMPLEMENTED;
+        }
+    }
+    return DECLINED;
+}
+
 void h2_ws_register_hooks(void)
 {
+    ap_hook_post_read_request(ws_post_read, NULL, NULL, APR_HOOK_MIDDLE);
     c2_ws_out_filter_handle =
         ap_register_output_filter("H2_C2_WS_OUT", h2_c2_ws_filter_out,
                                   NULL, AP_FTYPE_NETWORK);
 }
+
+#else /* H2_USE_WEBSOCKETS */
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+                                        conn_rec *c2, int no_body)
+{
+    (void)c2;
+    (void)no_body;
+    /* no rewriting */
+    return req;
+}
+
+void h2_ws_register_hooks(void)
+{
+    /*  NOP */
+}
+
+#endif /* H2_USE_WEBSOCKETS (else part) */

Modified: httpd/httpd/trunk/test/modules/http2/test_800_websockets.py
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/modules/http2/test_800_websockets.py?rev=1910535&r1=1910534&r2=1910535&view=diff
==============================================================================
--- httpd/httpd/trunk/test/modules/http2/test_800_websockets.py (original)
+++ httpd/httpd/trunk/test/modules/http2/test_800_websockets.py Wed Jun 21 12:14:08 2023
@@ -5,11 +5,8 @@ import shutil
 import subprocess
 import time
 from datetime import timedelta, datetime
-from typing import Tuple, Union, List
-import packaging.version
 
 import pytest
-import websockets
 from pyhttpd.result import ExecResult
 from pyhttpd.ws_util import WsFrameReader, WsFrame
 
@@ -18,18 +15,15 @@ from .env import H2Conf, H2TestEnv
 
 log = logging.getLogger(__name__)
 
-ws_version = packaging.version.parse(websockets.version.version)
-ws_version_min = packaging.version.Version('10.4')
 
-
-def ws_run(env: H2TestEnv, path, do_input=None,
-           inbytes=None, send_close=True,
-           timeout=5, scenario='ws-stdin',
-           wait_close: float = 0.0) -> Tuple[ExecResult, List[str], Union[List[WsFrame], bytes]]:
+def ws_run(env: H2TestEnv, path, authority=None, do_input=None, inbytes=None,
+           send_close=True, timeout=5, scenario='ws-stdin',
+           wait_close: float = 0.0):
     """ Run the h2ws test client in various scenarios with given input and
         timings.
     :param env: the test environment
     :param path: the path on the Apache server to CONNECt to
+    :param authority: the host:port to use as
     :param do_input: a Callable for sending input to h2ws
     :param inbytes: fixed bytes to send to h2ws, unless do_input is given
     :param send_close: send a CLOSE WebSockets frame at the end
@@ -41,9 +35,11 @@ def ws_run(env: H2TestEnv, path, do_inpu
     h2ws = os.path.join(env.clients_dir, 'h2ws')
     if not os.path.exists(h2ws):
         pytest.fail(f'test client not build: {h2ws}')
+    if authority is None:
+        authority = f'cgi.{env.http_tld}:{env.http_port}'
     args = [
         h2ws, '-vv', '-c', f'localhost:{env.http_port}',
-        f'ws://cgi.{env.http_tld}:{env.http_port}{path}',
+        f'ws://{authority}{path}',
         scenario
     ]
     # we write all output to files, because we manipulate input timings
@@ -80,8 +76,8 @@ def ws_run(env: H2TestEnv, path, do_inpu
 
 
 @pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
-@pytest.mark.skipif(condition=ws_version < ws_version_min,
-                    reason=f'websockets is {ws_version}, need at least {ws_version_min}')
+@pytest.mark.skipif(condition=not H2TestEnv().httpd_is_at_least("2.5.0"),
+                    reason=f'need at least httpd 2.5.0 for this')
 class TestWebSockets:
 
     @pytest.fixture(autouse=True, scope='class')
@@ -97,6 +93,7 @@ class TestWebSockets:
             ]
         })
         conf.add_vhost_cgi(proxy_self=True, h2proxy_self=True).install()
+        conf.add_vhost_test1(proxy_self=True, h2proxy_self=True).install()
         assert env.apache_restart() == 0
 
     def ws_check_alive(self, env, timeout=5):
@@ -150,7 +147,7 @@ class TestWebSockets:
     def test_h2_800_02_fail_proto(self, env: H2TestEnv, ws_server):
         r, infos, frames = ws_run(env, path='/ws/echo/', scenario='fail-proto')
         assert r.exit_code == 0, f'{r}'
-        assert infos == ['[1] :status: 400', '[1] EOF'], f'{r}'
+        assert infos == ['[1] :status: 501', '[1] EOF'], f'{r}'
 
     # CONNECT to a URL path that does not exist on the server
     def test_h2_800_03_not_found(self, env: H2TestEnv, ws_server):
@@ -193,11 +190,18 @@ class TestWebSockets:
         assert infos == ['[1] RST'], f'{r}'
 
     # CONNECT missing the :authority header
-    def test_h2_800_09_miss_authority(self, env: H2TestEnv, ws_server):
+    def test_h2_800_09a_miss_authority(self, env: H2TestEnv, ws_server):
         r, infos, frames = ws_run(env, path='/ws/echo/', scenario='miss-authority')
         assert r.exit_code == 0, f'{r}'
         assert infos == ['[1] RST'], f'{r}'
 
+    # CONNECT to authority with disabled websockets
+    def test_h2_800_09b_unsupported(self, env: H2TestEnv, ws_server):
+        r, infos, frames = ws_run(env, path='/ws/echo/',
+                                  authority=f'test1.{env.http_tld}:{env.http_port}')
+        assert r.exit_code == 0, f'{r}'
+        assert infos == ['[1] :status: 501', '[1] EOF'], f'{r}'
+
     # CONNECT and exchange a PING
     def test_h2_800_10_ws_ping(self, env: H2TestEnv, ws_server):
         ping = WsFrame.client_ping(b'12345')