Skip to main content

RoutingTable

Struct RoutingTable 

Source
pub struct RoutingTable { /* private fields */ }
Expand description

Routing table for stream-to-destination mapping

Implementations§

Source§

impl RoutingTable

Source

pub fn new(local_id: u64) -> Self

Create a new routing table

Source

pub fn local_id(&self) -> u64

Get local node ID

Source

pub fn add_route(&self, dest_id: u64, next_hop: SocketAddr) -> u64

Add or update the ORDINARY (unauthenticated) route.

Called by routed end-to-end installs and legacy/manual callers. Unconditionally replaces the ordinary candidate — a direct route is always preferred over any indirect one (metric 1; pingwave routes carry hop_count + 2, never below 2) — and refreshes updated_at.

Non-destructive to authenticated state by construction: this writes only the ordinary slot, so a routed (connect_via) install can no longer erase an authenticated learned route to the same endpoint. A routed end-to-end session contributes no adjacency evidence, and must not delete stronger evidence. Returns the transition token this install produced, so a caller that may later have to undo it can name the exact write rather than matching on a destination and address a replacement could already have reused.

Source

pub fn add_route_with_metric( &self, dest_id: u64, next_hop: SocketAddr, metric: u16, )

Add or update the ORDINARY route with an explicit metric.

Used by the pingwave-driven route installer. Within the ordinary slot the existing candidate is replaced only if the new metric is strictly better — a peer that crafts an equal metric must not displace an installed route — and on equal/worse metrics the existing candidate is kept but refreshed, since an alternate path’s arrival is evidence the destination is still reachable.

Both of those rules apply ONLY among unauthenticated candidates. This writer cannot see, replace, or refresh the protected candidate: an unauthenticated datagram is not evidence about an authenticated adjacency, in either direction.

Source

pub fn add_authenticated_route_with_metric( &self, dest_id: u64, next_hop: SocketAddr, next_hop_id: u64, metric: u16, )

Add or update a learned route that binds the authenticated identity of its adjacent next hop (SUBNET_AUTH_PLAN.md D6).

Same precedence contract as Self::add_route_with_metric — a strictly better metric replaces, anything else keeps the installed entry — with one addition: an equal-or-worse arrival that agrees with the installed entry’s next_hop upgrades an identity-less entry in place. A legacy entry left behind by an older writer would otherwise pin protected forwarding dead for that destination forever, because equal-metric refreshes never replace the entry that could carry the identity.

What an equal-or-worse arrival can never do is rewrite an installed identity — or refresh a route it does not carry. Freshness belongs to the installed adjacent identity/path:

  • same address + same identity → refresh;
  • same address + no installed identity → upgrade + refresh;
  • same address + conflicting identity → no rewrite, no refresh (a conflicting claim is evidence of address reuse, not of reachability — the conflicted entry is left to age out);
  • different next hop → no rewrite, no refresh. Another authenticated peer proving an alternate path exists is not evidence the installed path is alive; letting it renew the installed binding would pin a blackholed protected route fresh forever while the table refuses to switch to the alternate. (The ordinary writer’s refresh rule is unchanged — ordinary candidates carry no protected traffic. A future multi-route table may retain the alternate separately.)

The “no identity yet” case is now structural rather than an in-place upgrade: an ordinary candidate lives in its own slot, so an authenticated arrival always has an empty protected slot to land in regardless of what any pingwave installed. That is what keeps a forged legacy route from suppressing protected reachability forever.

Source

pub fn invalidate_protected_via(&self, identity: u64) -> usize

Retire every PROTECTED candidate bound to identity, across all destinations. Returns how many were retired.

Called when an authenticated adjacency ends in a way that does not replace it with an equivalent one — a direct session displaced by a routed end-to-end session, say. Every protected candidate whose next_hop_id is that peer was evidence about an adjacency that no longer exists, so it is invalidated rather than migrated: a routed session is not a weaker version of the direct one, it is different evidence entirely, and it cannot carry protected forwarding at all.

Ordinary candidates are untouched — they never depended on the adjacency.

Source

pub fn remove_ordinary_route(&self, dest_id: u64) -> Option<RouteEntry>

Remove the ORDINARY candidate — the symmetric counterpart of Self::add_route, which writes only that slot.

