From: Yann Ylavic Date: Thu, 7 Jan 2021 13:19:08 +0000 (+0000) Subject: mod_proxy_wstunnel: leave Upgrade requests handling to mod_proxy_http. X-Git-Tag: 2.5.0-alpha2-ci-test-only~1062 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=35a265606855386c80b1bd0ea8b8fe1cf6a8e01a;p=thirdparty%2Fapache%2Fhttpd.git mod_proxy_wstunnel: leave Upgrade requests handling to mod_proxy_http. 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 --- diff --git a/changes-entries/proxy_wstunnel_to_http.txt b/changes-entries/proxy_wstunnel_to_http.txt new file mode 100644 index 00000000000..b2068afab5f --- /dev/null +++ b/changes-entries/proxy_wstunnel_to_http.txt @@ -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] + diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number index 685949ac36d..d78ac9c3ca2 100644 --- a/docs/log-message-tags/next-number +++ b/docs/log-message-tags/next-number @@ -1 +1 @@ -10262 +10263 diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 0fc5b1e3824..b29b9a7c0e0 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -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 diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index c02e089f670..657ef248898 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -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; } diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c index bc9ffe73ca1..e8750294062 100644 --- a/modules/proxy/mod_proxy_wstunnel.c +++ b/modules/proxy/mod_proxy_wstunnel.c @@ -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 */ }; diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index db9ccf5cdfc..4db1b5ecf05 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -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); } /*