]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
mod_proxy_wstunnel: leave Upgrade requests handling to mod_proxy_http.
authorYann Ylavic <ylavic@apache.org>
Thu, 7 Jan 2021 13:19:08 +0000 (13:19 +0000)
committerYann Ylavic <ylavic@apache.org>
Thu, 7 Jan 2021 13:19:08 +0000 (13:19 +0000)
Let mod_proxy_http's canon and scheme handlers accept "ws[s]:" schemes so that
mod_proxy_wstunnel can decline requests when mod_proxy_http is loaded.

* modules/proxy/{mod_proxy.h,proxy_util.c} (ap_proxy_worker_can_upgrade):
  Add a "dflt" argument to ap_proxy_worker_can_upgrade() which, if not NULL,
  is matched when no worker upgrade= parameter is configured. This allows to
  handle the default "Upgrade: websocket" case for "ws[s]:" schemes.

* modules/proxy/mod_proxy_http.c (proxy_http_canon, proxy_http_handler):
  Add and use the new get_url_scheme() helper to parse URL schemes handled by
  mod_proxy_http and use it in canon and scheme handlers. This helper now
  accepts ws[s] schemes.

* modules/proxy/mod_proxy_wstunnel.c (proxy_wstunnel_post_config):
  New post_config hook to detect whether mod_proxy_http is loaded and set
  global fallback_to_mod_proxy_http flag in this case.

* modules/proxy/mod_proxy_wstunnel.c (proxy_wstunnel_check_trans,
                                      proxy_wstunnel_canon,
                                      proxy_wstunnel_handler):
  These hooks now early return DECLINED if fallback_to_mod_proxy_http is set.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1885239 13f79535-47bb-0310-9956-ffa450edef68

changes-entries/proxy_wstunnel_to_http.txt [new file with mode: 0644]
docs/log-message-tags/next-number
modules/proxy/mod_proxy.h
modules/proxy/mod_proxy_http.c
modules/proxy/mod_proxy_wstunnel.c
modules/proxy/proxy_util.c

diff --git a/changes-entries/proxy_wstunnel_to_http.txt b/changes-entries/proxy_wstunnel_to_http.txt
new file mode 100644 (file)
index 0000000..b2068af
--- /dev/null
@@ -0,0 +1,4 @@
+  *) mod_proxy_wstunnel: Leave Upgrade requests handling to mod_proxy_http,
+     allowing for (non-)Upgrade negotiation with the origin server.
+     [Yann Ylavic]
+
index 685949ac36d0b3bdb914de838431d963aeb81486..d78ac9c3ca2131a6e876c306adf2272b0b639381 100644 (file)
@@ -1 +1 @@
-10262
+10263
index 0fc5b1e38240b671743bcfea83097f31a9df8e01..b29b9a7c0e0d56fb71435541b22bb22e04ea0452 100644 (file)
@@ -755,11 +755,13 @@ PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
  * @param p       memory pool used for displaying worker name
  * @param worker  the worker
  * @param upgrade the Upgrade header to match
+ * @param dflt    default protocol (NULL for none)
  * @return        1 (true) or 0 (false)
  */
 PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
                                                const proxy_worker *worker,