This is what a caller undoing its own manual/legacy install wants. Removing the protected candidate too would let an ordinary-only API delete authenticated state it never created.

Source

pub fn remove_protected_route_if_owned( &self, dest_id: u64, identity: u64, ) -> Option<RouteEntry>

Remove the PROTECTED candidate, but only when it is bound to identity — the owner’s own retraction.

Source

pub fn remove_destination_all_candidates( &self, dest_id: u64, ) -> Option<RouteEntry>

Remove a destination outright — BOTH candidates, regardless of provenance or ownership.

Explicitly administrative: it crosses the provenance boundary every other operation respects, so it is named for what it does rather than reading like the inverse of add_route (it is not — see Self::remove_ordinary_route).

Returns the protected candidate if there was one, else the ordinary one.

Source

pub fn remove_route(&self, dest_id: u64) -> Option<RouteEntry>

👎Deprecated:

asymmetric with add_route: use remove_ordinary_route, remove_protected_route_if_owned, or remove_destination_all_candidates

Deprecated alias for Self::remove_destination_all_candidates.

Kept so existing callers keep compiling, but the name is misleading now that add_route writes one slot and this clears both. New code should say which it means.

Source

pub fn remove_route_if_from_hop( &self, dest_id: u64, next_hop: SocketAddr, identity: u64, sender_is_direct: bool, ) -> TransitionOutcome

Remove what the authenticated sender identity actually OWNS at next_hop — the predicate route WITHDRAWAL must use.

Ownership differs by candidate, and neither half is an address match alone:

  • protectednext_hop_id == Some(identity) is sufficient, and the address is deliberately NOT required. install_peer publishes the peer record and session index before migrating routes, so an authenticated withdrawal can legitimately arrive under the new session while the binding still carries the old address. Requiring the address there made the handler drop the graph edge and then return without removing the route, leaving route and graph state inconsistent until age-out.
  • ordinary — the address must match AND sender_is_direct must confirm the sender genuinely owns that address (forward and reverse indexes agree). A routed end-to-end peer records its RELAY’s address; without the confirmation it could remove unrelated legacy routes sitting at the shared relay tuple, which belong to the relay, not to it.

Returns a TransitionOutcome describing what actually changed, NOT merely whether something was removed.

With two candidates per destination, “a candidate was removed” and “this node can no longer reach the destination” are different facts. A caller that treats the first as the second disrupts sensing and cascades an “unreachable via me” withdrawal while a live alternate candidate is still installed right there. The outcome reports the effective path before and after and whether the destination is still reachable, so the caller can distinguish removing a hidden candidate (nothing to announce), switching the effective path (re-anchor locally), and genuinely losing the destination (promote or cascade).

Source

pub fn remove_ordinary_route_if_next_hop_is( &self, dest_id: u64, expected_next_hop: SocketAddr, ) -> bool

Remove the ORDINARY candidate for dest_id iff its next_hop still equals expected_next_hop — the provenance-specific rollback for a caller that installed an ordinary route.

Rollback must undo exactly what it did. Scanning both slots by address lets an ordinary registration’s failure erase a protected candidate that merely shares the address.

Source

pub fn remove_ordinary_route_if_token_is( &self, dest_id: u64, expected_token: u64, ) -> bool

Remove the ORDINARY candidate for dest_id iff the destination still carries the exact transition token expected_token — the rollback form for a caller undoing a write it can name.

Stronger than the address form, and for a reason address cannot cover: two registrations for the same endpoint through the same relay install an identical (dest_id, next_hop) pair, so an address-keyed rollback of the FIRST one silently deletes the second one’s route. The token is never reused, so it names the write and not the shape of the write.

Deliberately conservative: any other observable change to this destination since the install — including one to the protected candidate — moves the token and the rollback declines, leaving the candidate to age out rather than removing state it can no longer prove it owns.

Source

pub fn remove_route_if_next_hop_is( &self, dest_id: u64, expected_next_hop: SocketAddr, ) -> bool

Remove any candidate for dest_id whose next_hop still equals expected_next_hop. Used by rollback paths that registered a specific route and need to undo it without clobbering a newer concurrently-written entry. Returns true if anything was removed.

