Skip to main content

ProxySession

Struct ProxySession 

Source
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

Source

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).

Source

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.

Source

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.

Source

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.

Source

pub async fn run(&self, client_conn: Connection) -> Result<(), ProxyError>

Run the proxy session with a raw QUIC client connection.

Source

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.

Source

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.

Source

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.

Source

async fn connect_upstream(&self) -> Result<Transport, ProxyError>

Connect to the upstream relay (with optional timeout).

Source

async fn connect_upstream_inner(&self) -> Result<Transport, ProxyError>

Source

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.

Source

fn build_upstream_tls_config(&self) -> Result<ClientConfig, ProxyError>

Build a rustls ClientConfig for the upstream connection.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more