Expand description
The control plane — a live handle onto a running proxy.
Everything else in this crate is configured before it starts: a
ListenerConfig is consumed when the
endpoint is bound, a ProxySessionConfig
is copied once per accepted connection, and a
ProxyHook declares its interest once at
session start. That is deliberate — it is what makes a run reproducible
from the values that started it — but it leaves no way to ask a proxy
that is already running anything at all.
ProxyControl is that way. It is obtained from
TransparentProxy::control
before the accept loop is awaited, because the caller that owns the
proxy is usually the caller that is about to give up its thread of
control to run(); a handle that could only be taken afterwards could
not be taken at all. Everything it reports is therefore defined for a
proxy that has not bound yet: ProxyControl::local_addr answers
ProxyError::NotBound until the listener exists, and
ProxyControl::sessions answers an empty list.
§Two pieces of shared state, both released by a guard
The handle reads two things the proxy used to keep as stack locals of
run(): the bound listener, and the set of sessions that are live right
now. Both are published into this module when they come into existence
and removed when they go, and in both cases the removal is a Drop
rather than a call at each exit site.
That choice is the whole correctness argument for
ProxyControl::sessions. A session ends for at least five distinct
reasons — a forwarding task returned, a forwarding task errored, the peer
went away, a hook asked for a close, the proxy was cancelled — and a
sixth that no enumeration covers, the session’s whole future being
dropped by whoever spawned it. A list of removal sites is only ever as
complete as the reader who wrote it, and the failure it produces is
silent: sessions() keeps naming an id nothing can act on, and every
later call that takes that id fails in a way that looks like a race.
Drop is complete by construction, so the registration is handed out as
a SessionGuard and lives in the frame of the function that runs the
session.
A third piece is held here and released by nothing: the proxy-wide
shaping counters behind ProxyControl::stats. They are the one report
in this module that is cumulative rather than instantaneous, and that
is exactly why they cannot be assembled from the two above. A total
summed over the session list would fall every time a client disconnected,
because the list is released by a Drop and a session that has ended
leaves nothing behind — and a statistic that goes down under normal
operation cannot be alerted on. So each session is handed the counters
when it attaches, charges them as it forwards, and leaves them behind
when it goes.
§Reaching a session that is already running
Registering an id is enough to list a session but not to act on one. Acting needs the session’s cancellation token, its close request slot, its stream registry, its two control-stream inboxes and its egress knobs — and every one of those is built by the session before it dials the upstream relay, which is what lets the whole entry go into the registry at the top of the run function. A session spends its longest single operation connecting, sometimes for as long as its connect timeout allows and sometimes forever; one that only became reachable afterwards would be unreachable for exactly that long.
What is deliberately not in an entry is either connection. The two
Transports are owned by the forwarding scope, and a table scanned by
every list call has no business holding them alive past the session that
owns them.
The channels are created with the session rather than attached later because a task attached after the fact could never cover the sessions that were already running when it was attached — which is precisely the set a control plane exists to reach.
§Three levels of reach, and why they are not one mechanism
ProxyControl::close_session acts on the whole session, so it goes to
the session’s own command task: it is the only request that has to
wait for something — the bounded egress drain — and a waiting request
needs somewhere to wait that is not the caller’s thread.
ProxyControl::reset_stream and ProxyControl::inject_control act on
one stream, and neither waits. They resolve in the caller: the registry
entry carries the session’s StreamRegistry, so a key that names nothing
live is refused synchronously, and a key that names a live stream is reached
by dropping a StreamCommand into that stream’s own inbox. Routing them
through the session task as well would have added a hop and a second place
for a request to be lost, and would have made there is no such stream an
answer that arrives asynchronously — which a synchronous method signature
cannot deliver.
Nothing here accepts a request and discards it. Every method either
delivers to a task that will act on it or answers a ControlError
saying why it could not.
§Reconfiguring, and the one verb that cannot reach a live connection
Three of the requests here change what the proxy is rather than what a particular session is doing, and they reach three different distances. The distances are not a matter of how they were written; they are what the thing being changed will admit.
ProxyControl::set_impair reaches traffic already in flight. It acts
below QUIC, on the datagrams a leg’s socket is about to pass, and the
next one out carries the new profile.
ProxyControl::set_shape and ProxyControl::set_shaper_enabled
reach a running session. The switch is read on the next release decision
a queue makes, which for a stream held by a bucket that will refill is
within one pacing interval and for a stream held by a bucket configured
at zero is not until the max_hold clamp — see the method for why
nothing here can do better. A new profile is taken up by a running
session at its next stream, because a stream’s egress queue holds the
scheduler its units were admitted under and a class is an index into that
scheduler’s class list.
ProxyControl::set_transport reaches no connection that already
exists, ever. A QUIC connection takes its transport configuration once,
at setup, and keeps it for life — quinn offers four setters on a live
connection and no way to replace the configuration behind it. So a
profile set on the relay leg reaches the next connection this proxy
opens, which is the next session it accepts, and a profile set on the
client leg reaches the next connection it accepts, which the proxy does
not initiate and which may never arrive. On a proxy that never dials or
accepts again — every client already connected, nothing new coming — the
call is a permanent silent no-op that returns Ok(()), and no return
value here distinguishes that from a setting that reached everything
afterwards. It is written out on the method, in this module, and in the
crate documentation, in those words, because a caller who reads it as
“set the window” and measures the session in front of them will measure
the old window and believe the new one.
§Two kinds of name here are deliberately not links
SessionGuard, StreamRegistry and StreamCommand are crate-internal,
and set_impair and clear_impair exist only under the impair feature.
This page is public and is built without that feature, so a link to
either kind resolves to nothing — and an unresolved intra-doc link is an
error under the documentation build, not a warning. Both are written in
plain code font instead, so the prose can still name the thing it is
describing without breaking that build. Turning one back into a link is
what reddens it.
Structs§
- Abort
OnDrop 🔒 - A spawned task that is aborted when this value is dropped.
- Bound
Guard 🔒 - The published bound listener’s registration. Un-publishes on drop.
- Client
Leg 🔒 - The client-facing leg’s two mutable facts, under one lock.
- Control
Attachment 🔒 - A session’s attachment to its proxy’s control plane.
- Control
Leg 🔒 - The two ends of one control-stream direction’s request channel.
- Control
Plane 🔒 - The state a
ProxyControlreads, owned by the proxy and shared with every handle it hands out. - LegSetup 🔒
- What the control plane knows about the two legs from the configuration the proxy was built with, and cannot learn any other way.
- Proxy
Control - A handle onto a running proxy.
- Session
Guard 🔒 - One live session’s registration. Ends it on drop.
- Session
Handle 🔒 - What the control plane keeps for one live session.
- Shape
Control 🔒 - This proxy’s shaping profile, and whether it paces.
- Stream
Entry 🔒 - What the registry keeps for one live stream.
- Stream
Guard 🔒 - One live stream’s registration. Ends it on drop.
- Stream
Registry 🔒 - Every forwarded stream that is still live, and the
Gateeach one releases when it ends.
Enums§
- Control
Error - Why a control-plane request could not be carried out.
- Session
Command 🔒 - A request delivered to one session’s command task.
- Stream
Command 🔒 - A request delivered to the task that owns one forwarded stream.
Constants§
- COMMAND_
QUEUE_ 🔒DEPTH - How many commands a session’s inbox holds before a sender has to wait.