Address-only: correct for undoing a write THIS node just made (the caller knows exactly what it installed). Withdrawal — a claim from a REMOTE sender — must use Self::remove_route_if_from_hop instead.

Source

pub fn migrate_next_hop( &self, old: SocketAddr, new: SocketAddr, identity: u64, ) -> usize

Repoint the routes that ride through a re-handshaking peer at its new address, refreshing updated_at so the migrated entries aren’t immediately swept. Returns the number of entries migrated.

Called when a peer re-handshakes from a new address (NAT rebind): multi-hop routes learned through that peer still carry its previous address as next_hop, and — because equal-metric refreshes deliberately never overwrite an installed next_hop — nothing else would ever repoint them. Without this migration, address-keyed operations such as Self::remove_route_if_next_hop_is (used by the RT-5 route withdrawal receive path) silently miss those entries.

An entry moves only when the caller owns BOTH halves of it:

  • An entry bound to identity and still at old follows the identity to new. Requiring the expected old address is what makes a STALE migration harmless: two accepted re-handshakes for the same peer can serialize their peer-map replacement but finish these migrations in the opposite order, and identity equality alone would let the older A→B roll back the newer A→C. The older caller’s routes no longer point at A, so it matches nothing and returns 0.
  • An entry bound to a different identity is never touched, even when its next_hop equals old: the address may have been reused, and an address match must not retarget a route that belongs to someone else’s authenticated adjacency.
  • A legacy entry (no identity) migrates by address match, as before — it carries no protected traffic either way.
Source

pub fn lookup(&self, dest_id: u64) -> Option<SocketAddr>

Look up next hop for destination — the ordinary forwarding lookup, over whichever candidate is currently effective.

Returns None for stale routes — a candidate whose updated_at is older than the configured max_route_age (default: very large; call Self::set_max_route_age to enable expiry). Stale candidates stay in the map until a periodic Self::sweep_stale call removes them.

Source

pub fn add_authenticated_route( &self, dest_id: u64, next_hop: SocketAddr, next_hop_id: u64, ) -> u64

Install an identity-bound route for protected forwarding. Returns the transition token this install produced — see Self::add_route.

Source

pub fn lookup_authenticated(&self, dest_id: u64) -> Option<(u64, SocketAddr)>

The PROTECTED candidate’s identity and address — the protected-forwarding lookup. It answers “who is the next hop”, where plain lookup answers only “where do I send”.

Reads the protected slot alone: an ordinary candidate, however good its metric and whoever installed it, resolves to None rather than to an unauthenticated guess.

Source

pub fn rebind_authenticated_route( &self, dest_id: u64, identity: u64, new_addr: SocketAddr, ) -> bool

Move the protected candidate to a new address under the same identity. Returns false when it is absent or bound to a different identity.

Source

pub fn observe(&self, dest_id: u64) -> Option<RouteObservation>

Read a destination’s full candidate state for a later conditional write. Pair with Self::install_metered_if_unchanged or Self::remove_failed_candidates_if_unchanged.

Source

pub fn install_metered_if_unchanged( &self, dest_id: u64, observed: RouteObservation, next_hop: SocketAddr, provenance: AlternateProvenance, metric: u16, ) -> Option<TransitionOutcome>

Install a route with an explicit metric and provenance, ONLY if the destination has not changed since observed was read — the compare-and-set form every event-driven rewriter must use.

Those callers read peer state, decide, and only then write. Between the two a fresh authenticated route can land; an unconditional write would clobber it with a decision made about state that no longer exists. Decision-time filtering does not help — the race is at mutation time.

provenance picks the slot; the caller states the metric it is installing (metric 1 is the claim of an adjacency and nothing else). Returns the produced TransitionOutcome — including the new token — when the write happened, None when it was refused.

Returning the token ATOMICALLY matters: a caller that wrote and then re-read to learn “its” token can observe a third party’s write in between and record that as its own — which is how a conditional undo ends up undoing a newer writer.

Source

pub fn remove_failed_candidates_if_unchanged( &self, dest_id: u64, observed: RouteObservation, failed_identity: u64, failed_addr: SocketAddr, ) -> Option<TransitionOutcome>