-                                               const char *upgrade);
+                                               const char *upgrade,
+                                               const char *dflt);
 
 /**
  * Get the worker from proxy configuration
index c02e089f6701b7f690b3e66848d6688d4df09ba7..657ef248898c545d5b7f41f62e3ec1a030e6d2d4 100644 (file)
@@ -28,37 +28,71 @@ static int (*ap_proxy_clear_connection_fn)(request_rec *r, apr_table_t *headers)
 static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n,
                                     request_rec *r, int flags, int *read);
 
+static const char *get_url_scheme(const char **url, int *is_ssl)
+{
+    const char *u = *url;
+
+    switch (u[0]) {
+    case 'h':
+    case 'H':
+        if (strncasecmp(u + 1, "ttp", 3) == 0) {
+            if (u[4] == ':') {
+                *is_ssl = 0;
+                *url = u + 5;
+                return "http";
+            }
+            if (apr_tolower(u[4]) == 's' && u[5] == ':') {
+                *is_ssl = 1;
+                *url = u + 6;
+                return "https";
+            }
+        }
+        break;
+
+    case 'w':
+    case 'W':
+        if (apr_tolower(u[1]) == 's') {
+            if (u[2] == ':') {
+                *is_ssl = 0;
+                *url = u + 3;
+                return "ws";
+            }
+            if (apr_tolower(u[2]) == 's' && u[3] == ':') {
+                *is_ssl = 0;
+                *url = u + 4;
+                return "wss";
+            }
+        }
+        break;
+    }
+
+    *is_ssl = 0;
+    return NULL;
+}
 
 /*
  * Canonicalise http-like URLs.
  *  scheme is the scheme for the URL
  *  url    is the URL starting with the first '/'
- *  def_port is the default port for this scheme.
  */
 static int proxy_http_canon(request_rec *r, char *url)
 {
+    const char *base_url = url;
     char *host, *path, sport[7];
     char *search = NULL;
     const char *err;
     const char *scheme;
     apr_port_t port, def_port;
+    int is_ssl = 0;
 
-    /* ap_port_of_scheme() */
-    if (ap_cstr_casecmpn(url, "http:", 5) == 0) {
-        url += 5;
-        scheme = "http";
-    }
-    else if (ap_cstr_casecmpn(url, "https:", 6) == 0) {
-        url += 6;
-        scheme = "https";
-    }
-    else {
+    scheme = get_url_scheme((const char **)&url, &is_ssl);
+    if (!scheme) {
         return DECLINED;
     }
-    port = def_port = ap_proxy_port_of_scheme(scheme);
+    port = def_port = (is_ssl) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
 
     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
-                  "HTTP: canonicalising URL %s", url);
+                  "HTTP: canonicalising URL %s", base_url);
 
     /* do syntatic check.
      * We break the URL into host, port, path, search
@@ -66,7 +100,7 @@ static int proxy_http_canon(request_rec *r, char *url)
     err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
     if (err) {
         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01083)
-                      "error parsing URL %s: %s", url, err);
+                      "error parsing URL %s: %s", base_url, err);
         return HTTP_BAD_REQUEST;
     }
 
@@ -106,8 +140,9 @@ static int proxy_http_canon(request_rec *r, char *url)
     if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
         host = apr_pstrcat(r->pool, "[", host, "]", NULL);
     }
+
     r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
-            "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+                              "/", path, (search) ? "?" : "", search, NULL);
     return OK;
 }
 
@@ -1568,7 +1603,9 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
          * may be an HTTP_UPGRADE_REQUIRED response or some other status where
          * Upgrade makes sense to negotiate the protocol by other means.
          */
-        if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade)) {
+        if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade,
+                                                   (*req->proto == 'w')
+                                                   ? "WebSocket" : NULL)) {
             apr_table_setn(r->headers_out, "Connection", "Upgrade");
             apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, upgrade));
         }
@@ -1840,9 +1877,8 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
                               apr_port_t proxyport)
 {
     int status;
-    char *scheme;
-    const char *proxy_function;
-    const char *u;
+    const char *scheme;
+    const char *u = url;
     proxy_http_req_t *req = NULL;
     proxy_conn_rec *backend = NULL;
     apr_bucket_brigade *input_brigade = NULL;
@@ -1860,41 +1896,31 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
     apr_pool_t *p = r->pool;
     apr_uri_t *uri;
 
-    /* find the scheme */
-    u = strchr(url, ':');
-    if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0')
-        return DECLINED;
-    if ((u - url) > 14)
-        return HTTP_BAD_REQUEST;
-    scheme = apr_pstrmemdup(p, url, u - url);
-    /* scheme is lowercase */
-    ap_str_tolower(scheme);
-    /* is it for us? */
-    if (strcmp(scheme, "https") == 0) {
-        if (!ap_proxy_ssl_enable(NULL)) {
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112)
-                          "HTTPS: declining URL %s (mod_ssl not configured?)",
-                          url);
-            return DECLINED;
-        }
-        is_ssl = 1;
-        proxy_function = "HTTPS";
+    scheme = get_url_scheme(&u, &is_ssl);
+    if (!scheme && proxyname && strncasecmp(url, "ftp:", 4) == 0) {
+        u = url + 4;
+        scheme = "ftp";
+        is_ssl = 0;
     }
