pub struct ProxySession {
session_id: SessionId,
config: ProxySessionConfig,
client_alpn: Vec<u8>,
observer: Arc<dyn ProxyObserver>,
hook: Arc<dyn ProxyHook>,
cancel: CancellationToken,
counters: Arc<Recorder>,
shape_stats: Arc<ShapeRecorder>,
control: Option<ControlAttachment>,
}Expand description
A proxy session that forwards traffic between a client and an upstream relay. One session is created per accepted client connection.
Fields§
§session_id: SessionId§config: ProxySessionConfig§client_alpn: Vec<u8>The ALPN the client negotiated with us (empty for WebTransport or when unavailable). Drives both upstream ALPN selection and initial draft detection for drafts 15+.
observer: Arc<dyn ProxyObserver>§hook: Arc<dyn ProxyHook>§cancel: CancellationToken§counters: Arc<Recorder>This session’s slow-path counters, shared with every forwarding task. One per session, not per process: a test asserting that a session touched no slow path must not be spoiled by another session running beside it.
shape_stats: Arc<ShapeRecorder>This session’s shaping counters, shared with every forwarding task
the same way counters is. A sibling of Recorder, not an
extension of it: Counters is compared whole against
Counters::default() by tests/interest_none.rs and by value
elsewhere, and it would lose Copy for a Vec that is empty on
every unshaped session.
Always constructed, including when config.shape is None, for the
same reason StreamRegistry is: a structure that only existed when a
profile was configured would make the reports that name it
conditional on configuration nobody reading them can see. Its rows
are pre-sized from the profile’s class list at this point and never
resized, so moving a running session to a different class list means
building a new session-scoped recorder rather than resizing this one.
control: Option<ControlAttachment>This session’s attachment to its proxy’s control plane, or None
when it has no proxy.
None is not a degraded mode. A session constructed directly — which
is how this crate’s own tests drive one, and how a caller that wants
one socket per session reaches the seam — belongs to no
TransparentProxy, so there is no
plane for it to register with and no
ProxyControl that could name it.
Making it an Option rather than always constructing one is what
keeps that honest: an unattached session cannot appear in a list of
live sessions belonging to a proxy that never accepted it.
Implementations§
Source§impl ProxySession
impl ProxySession
Sourcepub fn new(
session_id: SessionId,
config: ProxySessionConfig,
client_alpn: Vec<u8>,
observer: Arc<dyn ProxyObserver>,
hook: Arc<dyn ProxyHook>,
cancel: CancellationToken,
) -> Self
pub fn new( session_id: SessionId, config: ProxySessionConfig, client_alpn: Vec<u8>, observer: Arc<dyn ProxyObserver>, hook: Arc<dyn ProxyHook>, cancel: CancellationToken, ) -> Self
Create a new proxy session.
client_alpn should be the ALPN the listener negotiated with the
client. Pass an empty slice if unavailable (e.g., WebTransport).
Sourcepub(crate) fn attach_control(&mut self, plane: Arc<ControlPlane>)
pub(crate) fn attach_control(&mut self, plane: Arc<ControlPlane>)
Attach this session to a proxy’s control plane.
Called by the accept loop between constructing the session and spawning it, which is the only window in which the session is still owned exclusively. It mints the command channel but registers nothing: registration happens when the session begins to run, so that the entry’s lifetime is the session’s and not this call’s.
It also replaces the shaping recorder, with one that forwards everything it is charged into the proxy’s own counters as well. A second recorder installed beside the first would need a second set of call sites on the data path, and a figure added to one and forgotten at the other is a divergence nothing would report; forwarding from inside means one call charges both or neither.
Replacing rather than mutating is what that window buys. Nothing has
run, so the recorder being discarded is all zeros, and nothing has
cloned it — ForwardCtx takes its Arc when the session starts
forwarding, which is after this returns — so every task will hold the
recorder that reports to the proxy, not a mixture.
Sourcepub fn counters(&self) -> Counters
pub fn counters(&self) -> Counters
This session’s slow-path counters.
Replaces the deleted process-global instrument::snapshot(). Cheap:
a read of ~12 relaxed atomics plus a 128-slot histogram scan.
A session whose hook declared Interest::NONE and whose observer
answers false to wants_events ends with
counters() == Counters::default() — that is what makes the
fast-path claim falsifiable rather than promised.
Sourcepub fn shape_stats(&self) -> ShapeStats
pub fn shape_stats(&self) -> ShapeStats
This session’s shaping statistics.
Readable while the session runs, which is the point: the
ProxySession is constructed behind an Arc before the accept task
is spawned (tests/common/mod.rs), so a test can sample its
classes without waiting for teardown and without a control plane.
A session with no ShapeProfile ends — and begins, and stays — at
shape_stats() == ShapeStats::default(). That is a falsifiable
claim rather than a promise only because the shaping path does move
these counters when it is entered: see
ShapeStats::objects_seen.
Allocates one Vec and one String per configured class. Cheap,
but not free — this is a reader’s call, not a data-path one.
Sourcepub async fn run(&self, client_conn: Connection) -> Result<(), ProxyError>
pub async fn run(&self, client_conn: Connection) -> Result<(), ProxyError>
Run the proxy session with a raw QUIC client connection.
Sourcefn initial_draft(&self) -> DraftVersion
fn initial_draft(&self) -> DraftVersion
The draft this session starts on. Drafts 15+ resolve unambiguously
from the client ALPN (moqt-15 through moqt-19); otherwise we fall
back to config.draft, which the control stream refines once it
peeks at CLIENT_SETUP / SERVER_SETUP for the moq-00 cohort (drafts
07–14).
It is the starting draft and not the session’s draft. That lives in
SessionDraft, which every forwarding task reads and the control
stream writes.
Sourcefn draft_is_fixed(&self) -> bool
fn draft_is_fixed(&self) -> bool
Whether the starting draft is fixed (ALPN-derived) or is still open to being named by a CLIENT_SETUP / SERVER_SETUP peek.
Sourceasync fn run_with_transport(&self, client: Transport) -> Result<(), ProxyError>
async fn run_with_transport(&self, client: Transport) -> Result<(), ProxyError>
Run the proxy session with an already-wrapped transport.
Connects to the upstream relay, then forwards all streams and datagrams bidirectionally between the client and relay. Parses MoQT frames inline and emits events via the observer.
Sourceasync fn connect_upstream(&self) -> Result<Transport, ProxyError>
async fn connect_upstream(&self) -> Result<Transport, ProxyError>
Connect to the upstream relay (with optional timeout).
async fn connect_upstream_inner(&self) -> Result<Transport, ProxyError>
Sourceasync fn connect_upstream_quic(
&self,
transport_config: Option<Arc<TransportConfig>>,
) -> Result<Transport, ProxyError>
async fn connect_upstream_quic( &self, transport_config: Option<Arc<TransportConfig>>, ) -> Result<Transport, ProxyError>
Connect to the upstream relay via QUIC.
transport_config is what this leg resolved to before anything was
built — the caller’s raw config, or one built from their profile, or
None for quinn’s defaults. It arrives as an argument rather than
being read from self.config here so that there is exactly one
place the two fields are reconciled, and so that the reconciliation
happens before the transport is even dispatched on.
Sourcefn build_upstream_tls_config(&self) -> Result<ClientConfig, ProxyError>
fn build_upstream_tls_config(&self) -> Result<ClientConfig, ProxyError>
Build a rustls ClientConfig for the upstream connection.