From: Jim Jagielski Date: Thu, 13 Jun 2013 15:09:31 +0000 (+0000) Subject: Merge r1452911, r1452949, r1452954, r1453022, r1453574, r1453875, r1453876, r1453963... X-Git-Tag: 2.4.5~123 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2d1f1bf01e86587a1c15d2dda31a84e41b0ada17;p=apache Merge r1452911, r1452949, r1452954, r1453022, r1453574, r1453875, r1453876, r1453963, r1454386, r1454414, r1454415, r1458285, r1458447 from trunk: Rough start for simple, tunneling websocket proxy support. Compiles at this stage and that's all I know :) force correct scheme info We need to ensure a conn_rec Work around blocking issues... And now pass the initial request to the backend... I dislike this duplication of code from mod_proxy_http() but maybe it's inevitable. I may create ap_proxy_create_headerb() to create a header brigade that both modules can use. Pull out duplicated code to proxy_util... rename extension module... tunnel is more accurate log nums "final" rename :) don't strip these, ensure that they exist. streamline bypass reqtimeout for websockets Keep mod_req clear. Simply scan thru input filters and remove it within the ws submodule. Nasty, but it keeps mod_req untouched (for now ;) ) Reviewed/backported by: jim git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1492699 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/Apache-apr2.dsw b/Apache-apr2.dsw index 8ffeb21962..da295a5dd3 100644 --- a/Apache-apr2.dsw +++ b/Apache-apr2.dsw @@ -288,6 +288,9 @@ Package=<4> Project_Dep_Name mod_proxy_scgi End Project Dependency Begin Project Dependency + Project_Dep_Name mod_proxy_wstunnel + End Project Dependency + Begin Project Dependency Project_Dep_Name mod_ratelimit End Project Dependency Begin Project Dependency @@ -2353,6 +2356,27 @@ Package=<4> }}} +############################################################################### + +Project: "mod_proxy_wstunnel"=.\modules\proxy\mod_proxy_wstunnel.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name libapr + End Project Dependency + Begin Project Dependency + Project_Dep_Name libhttpd + End Project Dependency + Begin Project Dependency + Project_Dep_Name mod_proxy + End Project Dependency +}}} + ############################################################################### Project: "mod_ratelimit"=.\modules\filters\mod_ratelimit.dsp - Package Owner=<4> diff --git a/Apache.dsw b/Apache.dsw index 5c00b537a9..9ea521ca33 100644 --- a/Apache.dsw +++ b/Apache.dsw @@ -303,6 +303,9 @@ Package=<4> Project_Dep_Name mod_proxy_scgi End Project Dependency Begin Project Dependency + Project_Dep_Name mod_proxy_wstunnel + End Project Dependency + Begin Project Dependency Project_Dep_Name mod_ratelimit End Project Dependency Begin Project Dependency @@ -2765,6 +2768,30 @@ Package=<4> ############################################################################### +Project: "mod_proxy_wstunnel"=.\modules\proxy\mod_proxy_wstunnel.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name libapr + End Project Dependency + Begin Project Dependency + Project_Dep_Name libaprutil + End Project Dependency + Begin Project Dependency + Project_Dep_Name libhttpd + End Project Dependency + Begin Project Dependency + Project_Dep_Name mod_proxy + End Project Dependency +}}} + +############################################################################### + Project: "mod_ratelimit"=.\modules\filters\mod_ratelimit.dsp - Package Owner=<4> Package=<5> diff --git a/CHANGES b/CHANGES index ceb6c3434f..0f840d368d 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,9 @@ Changes with Apache 2.4.5 + *) mod_proxy: Support web-socket tunnels via mod_proxy_wstunnel. + [Jim Jagielski] + *) mod_cache_socache: Use the name of the socache implementation when performing a lookup rather than using the raw arguments. [Martin Ksellmann ] diff --git a/Makefile.win b/Makefile.win index 76c08731d2..ec5889ec7c 100644 --- a/Makefile.win +++ b/Makefile.win @@ -526,6 +526,7 @@ _build: $(MAKE) $(MAKEOPT) -f mod_proxy_ftp.mak CFG="mod_proxy_ftp - Win32 $(LONG)" RECURSE=0 $(CTARGET) $(MAKE) $(MAKEOPT) -f mod_proxy_http.mak CFG="mod_proxy_http - Win32 $(LONG)" RECURSE=0 $(CTARGET) $(MAKE) $(MAKEOPT) -f mod_proxy_scgi.mak CFG="mod_proxy_scgi - Win32 $(LONG)" RECURSE=0 $(CTARGET) + $(MAKE) $(MAKEOPT) -f mod_proxy_wstunnel.mak CFG="mod_proxy_wstunnel - Win32 $(LONG)" RECURSE=0 $(CTARGET) cd ..\.. cd modules\proxy\balancers $(MAKE) $(MAKEOPT) -f mod_lbmethod_bybusyness.mak CFG="mod_lbmethod_bybusyness - Win32 $(LONG)" RECURSE=0 $(CTARGET) @@ -787,6 +788,7 @@ _copybin: copy modules\proxy\$(LONG)\mod_proxy_ftp.$(src_so) "$(inst_so)" <.y copy modules\proxy\$(LONG)\mod_proxy_http.$(src_so) "$(inst_so)" <.y copy modules\proxy\$(LONG)\mod_proxy_scgi.$(src_so) "$(inst_so)" <.y + copy modules\proxy\$(LONG)\mod_proxy_wstunnel.$(src_so) "$(inst_so)" <.y copy modules\proxy\balancers\$(LONG)\mod_lbmethod_bybusyness.$(src_so) "$(inst_so)" <.y copy modules\proxy\balancers\$(LONG)\mod_lbmethod_byrequests.$(src_so) "$(inst_so)" <.y copy modules\proxy\balancers\$(LONG)\mod_lbmethod_bytraffic.$(src_so) "$(inst_so)" <.y diff --git a/STATUS b/STATUS index 021b9ceda3..a0ab82736f 100644 --- a/STATUS +++ b/STATUS @@ -90,25 +90,6 @@ RELEASE SHOWSTOPPERS: PATCHES ACCEPTED TO BACKPORT FROM TRUNK: [ start all new proposals below, under PATCHES PROPOSED. ] - * mod_proxy_wstunnel: Backport (copy) websocket tunnel proxy submodule - trunk patch: http://svn.apache.org/viewvc?view=revision&revision=1452911 - http://svn.apache.org/viewvc?view=revision&revision=1452949 - http://svn.apache.org/viewvc?view=revision&revision=1452954 - http://svn.apache.org/viewvc?view=revision&revision=1453022 - http://svn.apache.org/viewvc?view=revision&revision=1453574 - http://svn.apache.org/viewvc?view=revision&revision=1453875 - http://svn.apache.org/viewvc?view=revision&revision=1453876 - http://svn.apache.org/viewvc?view=revision&revision=1453963 - http://svn.apache.org/viewvc?view=revision&revision=1454386 - http://svn.apache.org/viewvc?view=revision&revision=1454414 - http://svn.apache.org/viewvc?view=revision&revision=1454415 - http://svn.apache.org/viewvc?view=revision&revision=1458285 - http://svn.apache.org/viewvc?view=revision&revision=1458447 - 2.4.x patch: http://people.apache.org/~jim/patches/wstunnel.patch - +1: jim, minfrin (with fix to mmn bump) - gsmith: Windows will need these as well, then +1 - http://people.apache.org/~gsmith/httpd/diffs/wstunnel_winbits.diff - * rotatelogs: support -n number-of-files for circular set of filenames trunk patch: http://svn.apache.org/r1490493 http://svn.apache.org/r1490761 diff --git a/build/installwinconf.awk b/build/installwinconf.awk index 1a537462cb..d8db0fc603 100644 --- a/build/installwinconf.awk +++ b/build/installwinconf.awk @@ -167,6 +167,7 @@ BEGIN { print "#LoadModule proxy_html_module modules/mod_proxy_html.so" > dstfl; print "#LoadModule proxy_http_module modules/mod_proxy_http.so" > dstfl; print "#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so" > dstfl; + print "#LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so" > dstfl; print "#LoadModule ratelimit_module modules/mod_ratelimit.so" > dstfl; print "#LoadModule reflector_module modules/mod_reflector.so" > dstfl; print "#LoadModule remoteip_module modules/mod_remoteip.so" > dstfl; diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 3531807807..4c08be80b8 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -414,6 +414,8 @@ * ap_condition_if_range() * 20120211.19 (2.4.5-dev) Add post_perdir_config hook. * 20120211.20 (2.4.5-dev) Add dirwalk_stat hook. + * 20120211.21 (2.4.5-dev) Add in ap_proxy_create_hdrbrgd() and + * ap_proxy_pass_brigade() */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -421,7 +423,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20120211 #endif -#define MODULE_MAGIC_NUMBER_MINOR 20 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 21 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/modules/proxy/NWGNUmakefile b/modules/proxy/NWGNUmakefile index fe491c13d4..dce99d16f5 100644 --- a/modules/proxy/NWGNUmakefile +++ b/modules/proxy/NWGNUmakefile @@ -165,6 +165,7 @@ TARGET_nlm = \ $(OBJDIR)/proxylbm_hb.nlm \ $(OBJDIR)/proxylbm_req.nlm \ $(OBJDIR)/proxylbm_traf.nlm \ + $(OBJDIR)/proxywstunnel.nlm \ $(EOLIST) # diff --git a/modules/proxy/NWGNUproxywstunnel b/modules/proxy/NWGNUproxywstunnel new file mode 100644 index 0000000000..ce84ce4566 --- /dev/null +++ b/modules/proxy/NWGNUproxywstunnel @@ -0,0 +1,250 @@ +# +# Make sure all needed macro's are defined +# + +# +# Get the 'head' of the build environment if necessary. This includes default +# targets and paths to tools +# + +ifndef EnvironmentDefined +include $(AP_WORK)/build/NWGNUhead.inc +endif + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(APR)/include \ + $(APRUTIL)/include \ + $(SRC)/include \ + $(STDMOD)/http \ + $(STDMOD)/proxy \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = proxywstunnel + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Web Socket Tunnel Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = Prxy WbSkt Module + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)/build/NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 8192 + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If these are specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# If there is an NLM target, put it here +# +TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm + +# +# If there is an LIB target, put it here +# +TARGET_lib = + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_proxy_wstunnel.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(PRELUDE) \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + libc \ + aprlib \ + proxy \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @libc.imp \ + @aprlib.imp \ + @httpd.imp \ + @$(OBJDIR)/mod_proxy.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + proxy_wstunnel_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +vpath %.c balancers +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/proxy/config.m4 b/modules/proxy/config.m4 index e91cbf4c02..ce62591050 100644 --- a/modules/proxy/config.m4 +++ b/modules/proxy/config.m4 @@ -20,6 +20,7 @@ proxy_fcgi_objs="mod_proxy_fcgi.lo" proxy_scgi_objs="mod_proxy_scgi.lo" proxy_fdpass_objs="mod_proxy_fdpass.lo" proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo ajp_utils.lo" +proxy_wstunnel_objs="mod_proxy_wstunnel.lo" proxy_balancer_objs="mod_proxy_balancer.lo" case "$host" in @@ -33,6 +34,7 @@ case "$host" in proxy_scgi_objs="$proxy_scgi_objs mod_proxy.la" proxy_fdpass_objs="$proxy_fdpass_objs mod_proxy.la" proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la" + proxy_wstunnel_objs="$proxy_wstunnel_objs mod_proxy.la" proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la" ;; esac @@ -52,6 +54,7 @@ APACHE_MODULE(proxy_fdpass, Apache proxy to Unix Daemon Socket module. Requires enable_proxy_fdpass=no fi ],proxy) +APACHE_MODULE(proxy_wstunnel, Apache proxy Websocket Tunnel module. Requires and is enabled by --enable-proxy., $proxy_wstunnel_objs, , $proxy_mods_enable,, proxy) APACHE_MODULE(proxy_ajp, Apache proxy AJP module. Requires and is enabled by --enable-proxy., $proxy_ajp_objs, , $proxy_mods_enable,, proxy) APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module. Requires and is enabled by --enable-proxy., $proxy_balancer_objs, , $proxy_mods_enable,, proxy) diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 77f211987d..b8e95802b2 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -920,6 +920,46 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, proxy_dir_conf *dconf); +/** + * Create a HTTP request header brigade, old_cl_val and old_te_val as required. + * @parama p pool + * @param header_brigade header brigade to use/fill + * @param r request + * @param p_conn proxy connection rec + * @param worker selected worker + * @param conf per-server proxy config + * @param uri uri + * @param url url + * @param server_portstr port as string + * @param old_cl_val stored old content-len val + * @param old_te_val stored old TE val + * @return OK or HTTP_EXPECTATION_FAILED + */ +PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + apr_bucket_brigade *header_brigade, + request_rec *r, + proxy_conn_rec *p_conn, + proxy_worker *worker, + proxy_server_conf *conf, + apr_uri_t *uri, + char *url, char *server_portstr, + char **old_cl_val, + char **old_te_val); + +/** + * @param bucket_alloc bucket allocator + * @param r request + * @param p_conn proxy connection + * @param origin connection rec of origin + * @param bb brigade to send to origin + * @param flush flush + * @return status (OK) + */ +PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc, + request_rec *r, proxy_conn_rec *p_conn, + conn_rec *origin, apr_bucket_brigade *bb, + int flush); + #define PROXY_LBMETHOD "proxylbmethod" /* The number of dynamic workers that can be added when reconfiguring. diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index 47b2875839..0712b61d5f 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -250,44 +250,6 @@ static void terminate_headers(apr_bucket_alloc_t *bucket_alloc, APR_BRIGADE_INSERT_TAIL(header_brigade, e); } -static int pass_brigade(apr_bucket_alloc_t *bucket_alloc, - request_rec *r, proxy_conn_rec *p_conn, - conn_rec *origin, apr_bucket_brigade *bb, - int flush) -{ - apr_status_t status; - apr_off_t transferred; - - if (flush) { - apr_bucket *e = apr_bucket_flush_create(bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); - } - apr_brigade_length(bb, 0, &transferred); - if (transferred != -1) - p_conn->worker->s->transferred += transferred; - status = ap_pass_brigade(origin->output_filters, bb); - if (status != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01084) - "pass request body failed to %pI (%s)", - p_conn->addr, p_conn->hostname); - if (origin->aborted) { - const char *ssl_note; - - if (((ssl_note = apr_table_get(origin->notes, "SSL_connect_rv")) - != NULL) && (strcmp(ssl_note, "err") == 0)) { - return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, - "Error during SSL Handshake with" - " remote server"); - } - return APR_STATUS_IS_TIMEUP(status) ? HTTP_GATEWAY_TIME_OUT : HTTP_BAD_GATEWAY; - } - else { - return HTTP_BAD_REQUEST; - } - } - apr_brigade_cleanup(bb); - return OK; -} #define MAX_MEM_SPOOL 16384 @@ -366,7 +328,7 @@ static int stream_reqbody_chunked(apr_pool_t *p, } /* The request is flushed below this loop with chunk EOS header */ - rv = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0); + rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0); if (rv != OK) { return rv; } @@ -412,7 +374,7 @@ static int stream_reqbody_chunked(apr_pool_t *p, } /* Now we have headers-only, or the chunk EOS mark; flush it */ - rv = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1); + rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1); return rv; } @@ -422,7 +384,7 @@ static int stream_reqbody_cl(apr_pool_t *p, conn_rec *origin, apr_bucket_brigade *header_brigade, apr_bucket_brigade *input_brigade, - const char *old_cl_val) + char *old_cl_val) { int seen_eos = 0, rv = 0; apr_status_t status = APR_SUCCESS; @@ -511,7 +473,7 @@ static int stream_reqbody_cl(apr_pool_t *p, } /* Once we hit EOS, we are ready to flush. */ - rv = pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos); + rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos); if (rv != OK) { return rv ; } @@ -541,7 +503,7 @@ static int stream_reqbody_cl(apr_pool_t *p, * body; send it now with the flush flag */ bb = header_brigade; - return(pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1)); + return(ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1)); } return OK; @@ -685,7 +647,7 @@ static int spool_reqbody_cl(apr_pool_t *p, APR_BRIGADE_INSERT_TAIL(header_brigade, e); } /* This is all a single brigade, pass with flush flagged */ - return(pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1)); + return(ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1)); } /* @@ -752,257 +714,31 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r, apr_bucket_brigade *temp_brigade; apr_bucket *e; char *buf; - const apr_array_header_t *headers_in_array; - const apr_table_entry_t *headers_in; - int counter; apr_status_t status; enum rb_methods {RB_INIT, RB_STREAM_CL, RB_STREAM_CHUNKED, RB_SPOOL_CL}; enum rb_methods rb_method = RB_INIT; - const char *old_cl_val = NULL; - const char *old_te_val = NULL; + char *old_cl_val = NULL; + char *old_te_val = NULL; apr_off_t bytes_read = 0; apr_off_t bytes; int force10, rv; - apr_table_t *headers_in_copy; - proxy_dir_conf *dconf; conn_rec *origin = p_conn->connection; - int do_100_continue; - - dconf = ap_get_module_config(r->per_dir_config, &proxy_module); - header_brigade = apr_brigade_create(p, origin->bucket_alloc); - - /* - * Send the HTTP/1.1 request to the remote server - */ - - /* - * To be compliant, we only use 100-Continue for requests with bodies. - * We also make sure we won't be talking HTTP/1.0 as well. - */ - do_100_continue = (worker->s->ping_timeout_set - && ap_request_has_body(r) - && (PROXYREQ_REVERSE == r->proxyreq) - && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0"))); if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) { - /* - * According to RFC 2616 8.2.3 we are not allowed to forward an - * Expect: 100-continue to an HTTP/1.0 server. Instead we MUST return - * a HTTP_EXPECTATION_FAILED - */ if (r->expecting_100) { return HTTP_EXPECTATION_FAILED; } - buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL); force10 = 1; - p_conn->close = 1; } else { - buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL); force10 = 0; } - if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) { - origin->keepalive = AP_CONN_CLOSE; - p_conn->close = 1; - } - ap_xlate_proto_to_ascii(buf, strlen(buf)); - e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(header_brigade, e); - if (dconf->preserve_host == 0) { - if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */ - if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { - buf = apr_pstrcat(p, "Host: [", uri->hostname, "]:", - uri->port_str, CRLF, NULL); - } else { - buf = apr_pstrcat(p, "Host: [", uri->hostname, "]", CRLF, NULL); - } - } else { - if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { - buf = apr_pstrcat(p, "Host: ", uri->hostname, ":", - uri->port_str, CRLF, NULL); - } else { - buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL); - } - } - } - else { - /* don't want to use r->hostname, as the incoming header might have a - * port attached - */ - const char* hostname = apr_table_get(r->headers_in,"Host"); - if (!hostname) { - hostname = r->server->server_hostname; - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092) - "no HTTP 0.9 request (with no host line) " - "on incoming request and preserve host set " - "forcing hostname to be %s for uri %s", - hostname, r->uri); - } - buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL); - } - ap_xlate_proto_to_ascii(buf, strlen(buf)); - e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(header_brigade, e); - - /* handle Via */ - if (conf->viaopt == via_block) { - /* Block all outgoing Via: headers */ - apr_table_unset(r->headers_in, "Via"); - } else if (conf->viaopt != via_off) { - const char *server_name = ap_get_server_name(r); - /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host, - * then the server name returned by ap_get_server_name() is the - * origin server name (which does make too much sense with Via: headers) - * so we use the proxy vhost's name instead. - */ - if (server_name == r->hostname) - server_name = r->server->server_hostname; - /* Create a "Via:" request header entry and merge it */ - /* Generate outgoing Via: header with/without server comment: */ - apr_table_mergen(r->headers_in, "Via", - (conf->viaopt == via_full) - ? apr_psprintf(p, "%d.%d %s%s (%s)", - HTTP_VERSION_MAJOR(r->proto_num), - HTTP_VERSION_MINOR(r->proto_num), - server_name, server_portstr, - AP_SERVER_BASEVERSION) - : apr_psprintf(p, "%d.%d %s%s", - HTTP_VERSION_MAJOR(r->proto_num), - HTTP_VERSION_MINOR(r->proto_num), - server_name, server_portstr) - ); - } - - /* Use HTTP/1.1 100-Continue as quick "HTTP ping" test - * to backend - */ - if (do_100_continue) { - apr_table_mergen(r->headers_in, "Expect", "100-Continue"); - r->expecting_100 = 1; - } - - /* X-Forwarded-*: handling - * - * XXX Privacy Note: - * ----------------- - * - * These request headers are only really useful when the mod_proxy - * is used in a reverse proxy configuration, so that useful info - * about the client can be passed through the reverse proxy and on - * to the backend server, which may require the information to - * function properly. - * - * In a forward proxy situation, these options are a potential - * privacy violation, as information about clients behind the proxy - * are revealed to arbitrary servers out there on the internet. - * - * The HTTP/1.1 Via: header is designed for passing client - * information through proxies to a server, and should be used in - * a forward proxy configuation instead of X-Forwarded-*. See the - * ProxyVia option for details. - */ - if (dconf->add_forwarded_headers) { - if (PROXYREQ_REVERSE == r->proxyreq) { - const char *buf; - - /* Add X-Forwarded-For: so that the upstream has a chance to - * determine, where the original request came from. - */ - apr_table_mergen(r->headers_in, "X-Forwarded-For", - r->useragent_ip); - - /* Add X-Forwarded-Host: so that upstream knows what the - * original request hostname was. - */ - if ((buf = apr_table_get(r->headers_in, "Host"))) { - apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf); - } - - /* Add X-Forwarded-Server: so that upstream knows what the - * name of this proxy server is (if there are more than one) - * XXX: This duplicates Via: - do we strictly need it? - */ - apr_table_mergen(r->headers_in, "X-Forwarded-Server", - r->server->server_hostname); - } - } - proxy_run_fixups(r); - /* - * Make a copy of the headers_in table before clearing the connection - * headers as we need the connection headers later in the http output - * filter to prepare the correct response headers. - * - * Note: We need to take r->pool for apr_table_copy as the key / value - * pairs in r->headers_in have been created out of r->pool and - * p might be (and actually is) a longer living pool. - * This would trigger the bad pool ancestry abort in apr_table_copy if - * apr is compiled with APR_POOL_DEBUG. - */ - headers_in_copy = apr_table_copy(r->pool, r->headers_in); - ap_proxy_clear_connection(p, headers_in_copy); - /* send request headers */ - headers_in_array = apr_table_elts(headers_in_copy); - headers_in = (const apr_table_entry_t *) headers_in_array->elts; - for (counter = 0; counter < headers_in_array->nelts; counter++) { - if (headers_in[counter].key == NULL - || headers_in[counter].val == NULL - - /* Already sent */ - || !strcasecmp(headers_in[counter].key, "Host") - - /* Clear out hop-by-hop request headers not to send - * RFC2616 13.5.1 says we should strip these headers - */ - || !strcasecmp(headers_in[counter].key, "Keep-Alive") - || !strcasecmp(headers_in[counter].key, "TE") - || !strcasecmp(headers_in[counter].key, "Trailer") - || !strcasecmp(headers_in[counter].key, "Upgrade") - - ) { - continue; - } - /* Do we want to strip Proxy-Authorization ? - * If we haven't used it, then NO - * If we have used it then MAYBE: RFC2616 says we MAY propagate it. - * So let's make it configurable by env. - */ - if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) { - if (r->user != NULL) { /* we've authenticated */ - if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { - continue; - } - } - } - - - /* Skip Transfer-Encoding and Content-Length for now. - */ - if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) { - old_te_val = headers_in[counter].val; - continue; - } - if (!strcasecmp(headers_in[counter].key, "Content-Length")) { - old_cl_val = headers_in[counter].val; - continue; - } - - /* for sub-requests, ignore freshness/expiry headers */ - if (r->main) { - if ( !strcasecmp(headers_in[counter].key, "If-Match") - || !strcasecmp(headers_in[counter].key, "If-Modified-Since") - || !strcasecmp(headers_in[counter].key, "If-Range") - || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since") - || !strcasecmp(headers_in[counter].key, "If-None-Match")) { - continue; - } - } - - buf = apr_pstrcat(p, headers_in[counter].key, ": ", - headers_in[counter].val, CRLF, - NULL); - ap_xlate_proto_to_ascii(buf, strlen(buf)); - e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(header_brigade, e); + header_brigade = apr_brigade_create(p, origin->bucket_alloc); + rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, p_conn, + worker, conf, uri, url, server_portstr, + &old_cl_val, &old_te_val); + if (rv != OK) { + return rv; } /* We have headers, let's figure out our request body... */ diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c new file mode 100644 index 0000000000..365a20549e --- /dev/null +++ b/modules/proxy/mod_proxy_wstunnel.c @@ -0,0 +1,399 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mod_proxy.h" + +module AP_MODULE_DECLARE_DATA proxy_wstunnel_module; + +/* + * 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_wstunnel_canon(request_rec *r, char *url) +{ + char *host, *path, sport[7]; + char *search = NULL; + const char *err; + char *scheme; + apr_port_t port, def_port; + + /* ap_port_of_scheme() */ + if (strncasecmp(url, "ws:", 3) == 0) { + url += 3; + scheme = "ws:"; + def_port = apr_uri_port_of_scheme("http"); + } + else if (strncasecmp(url, "wss:", 4) == 0) { + url += 4; + scheme = "wss:"; + def_port = apr_uri_port_of_scheme("https"); + } + else { + return DECLINED; + } + + port = def_port; + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url); + + /* + * do syntactic check. + * We break the URL into host, port, path, search + */ + err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); + if (err) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02439) "error parsing URL %s: %s", + url, err); + return HTTP_BAD_REQUEST; + } + + /* + * now parse path/search args, according to rfc1738: + * process the path. With proxy-nocanon set (by + * mod_proxy) we use the raw, unparsed uri + */ + if (apr_table_get(r->notes, "proxy-nocanon")) { + path = url; /* this is the raw path */ + } + else { + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, + r->proxyreq); + search = r->args; + } + if (path == NULL) + return HTTP_BAD_REQUEST; + + apr_snprintf(sport, sizeof(sport), ":%d", port); + + 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); + return OK; +} + + +static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o, + apr_bucket_brigade *bb, char *name) +{ + int rv; +#ifdef DEBUGGING + apr_off_t len; +#endif + + do { + apr_brigade_cleanup(bb); + rv = ap_get_brigade(c_i->input_filters, bb, AP_MODE_READBYTES, + APR_NONBLOCK_READ, AP_IOBUFSIZE); + if (rv == APR_SUCCESS) { + if (c_o->aborted) + return APR_EPIPE; + if (APR_BRIGADE_EMPTY(bb)) + break; +#ifdef DEBUGGING + len = -1; + apr_brigade_length(bb, 0, &len); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02440) + "read %" APR_OFF_T_FMT + " bytes from %s", len, name); +#endif + rv = ap_pass_brigade(c_o->output_filters, bb); + if (rv == APR_SUCCESS) { + ap_fflush(c_o->output_filters, bb); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02441) + "error on %s - ap_pass_brigade", + name); + } + } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02442) + "error on %s - ap_get_brigade", + name); + } + } while (rv == APR_SUCCESS); + + if (APR_STATUS_IS_EAGAIN(rv)) { + rv = APR_SUCCESS; + } + return rv; +} + +/* Search thru the input filters and remove the reqtimeout one */ +static void remove_reqtimeout(ap_filter_t *next) +{ + ap_filter_t *reqto = NULL; + ap_filter_rec_t *filter; + + filter = ap_get_input_filter_handle("reqtimeout"); + if (!filter) { + return; + } + + while (next) { + if (next->frec == filter) { + reqto = next; + break; + } + next = next->next; + } + if (reqto) { + ap_remove_input_filter(reqto); + } +} + +/* + * process the request and write the response. + */ +static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, + proxy_conn_rec *conn, + proxy_worker *worker, + proxy_server_conf *conf, + apr_uri_t *uri, + char *url, char *server_portstr) +{ + apr_status_t rv = APR_SUCCESS; + apr_pollset_t *pollset; + apr_pollfd_t pollfd; + const apr_pollfd_t *signalled; + apr_int32_t pollcnt, pi; + apr_int16_t pollevent; + conn_rec *c = r->connection; + apr_socket_t *sock = conn->sock; + conn_rec *backconn = conn->connection; + int client_error = 0; + char *buf; + apr_bucket_brigade *header_brigade; + apr_bucket *e; + char *old_cl_val = NULL; + char *old_te_val = NULL; + apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc); + apr_socket_t *client_socket = ap_get_conn_socket(c); + + header_brigade = apr_brigade_create(p, backconn->bucket_alloc); + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending request"); + + rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, conn, + worker, conf, uri, url, server_portstr, + &old_cl_val, &old_te_val); + if (rv != OK) { + return rv; + } + + buf = apr_pstrcat(p, "Upgrade: WebSocket", CRLF, "Connection: Upgrade", CRLF, CRLF, NULL); + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + + if ((rv = ap_proxy_pass_brigade(c->bucket_alloc, r, conn, backconn, + header_brigade, 1)) != OK) + return rv; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()"); + + if ((rv = apr_pollset_create(&pollset, 2, p, 0)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02443) + "error apr_pollset_create()"); + return HTTP_INTERNAL_SERVER_ERROR; + } + +#if 0 + apr_socket_opt_set(sock, APR_SO_NONBLOCK, 1); + apr_socket_opt_set(sock, APR_SO_KEEPALIVE, 1); + apr_socket_opt_set(client_socket, APR_SO_NONBLOCK, 1); + apr_socket_opt_set(client_socket, APR_SO_KEEPALIVE, 1); +#endif + + pollfd.p = p; + pollfd.desc_type = APR_POLL_SOCKET; + pollfd.reqevents = APR_POLLIN; + pollfd.desc.s = sock; + pollfd.client_data = NULL; + apr_pollset_add(pollset, &pollfd); + + pollfd.desc.s = client_socket; + apr_pollset_add(pollset, &pollfd); + + + r->output_filters = c->output_filters; + r->proto_output_filters = c->output_filters; + r->input_filters = c->input_filters; + r->proto_input_filters = c->input_filters; + + remove_reqtimeout(r->input_filters); + + while (1) { /* Infinite loop until error (one side closes the connection) */ + if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled)) + != APR_SUCCESS) { + if (APR_STATUS_IS_EINTR(rv)) { + continue; + } + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()"); + return HTTP_INTERNAL_SERVER_ERROR; + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02445) + "woke from poll(), i=%d", pollcnt); + + for (pi = 0; pi < pollcnt; pi++) { + const apr_pollfd_t *cur = &signalled[pi]; + + if (cur->desc.s == sock) { + pollevent = cur->rtnevents; + if (pollevent & APR_POLLIN) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02446) + "sock was readable"); + rv = proxy_wstunnel_transfer(r, backconn, c, bb, "sock"); + } + else if ((pollevent & APR_POLLERR) + || (pollevent & APR_POLLHUP)) { + rv = APR_EPIPE; + ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447) + "err/hup on backconn"); + } + if (rv != APR_SUCCESS) + client_error = 1; + } + else if (cur->desc.s == client_socket) { + pollevent = cur->rtnevents; + if (pollevent & APR_POLLIN) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02448) + "client was readable"); + rv = proxy_wstunnel_transfer(r, c, backconn, bb, "client"); + } + } + else { + rv = APR_EBADF; + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02449) + "unknown socket in pollset"); + } + + } + if (rv != APR_SUCCESS) { + break; + } + } + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "finished with poll() - cleaning up"); + + if (client_error) { + return HTTP_INTERNAL_SERVER_ERROR; + } + return OK; +} + +/* + */ +static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker, + proxy_server_conf *conf, + char *url, const char *proxyname, + apr_port_t proxyport) +{ + int status; + char server_portstr[32]; + proxy_conn_rec *backend = NULL; + char *scheme; + int retry; + conn_rec *c = r->connection; + apr_pool_t *p = r->pool; + apr_uri_t *uri; + + if (strncasecmp(url, "wss:", 4) == 0) { + scheme = "WSS"; + } + else if (strncasecmp(url, "ws:", 3) == 0) { + scheme = "WS"; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) "declining URL %s", url); + return DECLINED; + } + + uri = apr_palloc(p, sizeof(*uri)); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02451) "serving URL %s", url); + + /* create space for state information */ + status = ap_proxy_acquire_connection(scheme, &backend, worker, + r->server); + if (status != OK) { + if (backend) { + backend->close = 1; + ap_proxy_release_connection(scheme, backend, r->server); + } + return status; + } + + backend->is_ssl = 0; + backend->close = 0; + + retry = 0; + while (retry < 2) { + char *locurl = url; + /* Step One: Determine Who To Connect To */ + status = ap_proxy_determine_connection(p, r, conf, worker, backend, + uri, &locurl, proxyname, proxyport, + server_portstr, + sizeof(server_portstr)); + + if (status != OK) + break; + + /* Step Two: Make the Connection */ + if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02452) + "failed to make connection to backend: %s", + backend->hostname); + status = HTTP_SERVICE_UNAVAILABLE; + break; + } + /* Step Three: Create conn_rec */ + if (!backend->connection) { + if ((status = ap_proxy_connection_create(scheme, backend, + c, r->server)) != OK) + break; + } + + /* Step Three: Process the Request */ + status = ap_proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl, + server_portstr); + break; + } + + /* Do not close the socket */ + ap_proxy_release_connection(scheme, backend, r->server); + return status; +} + +static void ap_proxy_http_register_hook(apr_pool_t *p) +{ + proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST); + proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST); +} + +AP_DECLARE_MODULE(proxy_wstunnel) = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + NULL, /* command apr_table_t */ + ap_proxy_http_register_hook /* register hooks */ +}; diff --git a/modules/proxy/mod_proxy_wstunnel.dsp b/modules/proxy/mod_proxy_wstunnel.dsp new file mode 100644 index 0000000000..7123bd6586 --- /dev/null +++ b/modules/proxy/mod_proxy_wstunnel.dsp @@ -0,0 +1,123 @@ +# Microsoft Developer Studio Project File - Name="mod_proxy_wstunnel" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_proxy_wstunnel - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak" CFG="mod_proxy_wstunnel - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_proxy_wstunnel - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_proxy_wstunnel - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_wstunnel_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so +# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_proxy_wstunnel.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_wstunnel_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so +# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_proxy_wstunnel.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_proxy_wstunnel - Win32 Release" +# Name "mod_proxy_wstunnel - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\mod_proxy_wstunnel.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter ".h" +# Begin Source File + +SOURCE=.\mod_proxy.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index 4f0a9a432e..961822fc2f 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -2838,3 +2838,329 @@ void proxy_util_register_hooks(apr_pool_t *p) { APR_REGISTER_OPTIONAL_FN(ap_proxy_retry_worker); } + +/* Clear all connection-based headers from the incoming headers table */ +typedef struct header_dptr { + apr_pool_t *pool; + apr_table_t *table; + apr_time_t time; +} header_dptr; + +static int clear_conn_headers(void *data, const char *key, const char *val) +{ + apr_table_t *headers = ((header_dptr*)data)->table; + apr_pool_t *pool = ((header_dptr*)data)->pool; + const char *name; + char *next = apr_pstrdup(pool, val); + while (*next) { + name = next; + while (*next && !apr_isspace(*next) && (*next != ',')) { + ++next; + } + while (*next && (apr_isspace(*next) || (*next == ','))) { + *next++ = '\0'; + } + apr_table_unset(headers, name); + } + return 1; +} + +static void proxy_clear_connection(apr_pool_t *p, apr_table_t *headers) +{ + header_dptr x; + x.pool = p; + x.table = headers; + apr_table_unset(headers, "Proxy-Connection"); + apr_table_do(clear_conn_headers, &x, headers, "Connection", NULL); + apr_table_unset(headers, "Connection"); +} + +PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + apr_bucket_brigade *header_brigade, + request_rec *r, + proxy_conn_rec *p_conn, + proxy_worker *worker, + proxy_server_conf *conf, + apr_uri_t *uri, + char *url, char *server_portstr, + char **old_cl_val, + char **old_te_val) +{ + conn_rec *c = r->connection; + int counter; + char *buf; + const apr_array_header_t *headers_in_array; + const apr_table_entry_t *headers_in; + apr_table_t *headers_in_copy; + apr_bucket *e; + int do_100_continue; + conn_rec *origin = p_conn->connection; + proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module); + + /* + * To be compliant, we only use 100-Continue for requests with bodies. + * We also make sure we won't be talking HTTP/1.0 as well. + */ + do_100_continue = (worker->s->ping_timeout_set + && ap_request_has_body(r) + && (PROXYREQ_REVERSE == r->proxyreq) + && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0"))); + + if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) { + /* + * According to RFC 2616 8.2.3 we are not allowed to forward an + * Expect: 100-continue to an HTTP/1.0 server. Instead we MUST return + * a HTTP_EXPECTATION_FAILED + */ + if (r->expecting_100) { + return HTTP_EXPECTATION_FAILED; + } + buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL); + p_conn->close = 1; + } else { + buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL); + } + if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) { + origin->keepalive = AP_CONN_CLOSE; + p_conn->close = 1; + } + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + if (dconf->preserve_host == 0) { + if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */ + if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { + buf = apr_pstrcat(p, "Host: [", uri->hostname, "]:", + uri->port_str, CRLF, NULL); + } else { + buf = apr_pstrcat(p, "Host: [", uri->hostname, "]", CRLF, NULL); + } + } else { + if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { + buf = apr_pstrcat(p, "Host: ", uri->hostname, ":", + uri->port_str, CRLF, NULL); + } else { + buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL); + } + } + } + else { + /* don't want to use r->hostname, as the incoming header might have a + * port attached + */ + const char* hostname = apr_table_get(r->headers_in,"Host"); + if (!hostname) { + hostname = r->server->server_hostname; + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092) + "no HTTP 0.9 request (with no host line) " + "on incoming request and preserve host set " + "forcing hostname to be %s for uri %s", + hostname, r->uri); + } + buf = apr_pstrcat(p, "Host: ", hostname, CRLF, NULL); + } + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + + /* handle Via */ + if (conf->viaopt == via_block) { + /* Block all outgoing Via: headers */ + apr_table_unset(r->headers_in, "Via"); + } else if (conf->viaopt != via_off) { + const char *server_name = ap_get_server_name(r); + /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host, + * then the server name returned by ap_get_server_name() is the + * origin server name (which does make too much sense with Via: headers) + * so we use the proxy vhost's name instead. + */ + if (server_name == r->hostname) + server_name = r->server->server_hostname; + /* Create a "Via:" request header entry and merge it */ + /* Generate outgoing Via: header with/without server comment: */ + apr_table_mergen(r->headers_in, "Via", + (conf->viaopt == via_full) + ? apr_psprintf(p, "%d.%d %s%s (%s)", + HTTP_VERSION_MAJOR(r->proto_num), + HTTP_VERSION_MINOR(r->proto_num), + server_name, server_portstr, + AP_SERVER_BASEVERSION) + : apr_psprintf(p, "%d.%d %s%s", + HTTP_VERSION_MAJOR(r->proto_num), + HTTP_VERSION_MINOR(r->proto_num), + server_name, server_portstr) + ); + } + + /* Use HTTP/1.1 100-Continue as quick "HTTP ping" test + * to backend + */ + if (do_100_continue) { + apr_table_mergen(r->headers_in, "Expect", "100-Continue"); + r->expecting_100 = 1; + } + + /* X-Forwarded-*: handling + * + * XXX Privacy Note: + * ----------------- + * + * These request headers are only really useful when the mod_proxy + * is used in a reverse proxy configuration, so that useful info + * about the client can be passed through the reverse proxy and on + * to the backend server, which may require the information to + * function properly. + * + * In a forward proxy situation, these options are a potential + * privacy violation, as information about clients behind the proxy + * are revealed to arbitrary servers out there on the internet. + * + * The HTTP/1.1 Via: header is designed for passing client + * information through proxies to a server, and should be used in + * a forward proxy configuation instead of X-Forwarded-*. See the + * ProxyVia option for details. + */ + if (dconf->add_forwarded_headers) { + if (PROXYREQ_REVERSE == r->proxyreq) { + const char *buf; + + /* Add X-Forwarded-For: so that the upstream has a chance to + * determine, where the original request came from. + */ + apr_table_mergen(r->headers_in, "X-Forwarded-For", + r->useragent_ip); + + /* Add X-Forwarded-Host: so that upstream knows what the + * original request hostname was. + */ + if ((buf = apr_table_get(r->headers_in, "Host"))) { + apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf); + } + + /* Add X-Forwarded-Server: so that upstream knows what the + * name of this proxy server is (if there are more than one) + * XXX: This duplicates Via: - do we strictly need it? + */ + apr_table_mergen(r->headers_in, "X-Forwarded-Server", + r->server->server_hostname); + } + } + + proxy_run_fixups(r); + /* + * Make a copy of the headers_in table before clearing the connection + * headers as we need the connection headers later in the http output + * filter to prepare the correct response headers. + * + * Note: We need to take r->pool for apr_table_copy as the key / value + * pairs in r->headers_in have been created out of r->pool and + * p might be (and actually is) a longer living pool. + * This would trigger the bad pool ancestry abort in apr_table_copy if + * apr is compiled with APR_POOL_DEBUG. + */ + headers_in_copy = apr_table_copy(r->pool, r->headers_in); + proxy_clear_connection(p, headers_in_copy); + /* send request headers */ + headers_in_array = apr_table_elts(headers_in_copy); + headers_in = (const apr_table_entry_t *) headers_in_array->elts; + for (counter = 0; counter < headers_in_array->nelts; counter++) { + if (headers_in[counter].key == NULL + || headers_in[counter].val == NULL + + /* Already sent */ + || !strcasecmp(headers_in[counter].key, "Host") + + /* Clear out hop-by-hop request headers not to send + * RFC2616 13.5.1 says we should strip these headers + */ + || !strcasecmp(headers_in[counter].key, "Keep-Alive") + || !strcasecmp(headers_in[counter].key, "TE") + || !strcasecmp(headers_in[counter].key, "Trailer") + || !strcasecmp(headers_in[counter].key, "Upgrade") + + ) { + continue; + } + /* Do we want to strip Proxy-Authorization ? + * If we haven't used it, then NO + * If we have used it then MAYBE: RFC2616 says we MAY propagate it. + * So let's make it configurable by env. + */ + if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) { + if (r->user != NULL) { /* we've authenticated */ + if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { + continue; + } + } + } + + /* Skip Transfer-Encoding and Content-Length for now. + */ + if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) { + *old_te_val = headers_in[counter].val; + continue; + } + if (!strcasecmp(headers_in[counter].key, "Content-Length")) { + *old_cl_val = headers_in[counter].val; + continue; + } + + /* for sub-requests, ignore freshness/expiry headers */ + if (r->main) { + if ( !strcasecmp(headers_in[counter].key, "If-Match") + || !strcasecmp(headers_in[counter].key, "If-Modified-Since") + || !strcasecmp(headers_in[counter].key, "If-Range") + || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since") + || !strcasecmp(headers_in[counter].key, "If-None-Match")) { + continue; + } + } + + buf = apr_pstrcat(p, headers_in[counter].key, ": ", + headers_in[counter].val, CRLF, + NULL); + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + } + return OK; +} + +PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc, + request_rec *r, proxy_conn_rec *p_conn, + conn_rec *origin, apr_bucket_brigade *bb, + int flush) +{ + apr_status_t status; + apr_off_t transferred; + + if (flush) { + apr_bucket *e = apr_bucket_flush_create(bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + apr_brigade_length(bb, 0, &transferred); + if (transferred != -1) + p_conn->worker->s->transferred += transferred; + status = ap_pass_brigade(origin->output_filters, bb); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01084) + "pass request body failed to %pI (%s)", + p_conn->addr, p_conn->hostname); + if (origin->aborted) { + const char *ssl_note; + + if (((ssl_note = apr_table_get(origin->notes, "SSL_connect_rv")) + != NULL) && (strcmp(ssl_note, "err") == 0)) { + return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "Error during SSL Handshake with" + " remote server"); + } + return APR_STATUS_IS_TIMEUP(status) ? HTTP_GATEWAY_TIME_OUT : HTTP_BAD_GATEWAY; + } + else { + return HTTP_BAD_REQUEST; + } + } + apr_brigade_cleanup(bb); + return OK; +} diff --git a/os/win32/BaseAddr.ref b/os/win32/BaseAddr.ref index 1bb7c64ba2..c426d44d4d 100644 --- a/os/win32/BaseAddr.ref +++ b/os/win32/BaseAddr.ref @@ -120,4 +120,4 @@ mod_data.so 0x6F710000 0x00010000 mod_allowmethods.so 0x6F700000 0x00010000 mod_macro.so 0x6F6F0000 0x00010000 mod_cache_socache.so 0x6F6E0000 0x00010000 - +mod_proxy_wstunnel.so 0x6F6D0000 0x00010000