-    else if (!(strcmp(scheme, "http") == 0 || (strcmp(scheme, "ftp") == 0 && proxyname))) {
-        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113) "HTTP: declining URL %s",
-                      url);
-        return DECLINED; /* only interested in HTTP, or FTP via proxy */
+    if (!scheme || u[0] != '/' || u[1] != '/' || u[2] == '\0') {
+        if (!scheme && (u = strchr(url, ':')) && (u - url) > 14) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10262)
+                          "overlong proxy URL scheme in %s", url);
+            return HTTP_BAD_REQUEST;
+        }
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113)
+                      "HTTP: declining URL %s", url);
+        return DECLINED; /* only interested in HTTP, WS or FTP via proxy */
     }
-    else {
-        if (*scheme == 'h')
-            proxy_function = "HTTP";
-        else
-            proxy_function = "FTP";
+    if (is_ssl && !ap_proxy_ssl_enable(NULL)) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112)
+                      "HTTP: declining URL %s (mod_ssl not configured?)", url);
+        return DECLINED;
     }
     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "HTTP: serving URL %s", url);
 
     /* create space for state information */
-    if ((status = ap_proxy_acquire_connection(proxy_function, &backend,
+    if ((status = ap_proxy_acquire_connection(scheme, &backend,
                                               worker, r->server)) != OK) {
         return status;
     }
@@ -1910,7 +1936,7 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
     req->dconf = dconf;
     req->worker = worker;
     req->backend = backend;
-    req->proto = proxy_function;
+    req->proto = scheme;
     req->bucket_alloc = c->bucket_alloc;
     req->can_go_async = (mpm_can_poll &&
                          dconf->async_delay_set &&
@@ -1921,10 +1947,14 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
     if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
         req->force10 = 1;
     }
-    else if (*worker->s->upgrade) {
-        /* Forward Upgrade header if it matches the configured one(s). */
+    else if (*worker->s->upgrade || *req->proto == 'w') {
+        /* Forward Upgrade header if it matches the configured one(s),
+         * the default being "WebSocket" for ws[s] schemes.
+         */
         const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
-        if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade)) {
+        if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade,
+                                                   (*req->proto == 'w')
+                                                   ? "WebSocket" : NULL)) {
             req->upgrade = upgrade;
         }
     }
@@ -2046,9 +2076,9 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
         }
 
         /* Step Two: Make the Connection */
-        if (ap_proxy_check_connection(proxy_function, backend, r->server, 1,
+        if (ap_proxy_check_connection(scheme, backend, r->server, 1,
                                       PROXY_CHECK_CONN_EMPTY)
-                && ap_proxy_connect_backend(proxy_function, backend, worker,
+                && ap_proxy_connect_backend(scheme, backend, worker,
                                             r->server)) {
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01114)
                           "HTTP: failed to make connection to backend: %s",
@@ -2058,8 +2088,7 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
         }
 
         /* Step Three: Create conn_rec */
-        if ((status = ap_proxy_connection_create_ex(proxy_function,
-                                                    backend, r)) != OK)
+        if ((status = ap_proxy_connection_create_ex(scheme, backend, r)) != OK)
             break;
         req->origin = backend->connection;
 
@@ -2104,8 +2133,7 @@ cleanup:
     if (req->backend) {
         if (status != OK)
             req->backend->close = 1;
-        ap_proxy_release_connection(proxy_function, req->backend,
-                                    r->server);
+        ap_proxy_release_connection(scheme, req->backend, r->server);
     }
     return status;
 }
index bc9ffe73ca1edd32177a95aec0017f6b81480acb..e8750294062f297a6b0aaf28181c944e28767ae8 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include "mod_proxy.h"
+#include "http_config.h"
 #include "ap_mpm.h"
 
 module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
@@ -33,6 +34,8 @@ typedef struct ws_baton_t {
     const char *scheme;
 } ws_baton_t;
 
+static int fallback_to_mod_proxy_http;
+
 static void proxy_wstunnel_callback(void *b);
 
 static int proxy_wstunnel_pump(ws_baton_t *baton, int async)
@@ -108,6 +111,11 @@ static void proxy_wstunnel_callback(void *b)
 
 static int proxy_wstunnel_check_trans(request_rec *r, const char *url)
 {
+    if (fallback_to_mod_proxy_http) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "check_trans fallback");
+        return DECLINED;
+    }
+
     if (ap_cstr_casecmpn(url, "ws:", 3) != 0
             && ap_cstr_casecmpn(url, "wss:", 4) != 0) {
         return DECLINED;
@@ -138,6 +146,11 @@ static int proxy_wstunnel_canon(request_rec *r, char *url)
     char *scheme;
     apr_port_t port, def_port;
 
+    if (fallback_to_mod_proxy_http) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "canon fallback");
+        return DECLINED;
+    }
+
     /* ap_port_of_scheme() */
     if (ap_cstr_casecmpn(url, "ws:", 3) == 0) {
         url += 3;
@@ -325,6 +338,11 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
     apr_uri_t *uri;
     int is_ssl = 0;
 
+    if (fallback_to_mod_proxy_http) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "handler fallback");
+        return DECLINED;
+    }
+
     if (ap_cstr_casecmpn(url, "wss:", 4) == 0) {
         scheme = "WSS";
         is_ssl = 1;
@@ -333,15 +351,15 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
         scheme = "WS";
     }
     else {
-        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) "declining URL %s", url);
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450)
+                      "declining URL %s", url);
         return DECLINED;
     }
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "serving URL %s", url);
 
     upgrade = apr_table_get(r->headers_in, "Upgrade");
