On Thu, Jul 1, 2021 at 10:15 AM Stefan Eissing
<stefan.eissing@greenbytes.de> wrote:
>
> > Am 30.06.2021 um 18:01 schrieb Eric Covener <covener@gmail.com>:
> >
> > On Wed, Jun 30, 2021 at 11:46 AM Stefan Eissing
> > <stefan.eissing@greenbytes.de> wrote:
> >>
> >> It looks like we stumbled upon an issue in https://bz.apache.org/bugzilla/show_bug.cgi?id=65402 which concerns the life times of our backend connections.
> >>
> >> When a frontend connection causes a backend request and drops, our backend connection only notifies the loss when it attempts to pass some data. In normal http response processing, this is not an issue since response chunks are usually coming in quite frequently. Then the proxied connection will fail to pass it to an aborted frontend connection and cleanup will occur.
> >>
> >> However, with such modern shenanigans such as Server Side Events (SSE), the request is supposed to be long running and will produce body chunks quite infrequently, like every 30 seconds or so. This leaves our proxy workers hanging in recv for quite a while and may lead to worker exhaustion.
> >>
> >> We can say SSE is a bad idea anyway, but that will probably not stop people from doing such crazy things.
> >>
> >> What other mitigations do we have?
> >> - pthread_kill() will interrupt the recv and probably make it fail
> >> - we can use shorter socket timeouts on backend and check r->connection status in between
> >> - ???
Can mod_proxy_http2 do better here? I suppose we lose all
relationships in h2->h1 and then h1->h2, asking just in case..
> >
> >
> > In trunk the tunnelling side of mod_proxy_http can go async and get
> > called back for activity on either side by asking Event to watch both
> > sockets.
>
>
> How does that work, actually? Do we have an example somewhere?
This is the ap_proxy_tunnel_create() and ap_proxy_tunnel_run()
called/used by mod_proxy_http for Upgrade(d) protocols.
I'm thinking to improve this interface to have a hook called in
ap_proxy_transfer_between_connections() with the data being forwarded
from one side to the other (in/out connection), and the hook could
decide to let the data pass, or retain them, and/or switch to
speculative mode, and/or remove/add one side/sense from the pollset,
or abort, or.. The REALLY_LAST hook would be something like the
existing ap_proxy_buckets_lifetime_transform().
The hook(s) would be responsible (each) of their connections' states,
mod_proxy_http could then be implemented fully async in a callback,
but I suppose mod_h2 could hook itself there too if it has something
to care about.
>
> > I'm not sure how browsers treat the SSE connection, can it ever have a
> > subsequent request? If not, maybe we could see the SSE Content-Type
> > and shoehorn it into the tunneling (figuring out what to do with
> > writes from the client, backport the event and async tunnel stuff?)
>
> I don't think they will do a subsequent request in the HTTP/1.1 sense,
> meaning they'll close their H1 connection and open a new one. In H2 land,
> the request connection is a virtual "secondary" one away.
The issue I see here for inbound h2 is that the tunneling loop needs
something to poll() on both sides, and there is no socket on the h2
slave connections to do that.. How to poll a h2 stream, pipe or
something?
>
> But changing behaviour based on the content type seems inadequate. When
> the server proxies applications (like uwsgi), the problem may also happen
> to requests that are slow producing responses.
>
> To DoS such a setup, where a proxied response takes n seconds, you'd need
> total_workers / n aborted requests per second. In HTTP/1.1 that would
> all be connections and maybe noticeable from a supervisor, but in H2 this
> could happen all on the same tcp connection (although our h2 implementation
> has some protection against abusive client behaviour).
>
> A general solution to the problem would therefore be valuable, imo.
The general/generic solution for anything proxy could be the tunneling
loop, a bit like a proxy_tcp (or proxy_transport) module to hook to.
>
> We should think about solving this in the context of mpm_event, which
> I believe is the production recommended setup that merits our efforts.
Yes, the tunneling loop stops and the poll()ing is deferred to MPM
event (the ap_hook_mpm_register_poll_callback*() API) when nothing
comes from either side for an AsyncDelay.
>
> If mpm_event could make the link between one connection to another,
> like frontend to backend, it could wake up backends on a frontend
> termination. Do you agree, Yann?
Absolutely, but there's more work to be done to get there :)
Also, is this kind of architecture what we really want?
Ideas, criticisms and discussions welcome!
>
> Could this be as easy as adding another "conn_rec *context" field
> in conn_rec that tracks this?
Tracking some connection close (at transport level) on the client side
to "abort" the transaction is not enough, a connection can be
half-closed and still want some response back for instance.
We want something that says abort (like h2 RST_STREAM), no such thing
in transactional HTTP/1 (AFAICT), that's why mod_h2 would need to hook
in the tunnel if it wanted to abort the loop (IIUC).
Cheers;
Yann.