Apply a peer-failure transition to ONE destination atomically: verify the observation, remove every candidate the failure invalidates, and keep every candidate it does not.

Removal is ALL failure handling does. A failure invalidates the evidence that depended on the failed peer; it does not know a replacement path, and manufacturing one here converted “some peer is alive” into “that peer may route this destination”. A surviving candidate simply wins the next lookup; a destination left with nothing becomes unreachable, which is the truthful answer until discovery produces fresh evidence.

The transition is still a CANDIDATE event, not a destination event: ownership is per candidate, matching the withdrawal rule — the protected candidate by bound identity (its address may have drifted), the ordinary one by address.

Returns None if the observation is stale (or the token space is exhausted), in which case nothing was touched.

Source

pub fn install_metered_if_absent( &self, dest_id: u64, next_hop: SocketAddr, provenance: AlternateProvenance, metric: u16, ) -> bool

Install a route into a destination that has NO entry at all — the compare-and-set form for a writer whose observation was “absent”.

Recovery needs this exactly once: a failure that removed a peer’s last candidate removed the destination with it, so the recovery that reinstalls the route to that peer ITSELF (from the peer’s live session — current evidence, not a saved record) observes absence. Declining when ANY entry exists is the same discipline as the token check: presence means another writer has spoken since the observation, and its evidence is newer.

Source

pub fn sweep_stale(&self, max_age: Duration) -> usize

Drop every candidate older than max_age. Returns the number of DESTINATIONS left without one (what route_count stops counting).

Called periodically from the heartbeat loop to keep dead routes out of the table.

A destination emptied here leaves the table with its last candidate: absence means “no current evidence”. A conditional writer whose observation predates the sweep declines — the observation no longer resolves — and can never be wrongly admitted, because a destination re-created by fresh evidence draws a fresh never-reused token.

Source

pub fn set_max_route_age(&self, age: Duration)

Configure the maximum route age for lookup staleness checks.

Defaults to Duration::MAX (effectively disabled). MeshNode sets this to 3 × session_timeout at construction.

Source

pub fn is_local(&self, dest_id: u64) -> bool

Check if destination is local

Source

pub fn get_stream_stats( &self, stream_id: u64, ) -> Option<Ref<'_, u64, SchedulerStreamStats>>

Get stream stats, creating the entry if absent.

Shares the MAX_STREAM_STATS admission gate with the record_* methods: an existing entry is always returned, but a novel stream_id is only created (and returned) while the map is below the cap, returning None once it’s reached. Without this gate, get_stream_stats was an unbounded-growth hole — it inserted a fresh entry for any id regardless of the cap the record_* path enforces.

Source

pub fn record_in(&self, stream_id: u64, bytes: u64)

Record incoming packet for stream

Source

pub fn record_out(&self, stream_id: u64, bytes: u64)

Record outgoing packet for stream

Source

pub fn record_drop(&self, stream_id: u64)

Record dropped packet for stream

Source

pub fn route_count(&self) -> usize

Get number of routes

Source

pub fn stream_count(&self) -> usize

Get number of active streams

Source

pub fn deactivate_route(&self, dest_id: u64)

Mark a destination’s candidates inactive (on failure)

Source

pub fn activate_route(&self, dest_id: u64)

Reactivate a destination’s candidates

Source

pub fn all_routes(&self) -> Vec<(u64, RouteEntry)>

Get the effective route per destination (for debugging/stats).

Source

pub fn all_route_candidates(&self) -> Vec<(u64, RouteEntry)>

Every candidate, both provenances, per destination.

Callers deciding whether a destination is AFFECTED by an event (a peer failing, say) must consider both slots: a destination can ride through the subject peer on either candidate, and looking only at the effective one would silently miss the other.

Source

pub fn cleanup_idle_streams(&self, idle_nanos: u64) -> usize

Clean up idle streams (no activity for given duration)

Source

pub fn aggregate_stats(&self) -> AggregateStats

Get aggregate stats

Trait Implementations§

Source§

impl Debug for RoutingTable

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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.

Source§

impl<T> Instrument for T

Source§

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

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

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> Same for T

Source§

type Output = T

Should always be Self
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.
Source§

impl<T> WithSubscriber for T

Source§

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
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

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