-    if (!upgrade || (*worker->s->upgrade &&
-                     !ap_proxy_worker_can_upgrade(p, worker, upgrade))
-                 || (!*worker->s->upgrade &&
-                     ap_cstr_casecmp(upgrade, "WebSocket") != 0)) {
+    if (!upgrade || !ap_proxy_worker_can_upgrade(p, worker, upgrade,
+                                                 "WebSocket")) {
         const char *worker_upgrade = *worker->s->upgrade ? worker->s->upgrade
                                                          : "WebSocket";
         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900)
@@ -431,6 +449,24 @@ static const char * proxyws_set_asynch_delay(cmd_parms *cmd, void *conf, const c
     return NULL;
 }
 
+static int proxy_wstunnel_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+                                      apr_pool_t *ptemp, server_rec *s)
+{
+    fallback_to_mod_proxy_http = 0;
+    if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG) {
+        apr_size_t i = 0;
+        const module *mod;
+        while ((mod = ap_loaded_modules[i++])) {
+            if (strcmp(mod->name, "mod_proxy_http.c") == 0) {
+                fallback_to_mod_proxy_http = 1;
+                break;
+            }
+        }
+    }
+
+    return OK;
+}
+
 static const command_rec ws_proxy_cmds[] =
 {
     AP_INIT_TAKE1("ProxyWebsocketIdleTimeout", proxyws_set_idle, NULL,
@@ -443,9 +479,10 @@ static const command_rec ws_proxy_cmds[] =
     {NULL}
 };
 
-static void ap_proxy_http_register_hook(apr_pool_t *p)
+static void ws_proxy_hooks(apr_pool_t *p)
 {
     static const char * const aszSucc[] = { "mod_proxy_http.c", NULL};
+    ap_hook_post_config(proxy_wstunnel_post_config, NULL, NULL, APR_HOOK_MIDDLE);
     proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, aszSucc, APR_HOOK_FIRST);
     proxy_hook_check_trans(proxy_wstunnel_check_trans, NULL, aszSucc, APR_HOOK_MIDDLE);
     proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, aszSucc, APR_HOOK_FIRST);
@@ -458,5 +495,5 @@ AP_DECLARE_MODULE(proxy_wstunnel) = {
     NULL,                       /* create per-server config structure */
     NULL,                       /* merge per-server config structures */
     ws_proxy_cmds,              /* command apr_table_t */
-    ap_proxy_http_register_hook /* register hooks */
+    ws_proxy_hooks              /* register hooks */
 };
index db9ccf5cdfcde501c50f2afcd7e4c969532da9ff..4db1b5ecf052ddf3a4f5ca326a16836684eb9891 100644 (file)
@@ -1656,13 +1656,19 @@ PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
 
 PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
                                                const proxy_worker *worker,
-                                               const char *upgrade)
+                                               const char *upgrade,
+                                               const char *dflt)
 {
+    /* Find in worker->s->upgrade list (if any) */
     const char *worker_upgrade = worker->s->upgrade;
-    return (*worker_upgrade
-            && (strcmp(worker_upgrade, "*") == 0
+    if (*worker_upgrade) {
+        return (strcmp(worker_upgrade, "*") == 0
                 || ap_cstr_casecmp(worker_upgrade, upgrade) == 0
-                || ap_find_token(p, worker_upgrade, upgrade)));
+                || ap_find_token(p, worker_upgrade, upgrade));
+    }
+
+    /* Compare to the provided default (if any) */
+    return (dflt && ap_cstr_casecmp(dflt, upgrade) == 0);
 }
 
 /*