From 3d2842e915d26a226a1524db8a718c9c9e2a57bc Mon Sep 17 00:00:00 2001 From: Yann Ylavic Date: Fri, 24 Sep 2021 15:52:50 +0000 Subject: [PATCH] mod_proxy: Add tunnel_forward hook. * modules/proxy/mod_proxy.h, modules/proxy/mod_proxy.c: Declare/implement the hook. * modules/proxy/proxy_util.c(proxy_transfer): Run tunnel_forward hooks when called by the tunneling loop. Simpler input/output brigade cleanup on exit. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1893603 13f79535-47bb-0310-9956-ffa450edef68 --- changes-entries/tunnel_forward_hook.txt | 2 + docs/log-message-tags/next-number | 2 +- modules/proxy/mod_proxy.c | 7 +++ modules/proxy/mod_proxy.h | 18 ++++++ modules/proxy/proxy_util.c | 75 +++++++++++++++++++------ 5 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 changes-entries/tunnel_forward_hook.txt diff --git a/changes-entries/tunnel_forward_hook.txt b/changes-entries/tunnel_forward_hook.txt new file mode 100644 index 0000000000..30e39847d2 --- /dev/null +++ b/changes-entries/tunnel_forward_hook.txt @@ -0,0 +1,2 @@ + *) mod_proxy: New tunnel_forward hooking mechanism allowing modules to handle + Upgrade(d) protocols data. [Yann Ylavic] \ No newline at end of file diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number index 817b26ce91..6f3af99369 100644 --- a/docs/log-message-tags/next-number +++ b/docs/log-message-tags/next-number @@ -1 +1 @@ -10295 +10297 diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index a4b0ce894c..035f52cb50 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -3474,6 +3474,7 @@ APR_HOOK_STRUCT( APR_HOOK_LINK(post_request) APR_HOOK_LINK(request_status) APR_HOOK_LINK(check_trans) + APR_HOOK_LINK(tunnel_forward) ) APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, scheme_handler, @@ -3517,3 +3518,9 @@ APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, request_status, APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, detach_backend, (request_rec *r, proxy_conn_rec *backend), (r, backend), OK, DECLINED) +APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(proxy, PROXY, int, tunnel_forward, + (proxy_tunnel_rec *tunnel, + conn_rec *c_i, conn_rec *c_o, + apr_bucket_brigade *bb), + (tunnel, c_i, c_o, bb), + OK, DECLINED) diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 646d9795fd..ad9a9eba32 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -1399,6 +1399,24 @@ PROXY_DECLARE(apr_off_t) ap_proxy_tunnel_conn_bytes_in( PROXY_DECLARE(apr_off_t) ap_proxy_tunnel_conn_bytes_out( const proxy_tunnel_conn_t *tc); +/** + * Tunnel forwarding hook + * Called for every brigade forwarded by a tunnel from/to the client to/from + * the origin. Each hook receives incoming buckets in bb and produces outgoing + * buckets in the same bb, much like an output filter. + * @param tunnel the tunnel + * @param c the connection the data are going to + * @param bb the incoming data + * @return OK/DECLINED to pass to the next hooks, DONE to not pass to + * the next hooks, an HTTP_ error on failure. + * @note A hook must not return DONE unless it consumes/sets-aside *all* the + * incoming buckets, and it must produce (non-meta-)data buckets only. + */ +PROXY_DECLARE_OPTIONAL_HOOK(proxy, PROXY, int, tunnel_forward, + (proxy_tunnel_rec *tunnel, + conn_rec *c_i, conn_rec *c_o, + apr_bucket_brigade *bb)) + /** * Clear the headers referenced by the Connection header from the given * table, and remove the Connection header. diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index 7ce2431f08..e620ee88ed 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -4531,7 +4531,8 @@ static apr_status_t proxy_transfer(request_rec *r, apr_off_t bsize, int flags, apr_off_t *bytes_in, - apr_off_t *bytes_out) + apr_off_t *bytes_out, + proxy_tunnel_rec *tunnel) { apr_status_t rv; int flush_each = 0; @@ -4557,8 +4558,7 @@ static apr_status_t proxy_transfer(request_rec *r, 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) - "proxy_transfer: error on %s - ap_get_brigade", - name); + "proxy_transfer: can't get data from %s", name); if (rv == APR_INCOMPLETE) { /* Don't return APR_INCOMPLETE, it'd mean "should yield" * for the caller, while it means "incomplete body" here @@ -4569,25 +4569,62 @@ static apr_status_t proxy_transfer(request_rec *r, } 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; } + len = -1; apr_brigade_length(bb_i, 0, &len); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306) - "proxy_transfer: read %" APR_OFF_T_FMT " bytes " - "of %s", len, name); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "proxy_transfer: got %" APR_OFF_T_FMT " bytes " + "from %s", len, name); if (bytes_in && len > 0) { *bytes_in += len; } - ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o); + + rv = ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o); + if (rv != APR_SUCCESS) { + break; + } + + if (tunnel) { + int rc = proxy_run_tunnel_forward(tunnel, c_i, c_o, bb_o); + if (rc != OK && rc != DONE) { + if (!ap_is_HTTP_ERROR(rc)) { + /* SUSPENDED is not allowed for now */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10295) + "proxy: %s: invalid status %d returned by " + "tunnel forward hooks", tunnel->scheme, rc); + } + rv = APR_EGENERAL; + break; + } + if (APR_BRIGADE_EMPTY(bb_o)) { + /* Buckets retained by the hooks, next. */ + continue; + } + if (rc == DONE) { + /* DONE with data is invalid because it'd mean that the next + * hooks wouldn't have a chance to see the data, hence no hook + * would be able to retain data. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10296) + "proxy: %s: invalid return value from tunnel " + " forward hook", tunnel->scheme); + rv = APR_EGENERAL; + break; + } + + len = -1; + apr_brigade_length(bb_o, 0, &len); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "proxy_transfer: forward %" APR_OFF_T_FMT " bytes " + "from %s", len, name); + } if (bytes_out && len > 0) { *bytes_out += len; } @@ -4612,14 +4649,14 @@ static apr_status_t proxy_transfer(request_rec *r, 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) - "proxy_transfer: error on %s - ap_pass_brigade", - name); + "proxy_transfer: can't pass %" APR_OFF_T_FMT + " bytes from %s", len, name); flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER; break; } + apr_brigade_cleanup(bb_o); /* Yield if the output filters stack is full? This is to avoid * blocking and give the caller a chance to POLLOUT async. @@ -4644,11 +4681,14 @@ static apr_status_t proxy_transfer(request_rec *r, } } - if (flags & AP_PROXY_TRANSFER_FLUSH_AFTER) { + /* bb_o first to avoid protential dangling buckets (transient) */ + apr_brigade_cleanup(bb_o); + apr_brigade_cleanup(bb_i); + + if ((flags & AP_PROXY_TRANSFER_FLUSH_AFTER) && !c_o->aborted) { 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, "proxy_transfer complete (%s %pI)", @@ -4675,7 +4715,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections( { apr_off_t bytes_out = 0; apr_status_t rv = proxy_transfer(r, c_i, c_o, bb_i, bb_o, name, bsize, - flags, NULL, &bytes_out); + flags, NULL, &bytes_out, NULL); if (sent && bytes_out > 0) { *sent = 1; } @@ -4853,7 +4893,8 @@ static int proxy_tunnel_transfer(proxy_tunnel_rec *tunnel, in->name, tunnel->read_buf_size, AP_PROXY_TRANSFER_YIELD_PENDING | AP_PROXY_TRANSFER_YIELD_MAX_READS, - &in->bytes_in, &out->bytes_out); + &in->bytes_in, &out->bytes_out, + tunnel); if (rv != APR_SUCCESS) { if (APR_STATUS_IS_INCOMPLETE(rv)) { /* Pause POLLIN while waiting for POLLOUT on the other