1929 lines
77 KiB
Diff
1929 lines
77 KiB
Diff
From fa22b50457c81465b5079dc44c7f1f1cb7431f5d Mon Sep 17 00:00:00 2001
|
|
From: minfrin <minfrin@twitter.com>
|
|
Date: Mon, 18 Jan 2021 12:21:00 AM GMT+0800
|
|
Subject: [PATCH] mod_proxy_http: handle upgrade/tunneling protocols
|
|
|
|
Backport to v2.4:
|
|
*) mod_proxy_http: handle upgrade/tunneling protocols. BZ 61616 is about
|
|
mod_proxy_connect but there has been wstunnel reports
|
|
on dev@ about that too lately.
|
|
trunk patch: https://svn.apache.org/r1678771
|
|
https://svn.apache.org/r1832348
|
|
https://svn.apache.org/r1869338
|
|
https://svn.apache.org/r1869420
|
|
https://svn.apache.org/r1878367
|
|
https://svn.apache.org/r1877557
|
|
https://svn.apache.org/r1877558
|
|
https://svn.apache.org/r1877646
|
|
https://svn.apache.org/r1877695
|
|
https://svn.apache.org/r1879401
|
|
https://svn.apache.org/r1879402
|
|
https://svn.apache.org/r1880200
|
|
https://svn.apache.org/r1885239
|
|
https://svn.apache.org/r1885240
|
|
https://svn.apache.org/r1885244
|
|
2.4.x patch: http://people.apache.org/~ylavic/patches/2.4.x-mod_proxy_http-upgrade-4on5-v2.patch
|
|
#158
|
|
+1: ylavic, covener, minfrin
|
|
ylavic: All the corresponding trunk changes to mod_proxy_wstunnel (but
|
|
r1885239) have been dropped for this backport proposal, the goal
|
|
being to handle upgrade in mod_proxy_http from now, while r1885239
|
|
allows to benefit from the Upgrade improvements done in proxy_http
|
|
with existing wstunnel configurations (provided mod_proxy_http
|
|
module is loaded).
|
|
|
|
|
|
|
|
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1885605 13f79535-47bb-0310-9956-ffa450edef68
|
|
|
|
Reference:https://github.com/apache/httpd/commit/fa22b50457c81465b5079dc44c7f1f1cb7431f5d
|
|
|
|
---
|
|
include/ap_mmn.h | 5 +-
|
|
modules/proxy/mod_proxy.c | 3 +-
|
|
modules/proxy/mod_proxy.h | 64 +++-
|
|
modules/proxy/mod_proxy_connect.c | 158 ++------
|
|
modules/proxy/mod_proxy_http.c | 461 ++++++++++++++++--------
|
|
modules/proxy/mod_proxy_wstunnel.c | 57 ++-
|
|
modules/proxy/proxy_util.c | 556 ++++++++++++++++++++++++++---
|
|
7 files changed, 967 insertions(+), 337 deletions(-)
|
|
|
|
diff --git a/include/ap_mmn.h b/include/ap_mmn.h
|
|
index 0af9c98..549c869 100644
|
|
--- a/include/ap_mmn.h
|
|
+++ b/include/ap_mmn.h
|
|
@@ -534,6 +534,9 @@
|
|
* 20120211.90 (2.4.42-dev) AP_REG_DEFAULT macro in ap_regex.h
|
|
* 20120211.91 (2.4.42-dev) Add ap_is_chunked() in httpd.h
|
|
* 20120211.92 (2.4.42-dev) AP_REG_NO_DEFAULT macro in ap_regex.h
|
|
+ * 20120211.93 (2.4.47-dev) Add proxy_tunnel_rec, ap_proxy_tunnel_create()
|
|
+ * and ap_proxy_tunnel_run() to proxy_util.
|
|
+ * 20120211.93 (2.4.47-dev) Add ap_proxy_worker_can_upgrade()
|
|
*/
|
|
|
|
#define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */
|
|
@@ -541,7 +544,7 @@
|
|
#ifndef MODULE_MAGIC_NUMBER_MAJOR
|
|
#define MODULE_MAGIC_NUMBER_MAJOR 20120211
|
|
#endif
|
|
-#define MODULE_MAGIC_NUMBER_MINOR 92 /* 0...n */
|
|
+#define MODULE_MAGIC_NUMBER_MINOR 93 /* 0...n */
|
|
|
|
/**
|
|
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
|
|
diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c
|
|
index fd91c1c..8a891ee 100644
|
|
--- a/modules/proxy/mod_proxy.c
|
|
+++ b/modules/proxy/mod_proxy.c
|
|
@@ -314,7 +314,8 @@ static const char *set_worker_param(apr_pool_t *p,
|
|
PROXY_STRNCPY(worker->s->flusher, val);
|
|
}
|
|
else if (!strcasecmp(key, "upgrade")) {
|
|
- if (PROXY_STRNCPY(worker->s->upgrade, val) != APR_SUCCESS) {
|
|
+ if (PROXY_STRNCPY(worker->s->upgrade,
|
|
+ strcasecmp(val, "ANY") ? val : "*") != APR_SUCCESS) {
|
|
return apr_psprintf(p, "upgrade protocol length must be < %d characters",
|
|
(int)sizeof(worker->s->upgrade));
|
|
}
|
|
diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h
|
|
index 884d2ab..e522a96 100644
|
|
--- a/modules/proxy/mod_proxy.h
|
|
+++ b/modules/proxy/mod_proxy.h
|
|
@@ -725,6 +725,19 @@ PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
|
|
proxy_worker *worker);
|
|
#define AP_PROXY_WORKER_NO_UDS (1u << 3)
|
|
|
|
+/**
|
|
+ * Return whether a worker upgrade configuration matches Upgrade header
|
|
+ * @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 *dflt);
|
|
+
|
|
/**
|
|
* Get the worker from proxy configuration
|
|
* @param p memory pool used for finding worker
|
|
@@ -1182,6 +1195,40 @@ PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
|
|
conn_rec *origin, apr_bucket_brigade *bb,
|
|
int flush);
|
|
|
|
+struct proxy_tunnel_conn; /* opaque */
|
|
+typedef struct {
|
|
+ request_rec *r;
|
|
+ const char *scheme;
|
|
+ apr_pollset_t *pollset;
|
|
+ apr_array_header_t *pfds;
|
|
+ apr_interval_time_t timeout;
|
|
+ struct proxy_tunnel_conn *client,
|
|
+ *origin;
|
|
+ apr_size_t read_buf_size;
|
|
+ int replied;
|
|
+} proxy_tunnel_rec;
|
|
+
|
|
+/**
|
|
+ * Create a tunnel, to be activated by ap_proxy_tunnel_run().
|
|
+ * @param tunnel tunnel created
|
|
+ * @param r client request
|
|
+ * @param c_o connection to origin
|
|
+ * @param scheme caller proxy scheme (connect, ws(s), http(s), ...)
|
|
+ * @return APR_SUCCESS or error status
|
|
+ */
|
|
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **tunnel,
|
|
+ request_rec *r, conn_rec *c_o,
|
|
+ const char *scheme);
|
|
+
|
|
+/**
|
|
+ * Forward anything from either side of the tunnel to the other,
|
|
+ * until one end aborts or a polling timeout/error occurs.
|
|
+ * @param tunnel tunnel to run
|
|
+ * @return OK if completion is full, HTTP_GATEWAY_TIME_OUT on timeout
|
|
+ * or another HTTP_ error otherwise.
|
|
+ */
|
|
+PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel);
|
|
+
|
|
/**
|
|
* Clear the headers referenced by the Connection header from the given
|
|
* table, and remove the Connection header.
|
|
@@ -1256,6 +1303,15 @@ PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
|
|
apr_bucket_brigade *from,
|
|
apr_bucket_brigade *to);
|
|
|
|
+/*
|
|
+ * The flags for ap_proxy_transfer_between_connections(), where for legacy and
|
|
+ * compatibility reasons FLUSH_EACH and FLUSH_AFTER are boolean values.
|
|
+ */
|
|
+#define AP_PROXY_TRANSFER_FLUSH_EACH (0x00)
|
|
+#define AP_PROXY_TRANSFER_FLUSH_AFTER (0x01)
|
|
+#define AP_PROXY_TRANSFER_YIELD_PENDING (0x02)
|
|
+#define AP_PROXY_TRANSFER_YIELD_MAX_READS (0x04)
|
|
+
|
|
/*
|
|
* Sends all data that can be read non blocking from the input filter chain of
|
|
* c_i and send it down the output filter chain of c_o. For reading it uses
|
|
@@ -1273,10 +1329,12 @@ PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
|
|
* @param name string for logging from where data was pulled
|
|
* @param sent if not NULL will be set to 1 if data was sent through c_o
|
|
* @param bsize maximum amount of data pulled in one iteration from c_i
|
|
- * @param after if set flush data on c_o only once after the loop
|
|
+ * @param flags AP_PROXY_TRANSFER_* bitmask
|
|
* @return apr_status_t of the operation. Could be any error returned from
|
|
* either the input filter chain of c_i or the output filter chain
|
|
- * of c_o. APR_EPIPE if the outgoing connection was aborted.
|
|
+ * of c_o. APR_EPIPE if the outgoing connection was aborted, or
|
|
+ * APR_INCOMPLETE if AP_PROXY_TRANSFER_YIELD_PENDING was set and
|
|
+ * the output stack gets full before the input stack is exhausted.
|
|
*/
|
|
PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
|
|
request_rec *r,
|
|
@@ -1287,7 +1345,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
|
|
const char *name,
|
|
int *sent,
|
|
apr_off_t bsize,
|
|
- int after);
|
|
+ int flags);
|
|
|
|
extern module PROXY_DECLARE_DATA proxy_module;
|
|
|
|
diff --git a/modules/proxy/mod_proxy_connect.c b/modules/proxy/mod_proxy_connect.c
|
|
index 84536ae..4822f56 100644
|
|
--- a/modules/proxy/mod_proxy_connect.c
|
|
+++ b/modules/proxy/mod_proxy_connect.c
|
|
@@ -156,25 +156,19 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
apr_socket_t *sock;
|
|
conn_rec *c = r->connection;
|
|
conn_rec *backconn;
|
|
- int done = 0;
|
|
|
|
- apr_bucket_brigade *bb_front;
|
|
- apr_bucket_brigade *bb_back;
|
|
apr_status_t rv;
|
|
apr_size_t nbytes;
|
|
char buffer[HUGE_STRING_LEN];
|
|
- apr_socket_t *client_socket = ap_get_conn_socket(c);
|
|
+
|
|
+ apr_bucket_brigade *bb;
|
|
+ proxy_tunnel_rec *tunnel;
|
|
int failed, rc;
|
|
- apr_pollset_t *pollset;
|
|
- apr_pollfd_t pollfd;
|
|
- const apr_pollfd_t *signalled;
|
|
- apr_int32_t pollcnt, pi;
|
|
- apr_int16_t pollevent;
|
|
- apr_sockaddr_t *nexthop;
|
|
|
|
apr_uri_t uri;
|
|
const char *connectname;
|
|
apr_port_t connectport = 0;
|
|
+ apr_sockaddr_t *nexthop;
|
|
|
|
/* is this for us? */
|
|
if (r->method_number != M_CONNECT) {
|
|
@@ -261,28 +255,6 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
}
|
|
}
|
|
|
|
- /* setup polling for connection */
|
|
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
|
|
-
|
|
- if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS) {
|
|
- apr_socket_close(sock);
|
|
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01020)
|
|
- "error apr_pollset_create()");
|
|
- return HTTP_INTERNAL_SERVER_ERROR;
|
|
- }
|
|
-
|
|
- /* Add client side to the poll */
|
|
- pollfd.p = r->pool;
|
|
- pollfd.desc_type = APR_POLL_SOCKET;
|
|
- pollfd.reqevents = APR_POLLIN | APR_POLLHUP;
|
|
- pollfd.desc.s = client_socket;
|
|
- pollfd.client_data = NULL;
|
|
- apr_pollset_add(pollset, &pollfd);
|
|
-
|
|
- /* Add the server side to the poll */
|
|
- pollfd.desc.s = sock;
|
|
- apr_pollset_add(pollset, &pollfd);
|
|
-
|
|
/*
|
|
* Step Three: Send the Request
|
|
*
|
|
@@ -305,6 +277,7 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
backconn->aborted = 1;
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01022)
|
|
"pre_connection setup failed (%d)", rc);
|
|
+ apr_socket_close(sock);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
@@ -314,9 +287,7 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
|
|
backconn->local_addr->port));
|
|
|
|
-
|
|
- bb_front = apr_brigade_create(p, c->bucket_alloc);
|
|
- bb_back = apr_brigade_create(p, backconn->bucket_alloc);
|
|
+ bb = apr_brigade_create(p, c->bucket_alloc);
|
|
|
|
/* If we are connecting through a remote proxy, we need to pass
|
|
* the CONNECT request on to it.
|
|
@@ -326,24 +297,24 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
*/
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
|
"sending the CONNECT request to the remote proxy");
|
|
- ap_fprintf(backconn->output_filters, bb_back,
|
|
+ ap_fprintf(backconn->output_filters, bb,
|
|
"CONNECT %s HTTP/1.0" CRLF, r->uri);
|
|
- ap_fprintf(backconn->output_filters, bb_back,
|
|
+ ap_fprintf(backconn->output_filters, bb,
|
|
"Proxy-agent: %s" CRLF CRLF, ap_get_server_banner());
|
|
- ap_fflush(backconn->output_filters, bb_back);
|
|
+ ap_fflush(backconn->output_filters, bb);
|
|
}
|
|
else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Returning 200 OK");
|
|
nbytes = apr_snprintf(buffer, sizeof(buffer),
|
|
"HTTP/1.0 200 Connection Established" CRLF);
|
|
ap_xlate_proto_to_ascii(buffer, nbytes);
|
|
- ap_fwrite(c->output_filters, bb_front, buffer, nbytes);
|
|
+ ap_fwrite(c->output_filters, bb, buffer, nbytes);
|
|
nbytes = apr_snprintf(buffer, sizeof(buffer),
|
|
"Proxy-agent: %s" CRLF CRLF,
|
|
ap_get_server_banner());
|
|
ap_xlate_proto_to_ascii(buffer, nbytes);
|
|
- ap_fwrite(c->output_filters, bb_front, buffer, nbytes);
|
|
- ap_fflush(c->output_filters, bb_front);
|
|
+ ap_fwrite(c->output_filters, bb, buffer, nbytes);
|
|
+ ap_fflush(c->output_filters, bb);
|
|
#if 0
|
|
/* This is safer code, but it doesn't work yet. I'm leaving it
|
|
* here so that I can fix it later.
|
|
@@ -354,8 +325,7 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
ap_rflush(r);
|
|
#endif
|
|
}
|
|
-
|
|
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
|
|
+ apr_brigade_cleanup(bb);
|
|
|
|
/*
|
|
* Step Four: Handle Data Transfer
|
|
@@ -363,88 +333,30 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
* Handle two way transfer of data over the socket (this is a tunnel).
|
|
*/
|
|
|
|
- /* we are now acting as a tunnel - the input/output filter stacks should
|
|
- * not contain any non-connection filters.
|
|
- */
|
|
- 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;
|
|
-/* r->sent_bodyct = 1;*/
|
|
-
|
|
- do { /* Loop until done (one side closes the connection, or an error) */
|
|
- rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled);
|
|
- if (rv != APR_SUCCESS) {
|
|
- if (APR_STATUS_IS_EINTR(rv)) {
|
|
- continue;
|
|
- }
|
|
- apr_socket_close(sock);
|
|
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01023) "error apr_poll()");
|
|
- return HTTP_INTERNAL_SERVER_ERROR;
|
|
- }
|
|
-#ifdef DEBUGGING
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01024)
|
|
- "woke from poll(), i=%d", pollcnt);
|
|
-#endif
|
|
-
|
|
- for (pi = 0; pi < pollcnt; pi++) {
|
|
- const apr_pollfd_t *cur = &signalled[pi];
|
|
+ /* r->sent_bodyct = 1; */
|
|
|
|
- if (cur->desc.s == sock) {
|
|
- pollevent = cur->rtnevents;
|
|
- if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
|
|
-#ifdef DEBUGGING
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01025)
|
|
- "sock was readable");
|
|
-#endif
|
|
- done |= ap_proxy_transfer_between_connections(r, backconn,
|
|
- c, bb_back,
|
|
- bb_front,
|
|
- "sock", NULL,
|
|
- CONN_BLKSZ, 1)
|
|
- != APR_SUCCESS;
|
|
- }
|
|
- else if (pollevent & APR_POLLERR) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(01026)
|
|
- "err on backconn");
|
|
- backconn->aborted = 1;
|
|
- done = 1;
|
|
- }
|
|
- }
|
|
- else if (cur->desc.s == client_socket) {
|
|
- pollevent = cur->rtnevents;
|
|
- if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
|
|
-#ifdef DEBUGGING
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01027)
|
|
- "client was readable");
|
|
-#endif
|
|
- done |= ap_proxy_transfer_between_connections(r, c,
|
|
- backconn,
|
|
- bb_front,
|
|
- bb_back,
|
|
- "client",
|
|
- NULL,
|
|
- CONN_BLKSZ, 1)
|
|
- != APR_SUCCESS;
|
|
- }
|
|
- else if (pollevent & APR_POLLERR) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02827)
|
|
- "err on client");
|
|
- c->aborted = 1;
|
|
- done = 1;
|
|
- }
|
|
- }
|
|
- else {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01028)
|
|
- "unknown socket in pollset");
|
|
- done = 1;
|
|
- }
|
|
+ rv = ap_proxy_tunnel_create(&tunnel, r, backconn, "CONNECT");
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10208)
|
|
+ "can't create tunnel for %pI (%s)",
|
|
+ nexthop, connectname);
|
|
+ return HTTP_INTERNAL_SERVER_ERROR;
|
|
+ }
|
|
|
|
+ rc = ap_proxy_tunnel_run(tunnel);
|
|
+ if (ap_is_HTTP_ERROR(rc)) {
|
|
+ if (rc == HTTP_GATEWAY_TIME_OUT) {
|
|
+ /* ap_proxy_tunnel_run() didn't log this */
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10224)
|
|
+ "tunnel timed out");
|
|
}
|
|
- } while (!done);
|
|
-
|
|
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
|
- "finished with poll() - cleaning up");
|
|
+ /* Don't send an error page if we sent data already */
|
|
+ if (proxyport && !tunnel->replied) {
|
|
+ return rc;
|
|
+ }
|
|
+ /* Custom log may need this, still */
|
|
+ r->status = rc;
|
|
+ }
|
|
|
|
/*
|
|
* Step Five: Clean Up
|
|
@@ -457,8 +369,6 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
|
|
else
|
|
ap_lingering_close(backconn);
|
|
|
|
- c->keepalive = AP_CONN_CLOSE;
|
|
-
|
|
return OK;
|
|
}
|
|
|
|
diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c
|
|
index ae5e3d5..14f94b1 100644
|
|
--- a/modules/proxy/mod_proxy_http.c
|
|
+++ b/modules/proxy/mod_proxy_http.c
|
|
@@ -31,36 +31,71 @@ static apr_status_t ap_proxy_http_cleanup(const char *scheme,
|
|
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 = 1;
|
|
+ *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 (strncasecmp(url, "http:", 5) == 0) {
|
|
- url += 5;
|
|
- scheme = "http";
|
|
- }
|
|
- else if (strncasecmp(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
|
|
@@ -68,7 +103,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;
|
|
}
|
|
|
|
@@ -122,8 +157,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;
|
|
}
|
|
|
|
@@ -237,17 +273,6 @@ static void add_cl(apr_pool_t *p,
|
|
#define ZERO_ASCII "\060"
|
|
#endif
|
|
|
|
-static void terminate_headers(apr_bucket_alloc_t *bucket_alloc,
|
|
- apr_bucket_brigade *header_brigade)
|
|
-{
|
|
- apr_bucket *e;
|
|
-
|
|
- /* add empty line at the end of the headers */
|
|
- e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
|
|
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
|
|
-}
|
|
-
|
|
-
|
|
#define MAX_MEM_SPOOL 16384
|
|
|
|
typedef enum {
|
|
@@ -260,6 +285,7 @@ typedef enum {
|
|
typedef struct {
|
|
apr_pool_t *p;
|
|
request_rec *r;
|
|
+ const char *proto;
|
|
proxy_worker *worker;
|
|
proxy_server_conf *sconf;
|
|
|
|
@@ -275,9 +301,12 @@ typedef struct {
|
|
|
|
rb_methods rb_method;
|
|
|
|
+ const char *upgrade;
|
|
+
|
|
+ unsigned int do_100_continue :1,
|
|
+ prefetch_nonblocking :1,
|
|
+ force10 :1;
|
|
int expecting_100;
|
|
- unsigned int do_100_continue:1,
|
|
- prefetch_nonblocking:1;
|
|
} proxy_http_req_t;
|
|
|
|
/* Read what's in the client pipe. If nonblocking is set and read is EAGAIN,
|
|
@@ -392,20 +421,26 @@ static int stream_reqbody(proxy_http_req_t *req)
|
|
}
|
|
}
|
|
else if (rb_method == RB_STREAM_CL
|
|
- && bytes_streamed > req->cl_val) {
|
|
- /* C-L < bytes streamed?!?
|
|
- * We will error out after the body is completely
|
|
- * consumed, but we can't stream more bytes at the
|
|
- * back end since they would in part be interpreted
|
|
- * as another request! If nothing is sent, then
|
|
- * just send nothing.
|
|
+ && (bytes_streamed > req->cl_val
|
|
+ || (seen_eos && bytes_streamed < req->cl_val))) {
|
|
+ /* C-L != bytes streamed?!?
|
|
+ *
|
|
+ * Prevent HTTP Request/Response Splitting.
|
|
+ *
|
|
+ * We can't stream more (or less) bytes at the back end since
|
|
+ * they could be interpreted in separate requests (more bytes
|
|
+ * now would start a new request, less bytes would make the
|
|
+ * first bytes of the next request be part of the current one).
|
|
*
|
|
- * Prevents HTTP Response Splitting.
|
|
+ * It can't happen from the client connection here thanks to
|
|
+ * ap_http_filter(), but some module's filter may be playing
|
|
+ * bad games, hence the HTTP_INTERNAL_SERVER_ERROR.
|
|
*/
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01086)
|
|
- "read more bytes of request body than expected "
|
|
+ "read %s bytes of request body than expected "
|
|
"(got %" APR_OFF_T_FMT ", expected "
|
|
"%" APR_OFF_T_FMT ")",
|
|
+ bytes_streamed > req->cl_val ? "more" : "less",
|
|
bytes_streamed, req->cl_val);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
@@ -431,13 +466,6 @@ static int stream_reqbody(proxy_http_req_t *req)
|
|
}
|
|
} while (!seen_eos);
|
|
|
|
- if (rb_method == RB_STREAM_CL && bytes_streamed != req->cl_val) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01087)
|
|
- "client %s given Content-Length did not match"
|
|
- " number of body bytes read", r->connection->client_ip);
|
|
- return HTTP_BAD_REQUEST;
|
|
- }
|
|
-
|
|
return OK;
|
|
}
|
|
|
|
@@ -565,6 +593,43 @@ static int spool_reqbody_cl(proxy_http_req_t *req, apr_off_t *bytes_spooled)
|
|
return OK;
|
|
}
|
|
|
|
+static void terminate_headers(proxy_http_req_t *req)
|
|
+{
|
|
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
|
|
+ apr_bucket *e;
|
|
+ char *buf;
|
|
+
|
|
+ /*
|
|
+ * Handle Connection: header if we do HTTP/1.1 request:
|
|
+ * If we plan to close the backend connection sent Connection: close
|
|
+ * otherwise sent Connection: Keep-Alive.
|
|
+ */
|
|
+ if (!req->force10) {
|
|
+ if (req->upgrade) {
|
|
+ buf = apr_pstrdup(req->p, "Connection: Upgrade" CRLF);
|
|
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
|
|
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
|
|
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
|
|
+
|
|
+ /* Tell the backend that it can upgrade the connection. */
|
|
+ buf = apr_pstrcat(req->p, "Upgrade: ", req->upgrade, CRLF, NULL);
|
|
+ }
|
|
+ else if (ap_proxy_connection_reusable(req->backend)) {
|
|
+ buf = apr_pstrdup(req->p, "Connection: Keep-Alive" CRLF);
|
|
+ }
|
|
+ else {
|
|
+ buf = apr_pstrdup(req->p, "Connection: close" CRLF);
|
|
+ }
|
|
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
|
|
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
|
|
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
|
|
+ }
|
|
+
|
|
+ /* add empty line at the end of the headers */
|
|
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
|
|
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
|
|
+}
|
|
+
|
|
static int ap_proxy_http_prefetch(proxy_http_req_t *req,
|
|
apr_uri_t *uri, char *url)
|
|
{
|
|
@@ -577,20 +642,14 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
|
|
apr_bucket_brigade *input_brigade = req->input_brigade;
|
|
apr_bucket_brigade *temp_brigade;
|
|
apr_bucket *e;
|
|
- char *buf;
|
|
apr_status_t status;
|
|
apr_off_t bytes_read = 0;
|
|
apr_off_t bytes;
|
|
- int force10, rv;
|
|
+ int rv;
|
|
apr_read_type_e block;
|
|
|
|
- if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
|
|
- if (req->expecting_100) {
|
|
- return HTTP_EXPECTATION_FAILED;
|
|
- }
|
|
- force10 = 1;
|
|
- } else {
|
|
- force10 = 0;
|
|
+ if (req->force10 && r->expecting_100) {
|
|
+ return HTTP_EXPECTATION_FAILED;
|
|
}
|
|
|
|
rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, p_conn,
|
|
@@ -761,7 +820,7 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
|
|
req->rb_method = RB_STREAM_CL;
|
|
}
|
|
else if (req->old_te_val) {
|
|
- if (force10
|
|
+ if (req->force10
|
|
|| (apr_table_get(r->subprocess_env, "proxy-sendcl")
|
|
&& !apr_table_get(r->subprocess_env, "proxy-sendchunks")
|
|
&& !apr_table_get(r->subprocess_env, "proxy-sendchunked"))) {
|
|
@@ -783,7 +842,7 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
|
|
}
|
|
req->rb_method = RB_STREAM_CL;
|
|
}
|
|
- else if (!force10
|
|
+ else if (!req->force10
|
|
&& (apr_table_get(r->subprocess_env, "proxy-sendchunks")
|
|
|| apr_table_get(r->subprocess_env, "proxy-sendchunked"))
|
|
&& !apr_table_get(r->subprocess_env, "proxy-sendcl")) {
|
|
@@ -827,23 +886,7 @@ static int ap_proxy_http_prefetch(proxy_http_req_t *req,
|
|
|
|
/* Yes I hate gotos. This is the subrequest shortcut */
|
|
skip_body:
|
|
- /*
|
|
- * Handle Connection: header if we do HTTP/1.1 request:
|
|
- * If we plan to close the backend connection sent Connection: close
|
|
- * otherwise sent Connection: Keep-Alive.
|
|
- */
|
|
- if (!force10) {
|
|
- if (!ap_proxy_connection_reusable(p_conn)) {
|
|
- buf = apr_pstrdup(p, "Connection: close" CRLF);
|
|
- }
|
|
- else {
|
|
- buf = apr_pstrdup(p, "Connection: Keep-Alive" CRLF);
|
|
- }
|
|
- 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);
|
|
- }
|
|
- terminate_headers(bucket_alloc, header_brigade);
|
|
+ terminate_headers(req);
|
|
|
|
return OK;
|
|
}
|
|
@@ -1161,6 +1204,36 @@ static int add_trailers(void *data, const char *key, const char *val)
|
|
return 1;
|
|
}
|
|
|
|
+static int send_continue_body(proxy_http_req_t *req)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ /* Send the request body (fully). */
|
|
+ switch(req->rb_method) {
|
|
+ case RB_SPOOL_CL:
|
|
+ case RB_STREAM_CL:
|
|
+ case RB_STREAM_CHUNKED:
|
|
+ status = stream_reqbody(req);
|
|
+ break;
|
|
+ default:
|
|
+ /* Shouldn't happen */
|
|
+ status = HTTP_INTERNAL_SERVER_ERROR;
|
|
+ break;
|
|
+ }
|
|
+ if (status != OK) {
|
|
+ conn_rec *c = req->r->connection;
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req->r,
|
|
+ APLOGNO(10154) "pass request body failed "
|
|
+ "to %pI (%s) from %s (%s) with status %i",
|
|
+ req->backend->addr,
|
|
+ req->backend->hostname ? req->backend->hostname : "",
|
|
+ c->client_ip, c->remote_host ? c->remote_host : "",
|
|
+ status);
|
|
+ req->backend->close = 1;
|
|
+ }
|
|
+ return status;
|
|
+}
|
|
+
|
|
static
|
|
int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
{
|
|
@@ -1171,6 +1244,7 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
proxy_conn_rec *backend = req->backend;
|
|
conn_rec *origin = req->origin;
|
|
int do_100_continue = req->do_100_continue;
|
|
+ int status;
|
|
|
|
char *buffer;
|
|
char fixed_buffer[HUGE_STRING_LEN];
|
|
@@ -1242,6 +1316,7 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
origin->local_addr->port));
|
|
do {
|
|
apr_status_t rc;
|
|
+ const char *upgrade = NULL;
|
|
int major = 0, minor = 0;
|
|
int toclose = 0;
|
|
|
|
@@ -1262,7 +1337,8 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
apr_table_setn(r->notes, "proxy_timedout", "1");
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01103) "read timeout");
|
|
if (do_100_continue) {
|
|
- return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, "Timeout on 100-Continue");
|
|
+ return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE,
|
|
+ "Timeout on 100-Continue");
|
|
}
|
|
}
|
|
/*
|
|
@@ -1314,12 +1390,12 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
/* Need to return OK to avoid sending an error message */
|
|
return OK;
|
|
}
|
|
- else if (!c->keepalives) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105)
|
|
- "NOT Closing connection to client"
|
|
- " although reading from backend server %s:%d"
|
|
- " failed.",
|
|
- backend->hostname, backend->port);
|
|
+ if (!c->keepalives) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105)
|
|
+ "NOT Closing connection to client"
|
|
+ " although reading from backend server %s:%d"
|
|
+ " failed.",
|
|
+ backend->hostname, backend->port);
|
|
}
|
|
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
|
|
"Error reading from remote server");
|
|
@@ -1339,8 +1415,8 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
*/
|
|
if ((major != 1) || (len >= response_field_size - 1)) {
|
|
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
|
|
- apr_pstrcat(p, "Corrupt status line returned by remote "
|
|
- "server: ", buffer, NULL));
|
|
+ apr_pstrcat(p, "Corrupt status line returned "
|
|
+ "by remote server: ", buffer, NULL));
|
|
}
|
|
backasswards = 0;
|
|
|
|
@@ -1437,10 +1513,29 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
*/
|
|
te = apr_table_get(r->headers_out, "Transfer-Encoding");
|
|
|
|
+ upgrade = apr_table_get(r->headers_out, "Upgrade");
|
|
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
|
|
+ if (!upgrade || !req->upgrade || (strcasecmp(req->upgrade,
|
|
+ upgrade) != 0)) {
|
|
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
|
|
+ apr_pstrcat(p, "Unexpected Upgrade: ",
|
|
+ upgrade ? upgrade : "n/a",
|
|
+ " (expecting ",
|
|
+ req->upgrade ? req->upgrade
|
|
+ : "n/a", ")",
|
|
+ NULL));
|
|
+ }
|
|
+ backend->close = 1;
|
|
+ }
|
|
+
|
|
/* strip connection listed hop-by-hop headers from response */
|
|
toclose = ap_proxy_clear_connection_fn(r, r->headers_out);
|
|
if (toclose) {
|
|
backend->close = 1;
|
|
+ if (toclose < 0) {
|
|
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
|
|
+ "Malformed connection header");
|
|
+ }
|
|
}
|
|
|
|
if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
|
|
@@ -1500,6 +1595,8 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
}
|
|
|
|
if (ap_is_HTTP_INFO(proxy_status)) {
|
|
+ const char *policy = NULL;
|
|
+
|
|
/* RFC2616 tells us to forward this.
|
|
*
|
|
* OTOH, an interim response here may mean the backend
|
|
@@ -1514,15 +1611,29 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
*
|
|
* We need to set "r->expecting_100 = 1" otherwise origin
|
|
* server behaviour will apply.
|
|
+ *
|
|
+ * 101 Switching Protocol has its own configuration which
|
|
+ * shouldn't be interfered by "proxy-interim-response".
|
|
*/
|
|
- const char *policy = apr_table_get(r->subprocess_env,
|
|
- "proxy-interim-response");
|
|
+ if (proxy_status != HTTP_SWITCHING_PROTOCOLS) {
|
|
+ policy = apr_table_get(r->subprocess_env,
|
|
+ "proxy-interim-response");
|
|
+ }
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
|
- "HTTP: received interim %d response", r->status);
|
|
+ "HTTP: received interim %d response (policy: %s)",
|
|
+ r->status, policy ? policy : "n/a");
|
|
if (!policy
|
|
|| (!strcasecmp(policy, "RFC")
|
|
&& (proxy_status != HTTP_CONTINUE
|
|
|| (req->expecting_100 = 1)))) {
|
|
+ switch (proxy_status) {
|
|
+ case HTTP_SWITCHING_PROTOCOLS:
|
|
+ AP_DEBUG_ASSERT(upgrade != NULL);
|
|
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
|
|
+ apr_table_setn(r->headers_out, "Upgrade",
|
|
+ apr_pstrdup(p, upgrade));
|
|
+ break;
|
|
+ }
|
|
if (proxy_status == HTTP_CONTINUE) {
|
|
r->expecting_100 = req->expecting_100;
|
|
req->expecting_100 = 0;
|
|
@@ -1580,30 +1691,9 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
major, minor, proxy_status_line);
|
|
|
|
if (do_send_body) {
|
|
- int status;
|
|
-
|
|
- /* Send the request body (fully). */
|
|
- switch(req->rb_method) {
|
|
- case RB_SPOOL_CL:
|
|
- case RB_STREAM_CL:
|
|
- case RB_STREAM_CHUNKED:
|
|
- status = stream_reqbody(req);
|
|
- break;
|
|
- default:
|
|
- /* Shouldn't happen */
|
|
- status = HTTP_INTERNAL_SERVER_ERROR;
|
|
- break;
|
|
- }
|
|
+ status = send_continue_body(req);
|
|
+
|
|
if (status != OK) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
- APLOGNO(10154) "pass request body failed "
|
|
- "to %pI (%s) from %s (%s) with status %i",
|
|
- backend->addr,
|
|
- backend->hostname ? backend->hostname : "",
|
|
- c->client_ip,
|
|
- c->remote_host ? c->remote_host : "",
|
|
- status);
|
|
- backend->close = 1;
|
|
return status;
|
|
}
|
|
}
|
|
@@ -1626,6 +1716,62 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
do_100_continue = 0;
|
|
}
|
|
|
|
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
|
|
+ apr_status_t rv;
|
|
+ proxy_tunnel_rec *tunnel;
|
|
+ apr_interval_time_t client_timeout = -1,
|
|
+ backend_timeout = -1;
|
|
+
|
|
+ /* If we didn't send the full body yet, do it now */
|
|
+ if (do_100_continue) {
|
|
+ r->expecting_100 = 0;
|
|
+ status = send_continue_body(req);
|
|
+ if (status != OK) {
|
|
+ return status;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10239)
|
|
+ "HTTP: tunneling protocol %s", upgrade);
|
|
+
|
|
+ rv = ap_proxy_tunnel_create(&tunnel, r, origin, "HTTP");
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10240)
|
|
+ "can't create tunnel for %s", upgrade);
|
|
+ return HTTP_INTERNAL_SERVER_ERROR;
|
|
+ }
|
|
+
|
|
+ /* Set timeout to the lowest configured for client or backend */
|
|
+ apr_socket_timeout_get(backend->sock, &backend_timeout);
|
|
+ apr_socket_timeout_get(ap_get_conn_socket(c), &client_timeout);
|
|
+ if (backend_timeout >= 0 && backend_timeout < client_timeout) {
|
|
+ tunnel->timeout = backend_timeout;
|
|
+ }
|
|
+ else {
|
|
+ tunnel->timeout = client_timeout;
|
|
+ }
|
|
+
|
|
+ /* Let proxy tunnel forward everything */
|
|
+ status = ap_proxy_tunnel_run(tunnel);
|
|
+ if (ap_is_HTTP_ERROR(status)) {
|
|
+ /* Tunnel always return HTTP_GATEWAY_TIME_OUT on timeout,
|
|
+ * but we can differentiate between client and backend here.
|
|
+ */
|
|
+ if (status == HTTP_GATEWAY_TIME_OUT
|
|
+ && tunnel->timeout == client_timeout) {
|
|
+ status = HTTP_REQUEST_TIME_OUT;
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ /* Update r->status for custom log */
|
|
+ status = HTTP_SWITCHING_PROTOCOLS;
|
|
+ }
|
|
+ r->status = status;
|
|
+
|
|
+ /* We are done with both connections */
|
|
+ return DONE;
|
|
+ }
|
|
+
|
|
if (interim_response) {
|
|
/* Already forwarded above, read next response */
|
|
continue;
|
|
@@ -1680,7 +1826,7 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
*/
|
|
r->status = HTTP_OK;
|
|
/* Discard body, if one is expected */
|
|
- if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) {
|
|
+ if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) {
|
|
const char *tmp;
|
|
/* Add minimal headers needed to allow http_in filter
|
|
* detecting end of body without waiting for a timeout. */
|
|
@@ -1703,6 +1849,17 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
return proxy_status;
|
|
}
|
|
|
|
+ /* Forward back Upgrade header if it matches the configured one(s), it
|
|
+ * 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,
|
|
+ (*req->proto == 'w')
|
|
+ ? "WebSocket" : NULL)) {
|
|
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
|
|
+ apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, upgrade));
|
|
+ }
|
|
+
|
|
/* send body - but only if a body is expected */
|
|
if ((!r->header_only) && /* not HEAD request */
|
|
(proxy_status != HTTP_NO_CONTENT) && /* not 204 */
|
|
@@ -1883,6 +2040,7 @@ int ap_proxy_http_process_response(proxy_http_req_t *req)
|
|
*/
|
|
ap_proxy_release_connection(backend->worker->s->scheme,
|
|
backend, r->server);
|
|
+ /* Ensure that the backend is not reused */
|
|
req->backend = NULL;
|
|
|
|
/* Pass EOS bucket down the filter chain. */
|
|
@@ -1937,9 +2095,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;
|
|
@@ -1956,41 +2113,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;
|
|
}
|
|
@@ -2003,11 +2150,27 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
|
|
req->sconf = conf;
|
|
req->worker = worker;
|
|
req->backend = backend;
|
|
+ req->proto = scheme;
|
|
req->bucket_alloc = c->bucket_alloc;
|
|
req->rb_method = RB_INIT;
|
|
|
|
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
|
|
|
|
+ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
|
|
+ req->force10 = 1;
|
|
+ }
|
|
+ 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,
|
|
+ (*req->proto == 'w')
|
|
+ ? "WebSocket" : NULL)) {
|
|
+ req->upgrade = upgrade;
|
|
+ }
|
|
+ }
|
|
+
|
|
/* We possibly reuse input data prefetched in previous call(s), e.g. for a
|
|
* balancer fallback scenario, and in this case the 100 continue settings
|
|
* should be consistent between balancer members. If not, we need to ignore
|
|
@@ -2031,15 +2194,18 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker,
|
|
* req->expecting_100 (i.e. cleared only if mod_proxy_http sent the
|
|
* "100 Continue" according to its policy).
|
|
*/
|
|
- req->do_100_continue = req->prefetch_nonblocking = 1;
|
|
+ req->do_100_continue = 1;
|
|
req->expecting_100 = r->expecting_100;
|
|
r->expecting_100 = 0;
|
|
}
|
|
+
|
|
/* Should we block while prefetching the body or try nonblocking and flush
|
|
* data to the backend ASAP?
|
|
*/
|
|
- else if (input_brigade || apr_table_get(r->subprocess_env,
|
|
- "proxy-prefetch-nonblocking")) {
|
|
+ if (input_brigade
|
|
+ || req->do_100_continue
|
|
+ || apr_table_get(r->subprocess_env,
|
|
+ "proxy-prefetch-nonblocking")) {
|
|
req->prefetch_nonblocking = 1;
|
|
}
|
|
|
|
@@ -2115,9 +2281,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",
|
|
@@ -2127,8 +2293,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;
|
|
|
|
@@ -2166,7 +2331,7 @@ cleanup:
|
|
if (req->backend) {
|
|
if (status != OK)
|
|
req->backend->close = 1;
|
|
- ap_proxy_http_cleanup(proxy_function, r, req->backend);
|
|
+ ap_proxy_http_cleanup(scheme, r, req->backend);
|
|
}
|
|
if (req->expecting_100) {
|
|
/* Restore r->expecting_100 if we didn't touch it */
|
|
diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c
|
|
index 28d6363..777a0f8 100644
|
|
--- a/modules/proxy/mod_proxy_wstunnel.c
|
|
+++ b/modules/proxy/mod_proxy_wstunnel.c
|
|
@@ -15,9 +15,12 @@
|
|
*/
|
|
|
|
#include "mod_proxy.h"
|
|
+#include "http_config.h"
|
|
|
|
module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
|
|
|
|
+static int fallback_to_mod_proxy_http;
|
|
+
|
|
/*
|
|
* Canonicalise http-like URLs.
|
|
* scheme is the scheme for the URL
|
|
@@ -32,6 +35,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 (strncasecmp(url, "ws:", 3) == 0) {
|
|
url += 3;
|
|
@@ -300,12 +308,17 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
|
|
int status;
|
|
char server_portstr[32];
|
|
proxy_conn_rec *backend = NULL;
|
|
+ const char *upgrade;
|
|
char *scheme;
|
|
apr_pool_t *p = r->pool;
|
|
char *locurl = url;
|
|
apr_uri_t *uri;
|
|
int is_ssl = 0;
|
|
- const char *upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket";
|
|
+
|
|
+ if (fallback_to_mod_proxy_http) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "handler fallback");
|
|
+ return DECLINED;
|
|
+ }
|
|
|
|
if (strncasecmp(url, "wss:", 4) == 0) {
|
|
scheme = "WSS";
|
|
@@ -315,20 +328,24 @@ 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;
|
|
}
|
|
-
|
|
- if (ap_cstr_casecmp(upgrade_method, "NONE") != 0) {
|
|
- const char *upgrade;
|
|
- upgrade = apr_table_get(r->headers_in, "Upgrade");
|
|
- if (!upgrade || (ap_cstr_casecmp(upgrade, upgrade_method) != 0 &&
|
|
- ap_cstr_casecmp(upgrade_method, "ANY") !=0)) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900)
|
|
- "declining URL %s (not %s, Upgrade: header is %s)",
|
|
- url, upgrade_method, upgrade ? upgrade : "missing");
|
|
- 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 || !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)
|
|
+ "require upgrade for URL %s "
|
|
+ "(Upgrade header is %s, expecting %s)",
|
|
+ url, upgrade ? upgrade : "missing", worker_upgrade);
|
|
+ apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
|
|
+ apr_table_setn(r->err_headers_out, "Upgrade", worker_upgrade);
|
|
+ return HTTP_UPGRADE_REQUIRED;
|
|
}
|
|
|
|
uri = apr_palloc(p, sizeof(*uri));
|
|
@@ -380,8 +397,18 @@ cleanup:
|
|
return status;
|
|
}
|
|
|
|
-static void ap_proxy_http_register_hook(apr_pool_t *p)
|
|
+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 =
|
|
+ (ap_find_linked_module("mod_proxy_http.c") != NULL);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static void ws_proxy_hooks(apr_pool_t *p)
|
|
{
|
|
+ ap_hook_post_config(proxy_wstunnel_post_config, NULL, NULL, APR_HOOK_MIDDLE);
|
|
proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
|
|
proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST);
|
|
}
|
|
@@ -393,5 +420,5 @@ AP_DECLARE_MODULE(proxy_wstunnel) = {
|
|
NULL, /* create per-server config structure */
|
|
NULL, /* merge per-server config structures */
|
|
NULL, /* 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 803923c..7ae239e 100644
|
|
--- a/modules/proxy/proxy_util.c
|
|
+++ b/modules/proxy/proxy_util.c
|
|
@@ -1658,6 +1658,23 @@ PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
|
|
return apr_pstrcat(p, "unix:", worker->s->uds_path, "|", worker->s->name, NULL);
|
|
}
|
|
|
|
+PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
|
|
+ const proxy_worker *worker,
|
|
+ const char *upgrade,
|
|
+ const char *dflt)
|
|
+{
|
|
+ /* Find in worker->s->upgrade list (if any) */
|
|
+ const char *worker_upgrade = worker->s->upgrade;
|
|
+ if (*worker_upgrade) {
|
|
+ return (strcmp(worker_upgrade, "*") == 0
|
|
+ || ap_cstr_casecmp(worker_upgrade, upgrade) == 0
|
|
+ || ap_find_token(p, worker_upgrade, upgrade));
|
|
+ }
|
|
+
|
|
+ /* Compare to the provided default (if any) */
|
|
+ return (dflt && ap_cstr_casecmp(dflt, upgrade) == 0);
|
|
+}
|
|
+
|
|
PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
|
|
proxy_balancer *balancer,
|
|
proxy_server_conf *conf,
|
|
@@ -3994,6 +4011,28 @@ PROXY_DECLARE(apr_port_t) ap_proxy_port_of_scheme(const char *scheme)
|
|
return 0;
|
|
}
|
|
|
|
+static APR_INLINE int ap_filter_should_yield(ap_filter_t *f)
|
|
+{
|
|
+ return f->c->data_in_output_filters;
|
|
+}
|
|
+
|
|
+static APR_INLINE int ap_filter_output_pending(conn_rec *c)
|
|
+{
|
|
+ ap_filter_t *f = c->output_filters;
|
|
+ while (f->next) {
|
|
+ f = f->next;
|
|
+ }
|
|
+ if (f->frec->filter_func.out_func(f, NULL)) {
|
|
+ return AP_FILTER_ERROR;
|
|
+ }
|
|
+ return c->data_in_output_filters ? OK : DECLINED;
|
|
+}
|
|
+
|
|
+static APR_INLINE int ap_filter_input_pending(conn_rec *c)
|
|
+{
|
|
+ return c->data_in_input_filters ? OK : DECLINED;
|
|
+}
|
|
+
|
|
PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
|
|
apr_bucket_brigade *from,
|
|
apr_bucket_brigade *to)
|
|
@@ -4032,6 +4071,16 @@ PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
|
|
return rv;
|
|
}
|
|
|
|
+/* An arbitrary large value to address pathological case where we keep
|
|
+ * reading from one side only, without scheduling the other direction for
|
|
+ * too long. This can happen with large MTU and small read buffers, like
|
|
+ * micro-benchmarking huge files bidirectional transfer with client, proxy
|
|
+ * and backend on localhost for instance. Though we could just ignore the
|
|
+ * case and let the sender stop by itself at some point when/if it needs to
|
|
+ * receive data, or the receiver stop when/if it needs to send...
|
|
+ */
|
|
+#define PROXY_TRANSFER_MAX_READS 10000
|
|
+
|
|
PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
|
|
request_rec *r,
|
|
conn_rec *c_i,
|
|
@@ -4041,81 +4090,498 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
|
|
const char *name,
|
|
int *sent,
|
|
apr_off_t bsize,
|
|
- int after)
|
|
+ int flags)
|
|
{
|
|
apr_status_t rv;
|
|
+ int flush_each = 0;
|
|
+ unsigned int num_reads = 0;
|
|
#ifdef DEBUGGING
|
|
apr_off_t len;
|
|
#endif
|
|
|
|
- do {
|
|
+ /*
|
|
+ * Compat: since FLUSH_EACH is default (and zero) for legacy reasons, we
|
|
+ * pretend it's no FLUSH_AFTER nor YIELD_PENDING flags, the latter because
|
|
+ * flushing would defeat the purpose of checking for pending data (hence
|
|
+ * determine whether or not the output chain/stack is full for stopping).
|
|
+ */
|
|
+ if (!(flags & (AP_PROXY_TRANSFER_FLUSH_AFTER |
|
|
+ AP_PROXY_TRANSFER_YIELD_PENDING))) {
|
|
+ flush_each = 1;
|
|
+ }
|
|
+
|
|
+ for (;;) {
|
|
apr_brigade_cleanup(bb_i);
|
|
rv = ap_get_brigade(c_i->input_filters, bb_i, AP_MODE_READBYTES,
|
|
APR_NONBLOCK_READ, bsize);
|
|
- if (rv == APR_SUCCESS) {
|
|
- if (c_o->aborted) {
|
|
- return APR_EPIPE;
|
|
- }
|
|
- if (APR_BRIGADE_EMPTY(bb_i)) {
|
|
- break;
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308)
|
|
+ "ap_proxy_transfer_between_connections: "
|
|
+ "error on %s - ap_get_brigade",
|
|
+ name);
|
|
+ if (rv == APR_INCOMPLETE) {
|
|
+ /* Don't return APR_INCOMPLETE, it'd mean "should yield"
|
|
+ * for the caller, while it means "incomplete body" here
|
|
+ * from ap_http_filter(), which is an error.
|
|
+ */
|
|
+ rv = APR_EGENERAL;
|
|
+ }
|
|
}
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (c_o->aborted) {
|
|
+ apr_brigade_cleanup(bb_i);
|
|
+ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
|
|
+ rv = APR_EPIPE;
|
|
+ break;
|
|
+ }
|
|
+ if (APR_BRIGADE_EMPTY(bb_i)) {
|
|
+ break;
|
|
+ }
|
|
#ifdef DEBUGGING
|
|
- len = -1;
|
|
- apr_brigade_length(bb_i, 0, &len);
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306)
|
|
- "ap_proxy_transfer_between_connections: "
|
|
- "read %" APR_OFF_T_FMT
|
|
- " bytes from %s", len, name);
|
|
+ len = -1;
|
|
+ apr_brigade_length(bb_i, 0, &len);
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306)
|
|
+ "ap_proxy_transfer_between_connections: "
|
|
+ "read %" APR_OFF_T_FMT
|
|
+ " bytes from %s", len, name);
|
|
#endif
|
|
- if (sent) {
|
|
- *sent = 1;
|
|
- }
|
|
- ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o);
|
|
- if (!after) {
|
|
- apr_bucket *b;
|
|
+ if (sent) {
|
|
+ *sent = 1;
|
|
+ }
|
|
+ ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o);
|
|
+ if (flush_each) {
|
|
+ apr_bucket *b;
|
|
+ /*
|
|
+ * Do not use ap_fflush here since this would cause the flush
|
|
+ * bucket to be sent in a separate brigade afterwards which
|
|
+ * causes some filters to set aside the buckets from the first
|
|
+ * brigade and process them when FLUSH arrives in the second
|
|
+ * brigade. As set asides of our transformed buckets involve
|
|
+ * memory copying we try to avoid this. If we have the flush
|
|
+ * bucket in the first brigade they directly process the
|
|
+ * buckets without setting them aside.
|
|
+ */
|
|
+ b = apr_bucket_flush_create(bb_o->bucket_alloc);
|
|
+ APR_BRIGADE_INSERT_TAIL(bb_o, b);
|
|
+ }
|
|
+ rv = ap_pass_brigade(c_o->output_filters, bb_o);
|
|
+ apr_brigade_cleanup(bb_o);
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307)
|
|
+ "ap_proxy_transfer_between_connections: "
|
|
+ "error on %s - ap_pass_brigade",
|
|
+ name);
|
|
+ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
|
|
+ break;
|
|
+ }
|
|
|
|
- /*
|
|
- * Do not use ap_fflush here since this would cause the flush
|
|
- * bucket to be sent in a separate brigade afterwards which
|
|
- * causes some filters to set aside the buckets from the first
|
|
- * brigade and process them when the flush arrives in the second
|
|
- * brigade. As set asides of our transformed buckets involve
|
|
- * memory copying we try to avoid this. If we have the flush
|
|
- * bucket in the first brigade they directly process the
|
|
- * buckets without setting them aside.
|
|
- */
|
|
- b = apr_bucket_flush_create(bb_o->bucket_alloc);
|
|
- APR_BRIGADE_INSERT_TAIL(bb_o, b);
|
|
+ /* Yield if the output filters stack is full? This is to avoid
|
|
+ * blocking and give the caller a chance to POLLOUT async.
|
|
+ */
|
|
+ if (flags & AP_PROXY_TRANSFER_YIELD_PENDING) {
|
|
+ int rc = OK;
|
|
+
|
|
+ if (!ap_filter_should_yield(c_o->output_filters)) {
|
|
+ rc = ap_filter_output_pending(c_o);
|
|
}
|
|
- rv = ap_pass_brigade(c_o->output_filters, bb_o);
|
|
- if (rv != APR_SUCCESS) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307)
|
|
+ if (rc == OK) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
|
"ap_proxy_transfer_between_connections: "
|
|
- "error on %s - ap_pass_brigade",
|
|
- name);
|
|
+ "yield (output pending)");
|
|
+ rv = APR_INCOMPLETE;
|
|
+ break;
|
|
}
|
|
- } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
|
|
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308)
|
|
+ if (rc != DECLINED) {
|
|
+ rv = AP_FILTER_ERROR;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Yield if we keep hold of the thread for too long? This gives
|
|
+ * the caller a chance to schedule the other direction too.
|
|
+ */
|
|
+ if ((flags & AP_PROXY_TRANSFER_YIELD_MAX_READS)
|
|
+ && ++num_reads > PROXY_TRANSFER_MAX_READS) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
|
"ap_proxy_transfer_between_connections: "
|
|
- "error on %s - ap_get_brigade",
|
|
- name);
|
|
+ "yield (max reads)");
|
|
+ rv = APR_SUCCESS;
|
|
+ break;
|
|
}
|
|
- } while (rv == APR_SUCCESS);
|
|
+ }
|
|
|
|
- if (after) {
|
|
+ if (flags & AP_PROXY_TRANSFER_FLUSH_AFTER) {
|
|
ap_fflush(c_o->output_filters, bb_o);
|
|
+ apr_brigade_cleanup(bb_o);
|
|
}
|
|
+ apr_brigade_cleanup(bb_i);
|
|
|
|
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
|
|
- "ap_proxy_transfer_between_connections complete");
|
|
+ "ap_proxy_transfer_between_connections complete (%s %pI)",
|
|
+ (c_i == r->connection) ? "to" : "from",
|
|
+ (c_i == r->connection) ? c_o->client_addr
|
|
+ : c_i->client_addr);
|
|
|
|
if (APR_STATUS_IS_EAGAIN(rv)) {
|
|
rv = APR_SUCCESS;
|
|
}
|
|
-
|
|
return rv;
|
|
}
|
|
|
|
+struct proxy_tunnel_conn {
|
|
+ /* the other side of the tunnel */
|
|
+ struct proxy_tunnel_conn *other;
|
|
+
|
|
+ conn_rec *c;
|
|
+ const char *name;
|
|
+
|
|
+ apr_pollfd_t *pfd;
|
|
+ apr_bucket_brigade *bb;
|
|
+
|
|
+ unsigned int down_in:1,
|
|
+ down_out:1;
|
|
+};
|
|
+
|
|
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **ptunnel,
|
|
+ request_rec *r, conn_rec *c_o,
|
|
+ const char *scheme)
|
|
+{
|
|
+ apr_status_t rv;
|
|
+ conn_rec *c_i = r->connection;
|
|
+ proxy_tunnel_rec *tunnel;
|
|
+
|
|
+ *ptunnel = NULL;
|
|
+
|
|
+ tunnel = apr_pcalloc(r->pool, sizeof(*tunnel));
|
|
+
|
|
+ rv = apr_pollset_create(&tunnel->pollset, 2, r->pool, APR_POLLSET_NOCOPY);
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ return rv;
|
|
+ }
|
|
+
|
|
+ tunnel->r = r;
|
|
+ tunnel->scheme = apr_pstrdup(r->pool, scheme);
|
|
+ tunnel->client = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn));
|
|
+ tunnel->origin = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn));
|
|
+ tunnel->pfds = apr_array_make(r->pool, 2, sizeof(apr_pollfd_t));
|
|
+ tunnel->read_buf_size = ap_get_read_buf_size(r);
|
|
+ tunnel->client->other = tunnel->origin;
|
|
+ tunnel->origin->other = tunnel->client;
|
|
+ tunnel->timeout = -1;
|
|
+
|
|
+ tunnel->client->c = c_i;
|
|
+ tunnel->client->name = "client";
|
|
+ tunnel->client->bb = apr_brigade_create(c_i->pool, c_i->bucket_alloc);
|
|
+ tunnel->client->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
|
|
+ tunnel->client->pfd->p = r->pool;
|
|
+ tunnel->client->pfd->desc_type = APR_POLL_SOCKET;
|
|
+ tunnel->client->pfd->desc.s = ap_get_conn_socket(c_i);
|
|
+ tunnel->client->pfd->client_data = tunnel->client;
|
|
+
|
|
+ tunnel->origin->c = c_o;
|
|
+ tunnel->origin->name = "origin";
|
|
+ tunnel->origin->bb = apr_brigade_create(c_o->pool, c_o->bucket_alloc);
|
|
+ tunnel->origin->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
|
|
+ tunnel->origin->pfd->p = r->pool;
|
|
+ tunnel->origin->pfd->desc_type = APR_POLL_SOCKET;
|
|
+ tunnel->origin->pfd->desc.s = ap_get_conn_socket(c_o);
|
|
+ tunnel->origin->pfd->client_data = tunnel->origin;
|
|
+
|
|
+ /* We should be nonblocking from now on the sockets */
|
|
+ apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
|
|
+ apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
|
|
+
|
|
+ /* No coalescing filters */
|
|
+ ap_remove_output_filter_byhandle(c_i->output_filters,
|
|
+ "SSL/TLS Coalescing Filter");
|
|
+ ap_remove_output_filter_byhandle(c_o->output_filters,
|
|
+ "SSL/TLS Coalescing Filter");
|
|
+
|
|
+ /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */
|
|
+ ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout");
|
|
+
|
|
+ /* The input/output filter stacks should contain connection filters only */
|
|
+ r->input_filters = r->proto_input_filters = c_i->input_filters;
|
|
+ r->output_filters = r->proto_output_filters = c_i->output_filters;
|
|
+
|
|
+ /* Won't be reused after tunneling */
|
|
+ c_i->keepalive = AP_CONN_CLOSE;
|
|
+ c_o->keepalive = AP_CONN_CLOSE;
|
|
+
|
|
+ /* Start with POLLOUT and let ap_proxy_tunnel_run() schedule both
|
|
+ * directions when there are no output data pending (anymore).
|
|
+ */
|
|
+ tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
|
|
+ tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
|
|
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))
|
|
+ || (rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
|
|
+ return rv;
|
|
+ }
|
|
+
|
|
+ *ptunnel = tunnel;
|
|
+ return APR_SUCCESS;
|
|
+}
|
|
+
|
|
+static void add_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd,
|
|
+ apr_int16_t events)
|
|
+{
|
|
+ apr_status_t rv;
|
|
+
|
|
+ AP_DEBUG_ASSERT((pfd->reqevents & events) == 0);
|
|
+
|
|
+ if (pfd->reqevents) {
|
|
+ rv = apr_pollset_remove(pollset, pfd);
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ AP_DEBUG_ASSERT(1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (events & APR_POLLIN) {
|
|
+ events |= APR_POLLHUP;
|
|
+ }
|
|
+ pfd->reqevents |= events | APR_POLLERR;
|
|
+ rv = apr_pollset_add(pollset, pfd);
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ AP_DEBUG_ASSERT(1);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void del_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd,
|
|
+ apr_int16_t events)
|
|
+{
|
|
+ apr_status_t rv;
|
|
+
|
|
+ AP_DEBUG_ASSERT((pfd->reqevents & events) != 0);
|
|
+
|
|
+ rv = apr_pollset_remove(pollset, pfd);
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ AP_DEBUG_ASSERT(0);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (events & APR_POLLIN) {
|
|
+ events |= APR_POLLHUP;
|
|
+ }
|
|
+ if (pfd->reqevents & ~(events | APR_POLLERR)) {
|
|
+ pfd->reqevents &= ~events;
|
|
+ rv = apr_pollset_add(pollset, pfd);
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ AP_DEBUG_ASSERT(0);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ pfd->reqevents = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int proxy_tunnel_forward(proxy_tunnel_rec *tunnel,
|
|
+ struct proxy_tunnel_conn *in)
|
|
+{
|
|
+ struct proxy_tunnel_conn *out = in->other;
|
|
+ apr_status_t rv;
|
|
+ int sent = 0;
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, tunnel->r,
|
|
+ "proxy: %s: %s input ready",
|
|
+ tunnel->scheme, in->name);
|
|
+
|
|
+ rv = ap_proxy_transfer_between_connections(tunnel->r,
|
|
+ in->c, out->c,
|
|
+ in->bb, out->bb,
|
|
+ in->name, &sent,
|
|
+ tunnel->read_buf_size,
|
|
+ AP_PROXY_TRANSFER_YIELD_PENDING |
|
|
+ AP_PROXY_TRANSFER_YIELD_MAX_READS);
|
|
+ if (sent && out == tunnel->client) {
|
|
+ tunnel->replied = 1;
|
|
+ }
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ if (APR_STATUS_IS_INCOMPLETE(rv)) {
|
|
+ /* Pause POLLIN while waiting for POLLOUT on the other
|
|
+ * side, hence avoid filling the output filters even
|
|
+ * more to avoid blocking there.
|
|
+ */
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, tunnel->r,
|
|
+ "proxy: %s: %s wait writable",
|
|
+ tunnel->scheme, out->name);
|
|
+ }
|
|
+ else if (APR_STATUS_IS_EOF(rv)) {
|
|
+ /* Stop POLLIN and wait for POLLOUT (flush) on the
|
|
+ * other side to shut it down.
|
|
+ */
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r,
|
|
+ "proxy: %s: %s read shutdown",
|
|
+ tunnel->scheme, in->name);
|
|
+ in->down_in = 1;
|
|
+ }
|
|
+ else {
|
|
+ /* Real failure, bail out */
|
|
+ return HTTP_INTERNAL_SERVER_ERROR;
|
|
+ }
|
|
+
|
|
+ del_pollset(tunnel->pollset, in->pfd, APR_POLLIN);
|
|
+ add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
|
|
+ }
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel)
|
|
+{
|
|
+ int rc = OK;
|
|
+ request_rec *r = tunnel->r;
|
|
+ apr_pollset_t *pollset = tunnel->pollset;
|
|
+ struct proxy_tunnel_conn *client = tunnel->client,
|
|
+ *origin = tunnel->origin;
|
|
+ apr_interval_time_t timeout = tunnel->timeout >= 0 ? tunnel->timeout : -1;
|
|
+ const char *scheme = tunnel->scheme;
|
|
+ apr_status_t rv;
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10212)
|
|
+ "proxy: %s: tunnel running (timeout %lf)",
|
|
+ scheme, timeout >= 0 ? (double)timeout / APR_USEC_PER_SEC
|
|
+ : (double)-1.0);
|
|
+
|
|
+ /* Loop until both directions of the connection are closed,
|
|
+ * or a failure occurs.
|
|
+ */
|
|
+ do {
|
|
+ const apr_pollfd_t *results;
|
|
+ apr_int32_t nresults, i;
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
|
|
+ "proxy: %s: polling (client=%hx, origin=%hx)",
|
|
+ scheme, client->pfd->reqevents, origin->pfd->reqevents);
|
|
+ do {
|
|
+ rv = apr_pollset_poll(pollset, timeout, &nresults, &results);
|
|
+ } while (APR_STATUS_IS_EINTR(rv));
|
|
+
|
|
+ if (rv != APR_SUCCESS) {
|
|
+ if (APR_STATUS_IS_TIMEUP(rv)) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(10213)
|
|
+ "proxy: %s: polling timed out "
|
|
+ "(client=%hx, origin=%hx)",
|
|
+ scheme, client->pfd->reqevents,
|
|
+ origin->pfd->reqevents);
|
|
+ rc = HTTP_GATEWAY_TIME_OUT;
|
|
+ }
|
|
+ else {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10214)
|
|
+ "proxy: %s: polling failed", scheme);
|
|
+ rc = HTTP_INTERNAL_SERVER_ERROR;
|
|
+ }
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(10215)
|
|
+ "proxy: %s: woken up, %i result(s)", scheme, nresults);
|
|
+
|
|
+ for (i = 0; i < nresults; i++) {
|
|
+ const apr_pollfd_t *pfd = &results[i];
|
|
+ struct proxy_tunnel_conn *tc = pfd->client_data;
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
|
|
+ "proxy: %s: #%i: %s: %hx/%hx", scheme, i,
|
|
+ tc->name, pfd->rtnevents, tc->pfd->reqevents);
|
|
+
|
|
+ /* sanity check */
|
|
+ if (pfd->desc.s != client->pfd->desc.s
|
|
+ && pfd->desc.s != origin->pfd->desc.s) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10222)
|
|
+ "proxy: %s: unknown socket in pollset", scheme);
|
|
+ return HTTP_INTERNAL_SERVER_ERROR;
|
|
+ }
|
|
+
|
|
+ if (!(pfd->rtnevents & (APR_POLLIN | APR_POLLOUT |
|
|
+ APR_POLLHUP | APR_POLLERR))) {
|
|
+ /* this catches POLLNVAL etc.. */
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10220)
|
|
+ "proxy: %s: polling events error (%x)",
|
|
+ scheme, pfd->rtnevents);
|
|
+ return HTTP_INTERNAL_SERVER_ERROR;
|
|
+ }
|
|
+
|
|
+ /* Write if we asked for POLLOUT, and got it or POLLERR
|
|
+ * alone (i.e. not with POLLIN|HUP). We want the output filters
|
|
+ * to know about the socket error if any, by failing the write.
|
|
+ */
|
|
+ if ((tc->pfd->reqevents & APR_POLLOUT)
|
|
+ && ((pfd->rtnevents & APR_POLLOUT)
|
|
+ || !(pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)))) {
|
|
+ struct proxy_tunnel_conn *out = tc, *in = tc->other;
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
|
|
+ "proxy: %s: %s output ready",
|
|
+ scheme, out->name);
|
|
+
|
|
+ rc = ap_filter_output_pending(out->c);
|
|
+ if (rc == OK) {
|
|
+ /* Keep polling out (only) */
|
|
+ continue;
|
|
+ }
|
|
+ if (rc != DECLINED) {
|
|
+ /* Real failure, bail out */
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10221)
|
|
+ "proxy: %s: %s flushing failed (%i)",
|
|
+ scheme, out->name, rc);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ /* No more pending data. If the other side is not readable
|
|
+ * anymore it's time to shutdown for write (this direction
|
|
+ * is over). Otherwise back to normal business.
|
|
+ */
|
|
+ del_pollset(pollset, out->pfd, APR_POLLOUT);
|
|
+ if (in->down_in) {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
|
|
+ "proxy: %s: %s write shutdown",
|
|
+ scheme, out->name);
|
|
+ apr_socket_shutdown(out->pfd->desc.s, 1);
|
|
+ out->down_out = 1;
|
|
+ }
|
|
+ else {
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
|
|
+ "proxy: %s: %s resume writable",
|
|
+ scheme, out->name);
|
|
+ add_pollset(pollset, in->pfd, APR_POLLIN);
|
|
+
|
|
+ /* Flush any pending input data now, we don't know when
|
|
+ * the next POLLIN will trigger and retaining data might
|
|
+ * block the protocol.
|
|
+ */
|
|
+ if (ap_filter_input_pending(in->c) == OK) {
|
|
+ rc = proxy_tunnel_forward(tunnel, in);
|
|
+ if (rc != OK) {
|
|
+ return rc;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Read if we asked for POLLIN|HUP, and got it or POLLERR
|
|
+ * alone (i.e. not with POLLOUT). We want the input filters
|
|
+ * to know about the socket error if any, by failing the read.
|
|
+ */
|
|
+ if ((tc->pfd->reqevents & APR_POLLIN)
|
|
+ && ((pfd->rtnevents & (APR_POLLIN | APR_POLLHUP))
|
|
+ || !(pfd->rtnevents & APR_POLLOUT))) {
|
|
+ rc = proxy_tunnel_forward(tunnel, tc);
|
|
+ if (rc != OK) {
|
|
+ return rc;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } while (!client->down_out || !origin->down_out);
|
|
+
|
|
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10223)
|
|
+ "proxy: %s: tunnel finished", scheme);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
PROXY_DECLARE (const char *) ap_proxy_show_hcmethod(hcmethod_t method)
|
|
{
|
|
proxy_hcmethods_t *m = proxy_hcmethods;
|
|
--
|
|
2.27.0
|
|
|