Documentation
¶
Overview ¶
Package router pkg/router/bottleneck.go c2-net-routing
Package router pkg/router/cascade_ack_registry.go c2-net-routing
ackRegistry is the shared correlation table that maps a cascade sessionID to the goroutine waiting for that session's CascadeAck.
It is shared between the CascadeHandler (which receives ACK packets off the transport manager's single cascade-handler slot and dispatches them) and any CascadeBuilder that injects cascades on the same visor. On the SOURCE visor, the source-side CascadeBuilder sends reserve/install cascades down the route's transports, but the ACKs come back on those same transports and are delivered to the visor's one registered cascade handler. Sharing this registry lets the handler hand those ACKs to the waiting builder instead of needing a second handler slot.
Package router pkg/router/cascade_builder.go c2-net-routing
CascadeBuilder constructs nested cascade messages for the RSN. It builds the Russian-doll structure from last hop to first, signs each layer for its target visor, and sends the cascade to the first hop.
Package router pkg/router/cascade_handler.go c2-net-routing
CascadeHandler processes cascade protocol messages on a visor. It handles CascadeSetupPacket and CascadeAckPacket arriving on route ID 0 of any transport.
Package router pkg/router/cascade_relay.go c2-net-routing
RSN relay: allows visors to reach the Route Setup Node through transport neighbors that have a direct "setup"-labeled transport to the RSN. The relay uses route ID 0 CascadeSetup/CascadeAck packets, piggybacking on the existing cascade infrastructure.
The relay mechanism works as follows: 1. Visor discovers RSN relay peers (cached from previous RSN responses) 2. Visor checks if it transports any relay peer 3. If yes, sends the setup RPC request to that peer on route ID 0 4. The relay peer forwards to the RSN over its "setup" transport 5. Response flows back the same path
This is Phase 2 of the cascade protocol spec.
Package router pkg/router/cascade_route.go c2-net-routing
createRouteGroupCascade implements the two-phase cascade protocol for route setup: reserve IDs from each hop, then install rules. Falls back to DMSG-based setup on failure.
Package router pkg/router/cascade_source.go c2-net-routing
Source-driven cascade orchestration.
In the source-driven design the RSN is a pure dmsg-reachable SIGNING ORACLE: it signs the per-hop reserve/install cascades but never dials hops and is never on the data path. The SOURCE (the requesting visor) drives the cascade down its OWN transports:
- Source -> RSN (RPC): CascadeSignReserve -> signed reserve cascade bytes + session IDs for forward and reverse.
- Source injects the reserve cascades into Forward[0].TpID / Reverse[0].TpID via its own CascadeBuilder.SendCascade and collects the reserved route-ID ACKs.
- Source -> RSN (RPC): CascadeSignInstall (route + session IDs + collected route IDs) -> signed install cascade bytes + the initiating EdgeRules. The RSN recomputes the rules deterministically; it does not trust source-supplied rules.
- Source injects the install cascades via SendCascade. Done.
Package router pkg/router/cascade_source_iface.go c2-net-routing
Package router pkg/router/client_pool.go c2-net-routing
ClientPool keeps reusable RPC connections to remote visors so the route setup node doesn't pay a full DMSG noise handshake on every route setup request. Connections are keyed by remote PK and evicted after an idle TTL.
Package router pkg/router/datagram_keys.go c2-net-routing faithful-UDP per-datagram AEAD master key from a route's Noise session. Stage 4b of #2607.
The datagram path deliberately has no stream-Noise channel of its own (per-datagram AEAD replaces the continuous keystream). Rather than run a second handshake, the datagram sibling of a route keys itself off the reliable route's *already-completed* Noise session: both peers take the session's ChannelBinding (the Noise handshake hash — identical on both ends, bound to the full transcript incl. any PQ-hybrid payload) and HKDF-expand it into a shared 32-byte master key. NewDatagramCipher then expands that master, per direction, into the actual ChaCha20-Poly1305 subkeys.
Domain separation: this derivation uses info label "skywire-datagram-master-v1", distinct from the per-direction labels NewDatagramCipher uses ("skywire-datagram-v1-init/-resp"), so the master and the direction subkeys can never collide even though both HKDF off related material.
Package router pkg/router/datagram_setup.go c2-net-routing construction of the faithful-UDP DatagramRouteGroup sibling. Stage 4b of #2607.
A datagram route is NOT a separate route. The reliable route is set up exactly as usual (saveRouteGroupRules: rules + transport + one-time Noise KK handshake → RouteGroup in rgsNs). When BOTH ends locally intend datagram mode for the route, each additionally builds a DatagramRouteGroup over the *same* forward/reverse rules and transport, keyed off the reliable session's ChannelBinding — no second handshake. DataPackets then flow through the reliable group, DatagramPackets through the sibling, dispatched by packet type (router_packet.go).
"Both ends locally intend it" (on-demand-by-local-intent, not a wire negotiation):
- dial side: DialOptions.Datagram (the DialPacket / forwarded-UDP dialer sets it);
- accept side: RegisterDatagramPort marks the local serving port (a forwarded_ports.udp port, or a packet-mode app listener).
If only one side intends it, that side's sibling exists but the peer drops its DatagramPackets — no reliable traffic is affected.
Package router pkg/router/dead_route_cache.go
Package router delivery_crc.go: the in-mux DELIVERY check (CapDeliveryCRC).
Framing. When both edges negotiate CapDeliveryCRC, a sequenced DATA frame's payload on the wire is
app_payload ‖ crc32c(seq_be32 ‖ app_payload) (4 bytes, big-endian)
The stamp is applied in wrapPayload BEFORE the per-frame AEAD seal, so the trailer rides inside the ciphertext and a SACK retransmit or an FEC reconstruction of the same sequence reproduces byte-identical frames. The receiver verifies and strips the trailer in deliverData at DELIVERY time — after reordering, on the in-order path — not on receipt.
Why here and not at the wire: per-frame AEAD already authenticates ONE frame as it arrives. What nothing checked before is that the bytes handed to the application after reassembly are the bytes the sender wrote, in that order — a reorder/flush defect (#4005) stayed invisible until an application hash failed. Binding the CRC to the sequence number means a frame delivered at the wrong position fails the check, not just a frame whose bytes were mangled.
Package router — in-flight first-hop reservations for diversify dials.
siblingRouteGroupExclusions answers "which first hops are taken?" from the route groups that already EXIST. That is blind to the dials still in flight, and a standby-pool fill is exactly the workload that runs several at once (setup.fill_inflight = 8, tunnel.audition_parallel = 3): at startup eight tunnels dialed inside one second each saw an empty exclusion set and each picked the same lowest-latency direct transport, so the pool came up eight deep on ONE first hop (#5125).
A dial therefore CLAIMS its first hop — transport, peer and remote IP — for the duration of the setup, and a sibling dial to the same exit treats the claim exactly like a first hop a live route group holds. The claim is released when the dial returns, by which time its route group is registered and siblingRouteGroupExclusions can see it for itself.
Package router pkg/router/dial_hook.go c2-net-routing routing policy integration point. The router calls a configured DialHook before each DialRoutes invocation; the hook can adjust the per-dial knobs (MuxRoutes, MinHops) or refuse the dial.
The hook is optional. With no hook configured (Config.DialHook is nil), DialRoutes behaves identically to its pre-integration shape. This keeps the integration zero-cost when no operator policy is in play.
The hook is failure-safe: any error from BeforeDial is treated as "no adjustment" and dialing proceeds with the caller's original opts. A buggy or panicking policy cannot break dialing.
Package router pkg/router/dial_tunnel_legs.go c2-net-routing
Multi-leg tunnels AT DIAL TIME.
An app tunnel (skysocks-client's `--tunnels N`, `--routed`) asks for MuxRoutes=1 — "form a route group, even a single-route one" — and its legs are bolted on afterwards, either by the operator (`proxy mux add`) or by the background self-heal growing toward the adaptive width. That costs the first seconds of every session: the tunnel carries on one leg while the others are still being planned and set up.
The leg count is a VISOR-side decision (the mux width lives in the router's adaptive preset, not in the app process, so the app cannot read it), which is why the raise happens here rather than in the app's dial request.
Package router pkg/router/direct_setup.go c2-net-routing
Package router pkg/router/direct_setup_rpc_native.go c2-net-routing
Package router pkg/router/dmsg_wrapper.go c2-net-routing
Package router pkg/router/fec.go — forward-error-correction primitive for the mux data plane (skycoin/skywire #4270). Aggregating one ordered stream across heterogeneous-RTT legs is head-of-line-bound: the no-skip reorder frontier waits for whatever frames were striped onto a slow leg. Retransmit recovery is coupled to that slow leg's RTT. Erasure coding removes the wait: the receiver reconstructs a missing/late data symbol from REPAIR symbols that arrived on the FAST legs, so recovery delay is independent of the slow leg (the escape route (c) from the in-order-stream wall; see the research survey in memory).
This file is the coding core only — a systematic Cauchy-Reed-Solomon block erasure code over GF(2^8): K data symbols are transmitted unchanged (systematic → zero decode cost when nothing is lost) plus R repair symbols; the receiver recovers ALL K data symbols from ANY K of the K+R (MDS). Integration into the mux striping / reorder buffer is a separate step; keeping the code standalone makes it exhaustively unit-testable without the live mesh.
Package router pkg/router/fec_flush.go — idle-flush policy for the packet-mux FEC tail (fec_mux.go, #4270).
THE WEAKNESS THIS CLOSES. Fixed-block FEC only emits repair when a block fills to K data frames (fecStriper.Add). On a unidirectional bulk stream the LAST block is almost always partial: after the final j<K frames the sender goes idle, so those j frames get NO repair, and a tail frame striped onto a slow leg stalls the no-skip reorder frontier with no FEC rescue — the one documented failure mode of fixed-block FEC on a bulk transfer.
THE FIX (adapted to a systematic block code with ZERO wire change). When the sender has been idle for T with a partial block pending, the route-group send loop (RouteGroup.fecFlushServiceFn) completes the block by emitting the K-j remaining slots as REAL empty PADDING frames through the normal send path. Each advances writeSeq and the striper, and the K-th completes the block so Add emits the repair as at any block boundary. The padding frames are real sealed frames (encoder/decoder agree in the wire domain), they fill the seq gap the no-skip reorder would otherwise stall on, and the receiver delivers them as 0-byte reads that the mux delivery loop filters out of the app stream (the app never writes an empty frame — Write rejects len==0). So the tail block becomes a genuine K-full MDS block with no new frame type and no receiver change.
This file holds the pure, deterministic policy the send loop consults; the emission itself lives in route_group.go (which owns leg selection).
Package router pkg/router/fec_mux.go — wiring the systematic Cauchy-RS erasure core (fec.go, #4270) into the packet-mux striping / reorder data plane.
The aggregation wall is head-of-line blocking: the no-skip reorder frontier (reorder.go) cannot advance past a sequence number striped onto a slow leg, so one ordered stream over heterogeneous-RTT legs is capped at the SLOW leg's rate — retransmit recovery is likewise bound to that leg's RTT. Erasure coding removes the wait: the sender groups K consecutive data frames into a block and emits R repair frames; if a data frame is late/lost on a slow leg but ANY K of the block's K+R symbols have arrived (repair frames ride the FAST legs), the receiver RECONSTRUCTS the missing frame and the frontier advances at the fast leg's rate instead of the slow one.
Layering: FEC operates in the WIRE (post-seal) domain. On send the striper is fed the on-wire frame payload AFTER per-frame seal (route_mux.wrapPayload); on receive the reassembler is fed the on-wire payload BEFORE open (route_mux.deliverData), and a reconstructed on-wire frame is passed through the normal open() path before entering the reorder buffer. Because repair symbols are linear combinations of already-sealed frames they reveal nothing beyond the ciphertext, so no separate repair-frame nonce is needed and the code works uniformly whether per-frame noise is on or off. This file is transport-agnostic and driven entirely by (seq, wireBytes) and (blockID, idx, symbol) tuples, so it is exhaustively unit-testable without the live mesh (see fec_mux_test.go).
Package router pkg/router/hol_retx.go c2-net-routing
Package router pkg/router/id_reserver.go c2-net-routing
Package router pkg/router/leg_adopt.go c2-net-routing
Leg ADOPTION — the last move of the shape axis: a LEG RESERVE becomes a stream-level tunnel again.
A split (leg_split.go) hands a packet-level leg back to the pool as a standalone group, and re-home (leg_rehome.go) can take it as a leg again. But the reserve has no app session and no end-to-end handshake, so it could never carry a stream: a chain that was ever split out stayed packet-level for good. Adoption closes that loop. The dialing app asks for a new tunnel naming the reserve (DialOptions.AdoptReservePort); both edges re-point the reserve's one consume rule at the new tunnel's descriptor — the same one-rule rewrite as re-home and split, on the same capability and the same leg control dialect, with one more flag (routing.LegRehomeAdopt) — and the chain is then set up EXACTLY like a freshly dialed route group from that point on: saveRouteGroupRules runs the handshake (initiator here, responder at the exit), the exit's copy goes through AcceptRoutes to whichever app listens on the service port, and the dialing app gets its conn from DialRoutes.
The descriptor is the dial's own, so the local port is the fresh one the app's dial reserved — the reserve's local port is shared with the tunnel it was split out of and could not be reused.
An exit that never learned the flag reads the request as a re-home naming a group it does not hold and refuses it; one that cannot be reached never answers. Either way the reserve is left exactly as it was and the app dials the way it always did.
Package router pkg/router/leg_hops_match.go c2-net-routing
One hop length per packet-level group (leg.hops_match).
A packet-level group stripes every frame across its legs, so its legs should be interchangeable: a direct transport to the exit next to a two-hop chain reorders against it on every frame, fails over to a path with a different shape, and hands the direct leg's observer the exit relationship the chain was built to hide. Every path that adds a leg on its own — the pool arbiter, the shape converger, the pool plan, the route-finder grow, the dial-time and rotation aux legs — asks legHopsMatch first. An operator's explicit `mux add` is not gated.
Package router pkg/router/leg_rehome.go c2-net-routing
Leg RE-HOME: an ACTIVE route group adopts the already-built route chain of a STANDBY route group as one of its packet-level mux legs, in place, with no setup-node dial.
The chain's intermediate rules are pure routeID -> (routeID, transport) switches and carry no descriptor (routing.IntermediaryForwardRule), so they never learn the chain changed hands. What binds a chain to one route group is its single ConsumeRule at each edge, whose descriptor selects the group (router_packet.go: desc := rule.RouteDescriptor() -> r.rgsNs[desc]). Re-home REWRITES that one rule at each edge — same key route ID, new descriptor — and moves the leg object (its transport, its two rules, its recorded hops) into the target group's mux. The standby group is then legless and closes.
This is the incremental half of the shared-trunk end state in docs/design/shared-warm-route-pool.md Phase 3: one chain still terminates in one consume rule, it just changes owner, so no demux is needed at the exit. It takes the capability bit that doc reserved — routing.CapLegRehome.
See docs/design/leg-rehome.md for the packet, both sequences and the in-flight-frame rules.
Package router pkg/router/leg_rtt_window.go c2-net-routing
Package router pkg/router/leg_split.go c2-net-routing
Leg SPLIT — the reverse of leg re-home (leg_rehome.go).
Re-home moves a whole standby CHAIN into an active group as one more packet-level mux leg. A split moves it back out: the leg leaves the group and becomes a standalone STANDBY route group again, with its own ports and its own consume rule at each edge, keeping the same transport and the same reserved route IDs at every hop. Nothing is dialed, nothing is closed.
It is the same rewrite in the other direction — one consume rule per edge re-pointed at a different descriptor — so it rides the SAME packet type with one more flag, routing.LegRehomeSplit, and the same capability bit (routing.CapLegRehome). An exit that never negotiated it simply never acks, and the caller falls back to what the release path did before: closing the transport.
Why it matters: a route has to be able to change from stream level to packet level and back trivially. Before this, the pool arbiter's release (pool_arbiter.go releaseLegByTransport) CLOSED a leg it had taken from the standby pool, so every load episode cost the app a fresh setup dial to refill the pool. A split returns the chain to the pool intact, ready to be taken again by re-home.
Package router pkg/router/local_route_bfs.go c2-net-routing
The breadth-first search behind calculateLocalRoutes' multi-hop fallback: given the visor's own first-hop transports and one snapshot of the network-wide transport graph, find the shortest path to dst whose length lands in [minHops, maxHops].
It lives in its own file because it is the single most expensive thing the router does per dial — on a visor with no direct transport to the destination it walks the whole ~16k-edge TPD graph — and it needs to be benchmarkable in isolation (see local_route_bfs_test.go).
Package router pkg/router/map.go c2-net-routing
Package router pkg/router/mux_control_frame.go c2-net-routing
IN-BAND leg control: the re-home and split request/ack/refusal/commit (leg_rehome.go, leg_split.go) carried as a mux control frame inside an ordinary routing.DataPacket, so it survives ANY relay.
The problem it solves. A relay forwards routing.LegRehomePacket only if its build knows the type (router_packet.go). Live, every re-home whose leg went through an unpinned fleet relay timed out, while the same request on a leg whose transport reached the exit directly was acked every time. Relays cannot be upgraded on demand and the fleet is heterogeneous by design. A DataPacket, though, is forwarded by route ID with the payload untouched — so the control message rides one, and no relay needs to change.
Where it sits relative to the data sequence space. A control frame is NOT application data and must never touch the reorder/SACK machinery: the mux carries a reliable ordered byte stream whose frontier may not be advanced or gapped by anything the app did not write. So a control frame takes its sequence number from a RESERVED high band (muxCtrlSeqBase and up, its own counter), and the receiver dispatches it in handlePacketNow — BEFORE handleDataPacket, the reorder buffer, the SACK tracker and the delivery CRC. It therefore cannot advance the frontier, cannot appear in a SACK, cannot be counted as a reorder drop, and is not retransmit-buffered: a lost control frame is recovered by the re-home ack timeout, exactly as a lost LegRehomePacket was.
Integrity. Identical to a data frame's: per-frame AEAD under the frame's own sequence-nonce when CapPerFrameNoise is negotiated, and a CRC32C over (seq ‖ body) when it is not — the same stamp delivery_crc.go applies, so an in-band frame is never less protected than the raw packet it replaces (which had no integrity check of its own at all).
Which mux seals a reply. A frame is opened by the group that owns the chain's consume rule AT THE RECEIVER when it lands, and per-frame keys are per route group — so a reply must be sealed by the group that owns the chain at the PEER, which during a move is not always the group that ends up with it locally. See replyRehomeOn's callers.
Package router mux_counters.go: cumulative, process-lifetime tallies of mux state transitions whose only other record is the bounded MuxEvent ring (mux_events.go), which drops old entries under sustained churn. Observability only — nothing here is read by control flow, and adding a count never changes what the router decides.
Package router pkg/router/mux_events.go c2-net-routing mux_events.go holds the router's bounded history of route-group and mux-leg changes.
Package router pkg/router/mux_scheduler.go c2-net-routing
Package router pkg/router/mux_shape.go c2-net-routing
The shape vocabulary. A SESSION is one (app, exit) pair; it owns k ACTIVE tunnels holding n_1..n_k legs, and that (k; n_1…n_k) is its SHAPE — "4x1" pure stream level (four tunnels, no packet striping), "1x4" pure packet level, "2x2" today's default (docs/design/mux-shape-axis.md).
Read-only as shipped: this file MEASURES the shape a session has and reports the shape mux.shape asks for. Nothing here moves a chain; the converger is a later step, and until it lands the target is advisory.
Package router pkg/router/mux_shape_converge.go c2-net-routing
The shape CONVERGER — the half of docs/design/mux-shape-axis.md that moves.
mux_shape.go measures a session's shape and reports the one mux.shape asks for. This file closes the gap between them, one move per arbiter tick, using only the four primitives that CONSERVE the session's chain budget C = Σ n_i + |pool|:
standby -> leg compose leg_rehome.go rehomeChain (GrowMuxFromPool
fallback for an exit without CapLegRehome)
leg -> standby decompose leg_split.go splitLeg
standby -> tunnel promote the role label flips to active
tunnel -> standby park the role label flips to standby, AFTER the
tunnel has been decomposed to one leg
Nothing here dials and nothing here closes. A move that cannot be made is DECLINED with the invariant that declined it, never forced.
mux.shape=auto is not converged at all: auto IS today's arbiter, so shapeStep hands the session straight back to poolArbiterStepExcluding and the default behavior is bit-for-bit what develop does. Only an operator who names a shape takes this path.
A tunnel this file PROMOTES carries no yamux session of its own: that session belongs to the dialing app. MuxInfo.ShapeTunnels publishes the target k and the app's reconcileActiveSet (pkg/skysocks/client_live_ops.go) follows it, promoting one of ITS standby tunnels per tunnel the shape asks for, so the streams spread as wide as the shape says. The one case still out of reach is a chain this file DECOMPOSED into a fresh standby group: the app never dialed it, so it has no session to promote.
Two holds stop the moves below. leg.split_on_release gates every decompose, and mux.shape_hold gates the whole step — the visor sets it for an app whose pool.freeze or tunnel.freeze_active is on (I8), which is how an app-process freeze reaches a package that cannot read the app's knobs.
Package router pkg/router/mux_shape_retry.go c2-net-routing
The shape converger's RETRY BACKOFF, and the leg it picks to move.
A re-home or a split that the exit never acknowledges is not a transient: the frame is being dropped by a hop that will still be dropping it in five seconds. Without a memory the converger asked the same dead leg again every arbiter tick, and because splitLeg QUIESCES the leg before it tells the exit (leg_split.go), the tunnel stopped striping over that chain for the whole leg.rehome_ack_timeout of every tick — measured live on 2026-09-23 as a 30 MB download taking 527 s against ~55 s in a shape that was not converging.
So a leg that fails to ack is remembered BY TRANSPORT and left alone for a backoff that doubles from shape.retry_backoff up to shapeRetryCeilingFactor times it, and the converger prefers a leg that has not failed — the direct leg to the exit first, since a transited split is the one a fleet intermediate silently drops.
Package router pkg/router/network_stats.go c2-net-routing
Package router pkg/router/noise_route_group.go c2-net-routing
Package router pkg/router/oracle_plan_cache.go c2-net-routing
ONE destination-transport query per fill, and N distinct paths out of it.
The RSN-oracle already computes the whole candidate set: rsn_oracle_routes.go intersects this visor's own live transports with the destination's own transports (fetched authoritatively through an RSN-signed query) and ranks every shared intermediate. On the campaign rig that intersection is 274 common intermediates — the local visor holds transports to 349 peers, the exit to 576 — and computeDisjoint2HopRoutes returns all 274, best first.
Then oracle2HopRoutes threw 273 of them away and returned legs[0], because its caller wanted one route. So a standby-pool fill of eight tunnels paid eight signed transport queries (a setup-node round trip plus a dmsg-direct delivery to the exit, capped at 14 s each) to compute the same 274-element set eight times — and, since a dial's exclusions come from SIBLING route groups that do not exist yet when the dials are concurrent, every one of them would have ranked the same intermediate first.
This cache fixes both halves:
- legsFor is a singleflight with a TTL (warm.plan_ttl). N concurrent dials to one exit make ONE query; the rest wait on it and read the same set.
- claim hands each caller the best candidate no sibling has taken, holding that claim for setup.plan_claim_ttl. N concurrent dials therefore take N DISTINCT intermediates without needing to see each other's route groups.
A claim is advisory. It expires on its own, it is never a promise that the dial succeeded, and a caller that finds every candidate claimed falls back to the best one — the previous behavior, never worse.
Package router pkg/router/parallel_route_config.go c2-net-routing
This file holds the pieces of the parallel-route-setup feature that must compile for EVERY build target (including the plain-tinygo edge whose router.go still calls newSuspectHopCache and whose SetDefaults reads the K bounds): the config bounds and the minimal suspect-hop penalty cache. The race machinery itself (which references the route-setup dialer, NoiseRouteGroup, route-finder, etc.) lives in parallel_route_setup.go behind the non-tinygo build tag. Keep this file free of route-setup-only symbols.
Package router pkg/router/parallel_route_setup.go c2-net-routing
parallel_route_setup.go implements PARALLEL candidate route-group setup: the steady-connection fix. Instead of the old one-candidate-at-a-time retry loop — where every dead / non-forwarding candidate burned the full handshakeAwaitTimeout (~10s) before the next was tried, so a run of bad candidates could burn the whole ~90s dial ceiling and wedge the app in "starting" — DialRoutes now sets up the top-K candidate routes CONCURRENTLY and the FIRST to complete its reciprocal handshake WINS and becomes the working route. Losing / failed candidates are canceled, torn down, and their intermediate hops marked suspect for a short TTL so the NEXT dial front-loads known-good hops.
Data-model constraint (why the two phases below): incoming handshake / data packets are demuxed to a route group by RouteDescriptor (see dispatchToRouteGroup), and every candidate of one dial shares the SAME descriptor (same src/dst/ports). The router therefore holds at most ONE raw route group per descriptor (r.rgsRaw[desc]), so two candidates cannot run their reciprocal handshakes concurrently — the second would collide on the descriptor. The race is consequently two-phase:
phase 1 (CONCURRENT): dial/reserve route IDs across every candidate's hops.
This is where an unreachable intermediate fails id_reservation and loses
the race cheaply, instead of blocking the whole dial. This is the
dominant live failure mode (dead/non-forwarding intermediate).
phase 2 (SERIAL, fastest-reservation-first): complete the reciprocal
handshake on reserved candidates; the FIRST to complete wins and is
returned immediately (it is the never-dropped working base — additional
mux legs grow on top later and must never unseat it). Because the
candidates are already reserved, walking to the next one on a handshake
failure costs no re-fetch and no re-reservation — unlike the old loop.
Package router pkg/router/park_hysteresis.go c2-net-routing
Package router pkg/router/pk_sort.go c2-net-routing
Package router pkg/router/pool_arbiter.go c2-net-routing
The standby POOL ARBITER — a pooled tunnel is a stream reserve AND a packet-level mux leg, decided at runtime rather than at dial time.
The proxy's standby pool (pkg/skysocks/client.go) holds tunnels open, pings them and ranks them so a stream that needs a route does not wait for a setup dial. Each one is a whole route group with ONE leg. Two facts follow:
A standby tunnel must stay single-leg. Widening one costs a second route chain to the same exit for a tunnel that is carrying nothing, and the exit pays for it: a rig run with the visor mux width at 2 grew a second leg on every one of 32 pooled tunnels through the self-heal top-up — 64 legs to one exit, 57 leg_parked, 23 reorder wedges, truncated downloads and +67 MiB of exit RSS. So width, dial.tunnel_legs and self-heal widening are ACTIVE-tunnel powers; see poolWideningAllowed.
The idle chains in that pool are exactly what a LOADED active tunnel wants. When the fan-out load signal engages on an active group and its leg count is under the mux width, this file takes the best pooled tunnel and makes it a leg — by re-home (leg_rehome.go: the chain changes owner in place, no dial) or, against an exit that never negotiated CapLegRehome, by growing on the pool's own plan (pool_legs.go). When the load goes away the leg is released and the app's fill tops the pool back up.
With pool.compose_idle (the default), the width is not a load response at all: an ACTIVE tunnel is held at pool.active_width legs whenever it is below it, idle included, and is never released below that width. The packet-level mux is here for privacy and for surviving a cut leg, and a spare that only appears after the load latches cannot survive a cut that happens before it — so the striping cost of an idle second chain is accepted. Legs taken ABOVE the idle width under load still go back after pool.leg_release.
The hysteresis is what keeps this from becoming churn: the load must have LATCHED (the forward fan-out's own latch, or a continuous transfer episode of at least unidir.fanout_engage in the reverse direction), at most one leg per pool.leg_interval, never below pool.min_standby tunnels left in the pool, and a leg is released only after pool.leg_release with no load at all.
Stream promotion always wins. The arbiter only ever considers a sibling the APP still labels standby — the promoter re-stamps its pick "active" through NoteTunnelEvent before it puts a stream on it, so a tunnel the promoter has just taken is invisible here by the time the arbiter next ticks.
Package router pkg/router/pool_legs.go c2-net-routing
POOL-SOURCED MUX LEGS — grow an active tunnel on the routes its own STANDBY tunnels already proved.
A multi-tunnel proxy (skysocks-client's standby pool, skyenv. SkysocksClientStandbyPool) holds one tunnel per disjoint route to the exit, up to pool.size_cap. Each one is a whole route group with a SINGLE leg (the app dials MuxRoutes=1), already ranked by the promoter and already carrying a live first-hop transport. When the ACTIVE tunnel wants a second packet-level mux leg, today it discovers one from scratch: a route-finder round trip plus the disjointness BFS, per leg, per group — while a dozen ready, measured, disjoint paths to that same exit are sitting in the pool.
This file makes the pool the FIRST source of aux-leg plans. It does not — and cannot — hand a standby tunnel's RULES to the active group: a chain's consume rule at each end binds it to one route descriptor, so rules are never shared between groups (docs/design/shared-warm-route-pool.md). What is shared is the PLAN: the forward+reverse hop sequence, which is group-independent. Seeding those plans into the visor-level warmRoutePool means addOneAuxLeg — and the grow operation below — dial their own rule chain onto a known-good route with NO route-finder call at all.
The governing rule from the same design doc's last section: a pool entry is a (transport, route_id) logical route, and legs MAY share a transport with a standby tunnel; what must never be double-used is the ACTIVE set's binding bottleneck. So a plan whose first hop the target group already holds is skipped here, before it can reach the cache — two legs on one first hop are one link's capacity wearing two route IDs.
Ranking is by the standby tunnel's MEASURED end-to-end route latency (the number a routing policy judges a leg by — MuxLeg.RouteLatencyMS / legEndToEndLatencyMs), with the first hop's observed throughput prior (transport.Entry.ThroughputBps via ManagedTransport.GetThroughputBps, the same signal the dial ranker reads through throughputFor) as the secondary key. A pool tunnel has been pinged and used; that is strictly better information than the route-finder's topology-only estimate.
Settlement: the app's own pool settlement marks (poolSettledAt / poolSettledN, pkg/skysocks/client.go) never cross into the visor — the router sees tunnels, not the app's pool state machine. So there is nothing to wait for: the seed is taken LAZILY on each grow, off whatever standby tunnels exist at that instant. An empty or unsettled pool seeds nothing and every path below falls through to today's route-finder dial, unchanged.
Package router pkg/router/rack_tlp.go c2-net-routing
Package router pkg/router/reorder.go c2-net-routing
Package router pkg/router/route_group.go c2-net-routing
Package router pkg/router/route_group_close.go c2-net-routing
Package router pkg/router/route_group_dialer.go c2-net-routing
Package router pkg/router/route_group_hops.go c2-net-routing
Package router pkg/router/route_group_intake.go c2-net-routing
Package router pkg/router/route_group_legs.go c2-net-routing
Package router pkg/router/route_group_noise.go c2-net-routing
Package router pkg/router/route_group_ping.go c2-net-routing
Package router pkg/router/route_group_sack.go c2-net-routing
Package router pkg/router/route_group_services.go c2-net-routing
Package router pkg/router/route_group_stats.go c2-net-routing
Package router pkg/router/route_mux.go c2-net-routing
Package router pkg/router/route_mux_ack.go c2-net-routing
Package router pkg/router/route_mux_delivery.go c2-net-routing
Package router pkg/router/route_mux_legs.go c2-net-routing
Package router pkg/router/route_mux_record.go c2-net-routing
Package router pkg/router/route_mux_select.go c2-net-routing
Package router pkg/router/route_mux_window.go c2-net-routing
Package router pkg/router/route_status.go c2-net-routing
Package router pkg/router/router.go c2-net-routing
Package router pkg/router/router_config.go c2-net-routing router_config.go contains setters, config methods, and Close.
Package router pkg/router/router_dial.go c2-net-routing router_dial.go contains route dialing and route finding logic.
Package router pkg/router/router_dial_filter.go c2-net-routing
Package router pkg/router/router_dial_local.go c2-net-routing
Package router pkg/router/router_dial_mux.go c2-net-routing
Package router pkg/router/router_dial_select.go c2-net-routing
Package router pkg/router/router_forward.go c2-net-routing
The TRANSIT write path: what this visor does with a frame it only relays.
forwardPacket used to end in a SYNCHRONOUS tp.WritePacket on the router's single inbound packet loop, with the router's serve context — which never cancels. managedTransport.writeTo takes the transport write lock and only then sets a deadline of one minute, so ONE peer that stopped draining (a stranger routing through us, whose frames are the cheapest thing on the visor to drop) parked the loop that feeds EVERY route group. Measured live on 2026-09-18: a ~30 s all-paths blackout on the local visor, every transport logging "Dropping packet: readCh full for 30s (application not reading)" while the goroutine dump showed the loop in writeTo for a transit peer. The same class as #5018, which took the route-group READ side off this loop with a per-group intake worker; this is the write side.
So: the loop hands the frame to a bounded per-next-hop-transport queue and returns. One writer goroutine per transport drains it — which also preserves the ordering the shared write lock used to give — and each write carries forward.write_timeout as its own deadline. When the peer does not drain, the queue fills and frames are DROPPED with a counted, named reason (forward_drop_queue_full / forward_drop_write_timeout), visible in `visor state --select diag` as intake.forward_queues and, once per forward.drop_event_window per transport, as a forward_drops mux event.
Package router pkg/router/router_gc.go c2-net-routing router_gc.go contains garbage collection for routing rules.
Package router pkg/router/router_intake.go c3-rtr-core
Intake diagnostics for `visor state`: what the router's single inbound packet loop is doing with what the transports hand it. Counted here rather than only logged so a stalled or misdelivered path shows up in a snapshot.
Package router pkg/router/router_leg_controls.go c2-net-routing
Per-LEG operator controls on a live route group, and the negotiated view of the group they act on.
Two of these existed in the data plane with no way to reach them: WeightModeExplicit / SetExplicitWeights (transport_selector.go) had no caller outside the routing-policy distribution, and a FORWARD-ONLY leg (appendRouteAsymmetric addFwd=true/addRev=false) could only be produced by a preset's rotation hook. Both are operator decisions — "put 70% of the send on this leg", "this leg is upload capacity only" — so both get an RPC here and a CLI verb in `skywire cli proxy mux`.
These are OPTIONAL router capabilities: pkg/visor reaches them by asserting this interface on its router rather than widening the Router interface, so a router build without them (tinygo source-only) simply reports the control as unavailable instead of failing to compile.
Package router pkg/router/router_mux_ops.go c2-net-routing router_mux_ops.go contains mux route operations on the router.
Package router pkg/router/router_packet.go c2-net-routing router_packet.go contains all packet handling logic.
Package router pkg/router/router_pending.go c2-net-routing
Package router pkg/router/router_pending_legs.go c2-net-routing
Package router pkg/router/router_route_source.go c3-rtr-core
Where multi-hop routes came from, for `visor state --select diag`: the route finder, the local graph (a hypervisor's attached visors, #4750), or the local fallback after a route-finder miss. Counted so the saving from a local graph is visible in a snapshot rather than only in logs.
Package router pkg/router/router_rules.go c2-net-routing router_rules.go contains rule and route group management logic.
Package router pkg/router/router_serve.go c2-net-routing router_serve.go contains functions for serving and accepting routes.
Package router pkg/router/routerclient.go c2-net-routing
Package router pkg/router/rpc_gateway.go c2-net-routing
Package router pkg/router/rsn_oracle_routes.go c2-net-routing
RSN-oracle 2-hop route computation (phase-1).
Given the source's OWN transports and the destination's OWN transports (fetched authoritatively from the destination via the RSN-signed transport-query, transport_query.go), the set of single-intermediate (2-hop) routes S->I->D is the overlap of the two peer sets: an intermediate I is usable iff S has a live transport S-I AND D has a transport I-D. No TPD snapshot is consulted — the common route type becomes independent of the transport-discovery service's global view.
This is the S-side crux and is deliberately a pure function of its inputs (computeDisjoint2HopRoutes) so it is unit-testable with mock transport sets. The result is a disjoint set of legs (one per distinct intermediate PK) ready to feed the existing mux establishment (establishMuxRoutes consumes ForwardHops/ReverseHops per leg).
Package router pkg/router/rules_map.go c2-net-routing
Package router pkg/router/sack.go c2-net-routing
Package router pkg/router/sbd_trial.go c2-net-routing
The route group's half of "a shared-bottleneck park is a trial": the pending trials, the verified-independent pair suppression, and the unpark a failed trial performs. The decision arithmetic and the constants live in bottleneck.go next to the detector whose ruling they qualify.
Package router pkg/router/service_gate.go c2-net-routing
Suspending a route group's service loops while they cannot do useful work.
A group runs seven knob-paced service loops. Three of them tick ten and two times a second — send-window, tlp, reorder-stall — and on a group that is holding a route open without moving bytes, all three are provable no-ops: tlpProbeSeq bails when the retransmit buffer holds nothing outstanding, reorderStallServiceFn bails when the frontier has no gap, and a send window recomputed from unchanged inputs yields the unchanged window. Two more — legstate-resync and unidir-flip — iterate from leg index 1, so they are no-ops on a single-leg group by construction.
That is ~23 timer wakeups per second per group per end spent proving there is nothing to do. It does not matter at one group; the standby tunnel pool holds dozens, and its ceiling is in the hundreds, where it is most of the visor's idle CPU.
So those five loops take a gate. While the gate says dormant the loop stops its ticker and parks on a broadcast channel, costing nothing but its stack; the write path, the receive path and the leg-append path wake it. The gate is re-evaluated after every wake, so a spurious wake is free.
The loops that are NOT gated are the ones that must fire precisely when nothing is happening: keep-alive, leg-liveness, sack, and leg-dataprogress — whose sole-leg black-hole path exists to notice a leg that sent bytes and delivered none.
Package router pkg/router/settings.go c2-net-routing
Runtime-settable router knobs, so `skywire cli route settings` can move the dataplane on a running visor instead of a rebuild and a fleet deploy.
The values live in pkg/router/routersettings — one catalog entry per knob, each seeded with the constant this package compiled with, so a visor that never sets one behaves byte for byte as it did. This file is the bridge: the named accessors the rest of the package and pkg/visor call, plus the per-route-group resolution of an app's overrides.
Two shapes of read:
- a package-level accessor (DeadRouteHold, SBDMinSamples, …) reads the visor-wide value. Used where there is no route group in hand;
- a *routersettings.View read through rg.knobs() / m.knobs() carries the owning app's overrides. The view is resolved ONCE when the group is built and re-resolved only when the catalog version moves (the group's send-window service tick notices), never per packet.
Package router pkg/router/settings_defaults.go c2-net-routing
The compiled defaults that do not belong to any one mechanism's file, kept as named constants so the catalog has something to be asserted against (TestCatalogDefaultsMatchConstants) and so a reader of `route settings` can find the value the binary was built with.
Package router pkg/router/settings_dial.go c2-net-routing
The DIAL-TIME half of the router's knob bridge: the route-ranking priors, how many candidates a dial asks the finder for, the warm-plan cache shape, the dead-route young-death window, the dial-time tunnel width and the operator's prefer-these-peers list.
Same contract as settings.go, and the SAME catalog: every scalar here is a pkg/router/routersettings entry, so it is set with `route settings k=v`, read back in `route settings --json` under knobs / knob_detail, scoped per app where a dial knows the app, persisted into routing.router_settings and restored at the next start. `route settings dial` is a convenience VIEW over this section of the catalog, not a second mechanism.
It is a separate file from settings.go only to keep the two sets reviewable apart: settings.go holds the send-window/bottleneck/forward knobs that act on a LIVE route group, this one holds the knobs a dial reads while it is picking a route.
The one non-catalog member is the prefer-these-peers LIST: the catalog's payload is an int64 per knob, which a set of public keys is not. It keeps its own atomic here and its own RPC field.
Package router pkg/router/settings_group.go c2-net-routing
Per-route-group resolution of the knob catalog.
`route settings --app <name> k=v` sets an override map for the route groups a given app owns, so a subject client and its paired reference client can run different values on one visor. The app name is the one the visor already knows: the tag saveRouteGroupRules attaches from the appserver proc's dial options (RouteGroup.SetAppName).
Resolution happens ONCE when the group is built, again when the app tag arrives, and thereafter only when the catalog version moves — the group's send-window service loop calls refreshKnobs on its tick, which is the change notification. A read on the data path is one atomic pointer load; nothing resolves per packet.
Package router pkg/router/setup_batch.go c2-net-routing
The setup-node side of the batched route-setup protocol (routing/batch_route.go): install N routes to ONE destination with the per-hop work coalesced.
What coalescing actually removes. A single CreateRouteGroup for a 2-hop route S->I->D does, per route:
3 dials / pool borrows (S, I, D) -> one id-reservation RPC each 1 AddIntermediaryRules RPC on I 1 AddEdgeRules RPC on D
Eight such routes to the same exit over eight different intermediates cost 8 setup requests and 8*5 = 40 router RPCs, of which 16 are the SAME two peers (S and D) asked for route IDs eight times each.
CreateRouteGroupBatch builds ONE IDReserver over every member's forward and reverse hop lists. NewIDReserver already tallies "how many route IDs does this PK owe" across all the paths it is given, so S and D are dialed once and asked once — for all eight routes' worth of IDs in a single RPC. The intermediary-rule broadcast is merged the same way: the rules of every member that traverses a given hop go out in one AddIntermediaryRules call to that hop, so a batch whose members SHARE an intermediate (the common shape once the pool grows past the count of distinct first hops) collapses those too.
The 8-route fill above becomes 1 setup request + 10 id reservations (S, D, I1..I8) + 8 intermediary installs + 8 destination edge installs = 27 RPCs against 48, and the two peers common to every route are dialed twice instead of sixteen times.
Partial success is the contract. A member whose intermediate is unreachable, whose route fails Check, or whose destination edge install is refused comes back with an Error string while its siblings come back installed — and only that member's already-installed rules are torn down, from its own tracker. The only all-or-nothing step is the shared id reservation: if the source or the destination cannot be reached at all there is no batch to run, and every member fails with that one error.
Package router pkg/router/setup_batch_client.go c2-net-routing
The INITIATOR side of batched route setup: collect the sibling dials a multi-route request produces and send them to the setup node as one message.
Why a coalescer and not a "DialRoutesBatch" entry point. Every multi-route dial this visor makes already fans out into concurrent single dials — the standby-pool fill, `--tunnels N`, the mux leg self-heal, a diversify dial's K-race. They arrive at setupNodeDialer.Dial within milliseconds of each other, each carrying its own BidirectionalRoute to the SAME exit. Collecting them here batches every one of those callers without any of them knowing about batching, and without touching DialRoutes' 650 lines of route-group construction, handshake and rule bookkeeping.
The window is paid by the FIRST dial of a burst and by nobody else: a lone dial waits setup.batch_window (40 ms by default) against a setup round trip whose measured p50 is 0.6-2.1 s. A burst of eight pays it once, for all eight.
Negotiation, not a flag. The leader asks the setup node for its capability list once per connection; a node that does not advertise CapBatchRouteSetup (or that predates the Capabilities RPC entirely, answering "can't find method") makes the whole group fall back to today's concurrent single requests. Nothing has to be configured on either side, and a mixed fleet of setup nodes works.
Package router pkg/router/setup_path_stats.go c2-net-routing
Which setup path this visor's routes actually took, on the VISOR side.
The setup node's /stats counts requests by kind, but a visor operator cannot read the setup node's whitelisted view and, more to the point, the question they are asking is about their own dials: did the batched form get used at all, did the oracle query once per fill or once per route, and did the concurrent dials of a fill end up on distinct intermediates. This is that, under `visor state --select diag` as route_setup.
Package router pkg/router/setup_rpc_gateway.go c2-net-routing
Package router pkg/router/setupclient.go c2-net-routing
Package router pkg/router/setupconfig.go c2-net-routing
Package router pkg/router/setupnode.go c2-net-routing
Package router pkg/router/tpd_cache.go c2-net-routing
Package router pkg/router/transport_close.go c2-net-routing
What happens to a route group when a transport underneath it goes away.
A leg is a (transport, forward rule, consume rule) triple, and nothing used to tell a leg that its transport had closed. The transport manager has fired a close hook since the VStream muxes needed one (transport.Manager. OnTransportClosed), but the router never registered: it found out by polling tp.IsClosed() from the keep-alive and leg-liveness services, and both of those step AROUND a closed transport rather than rule on it. legLivenessServiceFn skips a closed leg, so that leg is never probed, never missed and never declared dead; pruneDeadTransports refuses to drop the last leg. A single-leg group whose transport the visor itself removed therefore kept all of its rules and stayed registered, and the session on top only learned of the death when one of its own writes happened to land on the dead transport and error.
On a download that write can be a long time coming: the bytes stop, so the app consumes nothing more, so it emits no window update, so nothing is written, so nothing errors. Three runs of near-identical trees on the standby-pool bench measured the same cut at 0.5 s, 5.3 s and 14.4 s to first byte — the spread was entirely "when does this side next write?", not anything about the failover, which is same-tick once the session errors.
A transport the visor removed is not a silent peer. The liveness probe is for peers that went quiet; this is a link we KNOW is gone, so it is ruled on at the close rather than at a timeout.
Package router pkg/router/transport_list_routes.go c2-net-routing
Package router pkg/router/transport_query.go c2-net-routing
RSN-oracle transport-list query protocol (phase-1).
A source visor S that wants to build a SINGLE-INTERMEDIATE (2-hop) route S->I->D needs only two transport sets: its OWN (known locally) and the destination D's OWN. The intermediate set is their overlap in peer PKs. D's transports are authoritative and fresh from D itself, so a 2-hop route can be computed WITHOUT the transport-discovery service (TPD) — TPD is only needed for routes with >=2 intermediates.
This file defines the query the source carries to the destination, reusing the cascade RSN-signature primitive (cipher.Sign/Verify against a per-visor trusted-RSN allowlist): the setup node (RSN) signs a query CAPABILITY targeting D, and D honors the query only if the RSN is on its trust list and the signature verifies for D's own PK — exactly the authorization model CascadeSetup.Verify already enforces (see cascade.go). The query is a one-shot control-plane message, so it marshals as JSON (the compact-entry response already carries json tags); the cascade's hand-rolled binary form is reserved for the per-hop data-plane-adjacent path.
Package router pkg/router/transport_query_dmsg.go c2-net-routing
dmsg-direct (phase-1) delivery for the RSN-oracle transport-list query.
A source visor S carries an RSN-signed TransportQuery to destination D over the always-available dmsg relay — the same way `tp --remote` / the setup client reach a remote visor's RPC over dmsg (see setupclient.go). D listens on skyenv.DmsgTransportQueryPort, verifies the RSN signature against its trusted-RSN allowlist, and returns its own transport list.
Both halves are served over gob RPC to match the existing setup-node path (a gobrpc server and client), so no new codec is introduced. The visor wires both halves at startup (init_router.go).
Package router pkg/router/transport_selector.go c2-net-routing
Package router pkg/router/unidir.go c2-net-routing
Package router pkg/router/warm_route_pool.go c2-net-routing
Package router pkg/router/wrappers.go c2-net-routing
Index ¶
- Constants
- Variables
- func BroadcastIntermediaryRules(ctx context.Context, log logrus.FieldLogger, rtIDR IDReserver, ...) (err error)
- func CreateRouteGroup(ctx context.Context, dialer network.Dialer, pool *ClientPool, ...) (resp routing.EdgeRules, err error)
- func DeadRouteHold() time.Duration
- func DeadRouteHoldMax() time.Duration
- func DeadRouteYoungAge() time.Duration
- func DeliveryCRCAdvertised() bool
- func DialDiversifyCandidates() int
- func DialForegroundMux() int
- func DialMuxRouteHeadroom() int
- func DialPreferPKs() []cipher.PubKey
- func DialRouteCandidates() int
- func DialThroughputPriorScale() float64
- func DialTunnelLegs() int
- func DialTypePriorScale() float64
- func DialUnknownHopPenaltyMs() float64
- func DialUnknownLatencyCostMs() float64
- func EcfMaxWindowBytes() int64
- func EcfMinWindowBytes() int64
- func EcfWindowMargin() float64
- func FindDirectRSNTransport(rsnPK cipher.PubKey, tm *transport.Manager) *transport.ManagedTransport
- func ForwardSpill() bool
- func ForwardSwitchMargin() float64
- func ForwardSwitchSamples() int
- func HOLRetxAdvertised() bool
- func LegDelivAlpha() float64
- func LegParkMinHold() time.Duration
- func LegProbeBytes() int64
- func LegStarveRatio() float64
- func PerFrameNoiseEnabled() bool
- func PoolBoundRefresh() time.Duration
- func RegisterSetupRPCGateway(rpcSrv *rpc.Server, rpcName string, rpcRcvr *SetupRPCGateway) error
- func ReorderSetupNodes(nodes []cipher.PubKey, successPK cipher.PubKey) []cipher.PubKey
- func SACKAdvertised() bool
- func SBDBackoff() time.Duration
- func SBDDemote() bool
- func SBDEnabled() bool
- func SBDMinEvidenceRate() int64
- func SBDMinSamples() int
- func SBDSampleInterval() time.Duration
- func SBDTrialLoss() float64
- func SBDTrialWindow() time.Duration
- func SendWindowWaitMax() time.Duration
- func ServeTransportQueryListener(ctx context.Context, dmsgC *dmsg.Client, gw *TransportQueryRPCGateway, ...) error
- func SetDeadRouteHold(d time.Duration) bool
- func SetDeadRouteHoldMax(d time.Duration) bool
- func SetDeadRouteYoungAge(d time.Duration) bool
- func SetDialDiversifyCandidates(n int) bool
- func SetDialForegroundMux(n int) bool
- func SetDialMuxRouteHeadroom(n int) bool
- func SetDialPreferPKs(pks []cipher.PubKey)
- func SetDialRouteCandidates(n int) bool
- func SetDialThroughputPriorScale(v float64) bool
- func SetDialTunnelLegs(n int) bool
- func SetDialTypePriorScale(v float64) bool
- func SetDialUnknownHopPenaltyMs(v float64) bool
- func SetDialUnknownLatencyCostMs(v float64) bool
- func SetEcfMaxWindowBytes(v int64) bool
- func SetEcfMinWindowBytes(v int64) bool
- func SetEcfWindowMargin(v float64) bool
- func SetForwardSpill(on bool) bool
- func SetForwardSwitchMargin(v float64) bool
- func SetLegDelivAlpha(v float64) bool
- func SetLegParkMinHold(d time.Duration) bool
- func SetLegProbeBytes(v int64) bool
- func SetLegStarveRatio(v float64) bool
- func SetSBDBackoff(d time.Duration) bool
- func SetSBDDemote(on bool) bool
- func SetSBDEnabled(on bool) bool
- func SetSBDMinEvidenceRate(v int64) bool
- func SetSBDMinSamples(n int) bool
- func SetSBDSampleInterval(d time.Duration) bool
- func SetSBDTrialLoss(v float64) bool
- func SetSBDTrialWindow(d time.Duration) bool
- func SetSendWindowWaitMax(d time.Duration) bool
- func SetSetupBatchMax(n int) bool
- func SetSetupBatchWindow(d time.Duration) bool
- func SetSetupCircuitBreaker(on bool) bool
- func SetSetupCircuitFailThreshold(n int) bool
- func SetSetupCircuitFailWindow(d time.Duration) bool
- func SetSetupCircuitMaxOpenDuration(d time.Duration) bool
- func SetSetupCircuitOpenDuration(d time.Duration) bool
- func SetSetupFillInflight(n int) bool
- func SetSetupFirstHopFilterMax(n int) bool
- func SetSetupPlanClaimTTL(d time.Duration) bool
- func SetWarmPlanBucketCap(n int) bool
- func SetWarmPlanTTL(d time.Duration) bool
- func SetupBatchMax() int
- func SetupBatchWindow() time.Duration
- func SetupCircuitBreaker() bool
- func SetupCircuitFailThreshold() int
- func SetupCircuitFailWindow() time.Duration
- func SetupCircuitMaxOpenDuration() time.Duration
- func SetupCircuitOpenDuration() time.Duration
- func SetupFillInflight() int
- func SetupFirstHopFilterMax() int
- func SetupPlanClaimTTL() time.Duration
- func WarmPlanBucketCap() int
- func WarmPlanTTL() time.Duration
- func WithForceLegacyRouteSetup(ctx context.Context) context.Context
- func WrapDmsgClient(dmsgC *dmsg.Client) network.Dialer
- type BatchSavings
- type CandidateInfo
- type CapabilitiesArgs
- type CapabilitiesReply
- type CascadeBuilder
- func (cb *CascadeBuilder) AckRegistry() *ackRegistry
- func (cb *CascadeBuilder) BuildInstallMessage(hops []routing.Hop, sessionID uint64, ...) ([]byte, error)
- func (cb *CascadeBuilder) BuildReserveMessage(hops []routing.Hop, reserveN uint8) (uint64, []byte, error)
- func (cb *CascadeBuilder) HandleAck(p routing.Packet, _ *transport.ManagedTransport)
- func (cb *CascadeBuilder) SendCascade(ctx context.Context, firstHopTpID uuid.UUID, sessionID uint64, payload []byte, ...) (*routing.CascadeAck, error)
- func (cb *CascadeBuilder) SetTimeouts(reserve, install time.Duration)
- type CascadeConfig
- type CascadeHandler
- type CascadeSignInstallArgs
- type CascadeSignInstallReply
- type CascadeSignReserveArgs
- type CascadeSignReserveReply
- type Client
- func (c *Client) AddEdgeRules(ctx context.Context, rules routing.EdgeRules) (ok bool, err error)
- func (c *Client) AddIntermediaryRules(ctx context.Context, rules []routing.Rule) (ok bool, err error)
- func (c *Client) Close() error
- func (c *Client) DelRules(ctx context.Context, routeIDs []routing.RouteID) (ok bool, err error)
- func (c *Client) ReserveIDs(ctx context.Context, n uint8) (rtIDs []routing.RouteID, err error)
- type ClientPool
- type Config
- type DatagramCipher
- func (dc *DatagramCipher) MaxPlaintext() int
- func (dc *DatagramCipher) NeedsRekey() bool
- func (dc *DatagramCipher) Open(routeID uint32, sealed []byte) ([]byte, error)
- func (dc *DatagramCipher) Rekey(masterKey [32]byte, cfg *DatagramCipherConfig) error
- func (dc *DatagramCipher) Seal(routeID uint32, plaintext []byte) ([]byte, error)
- type DatagramCipherConfig
- type DatagramRouteGroup
- func (dg *DatagramRouteGroup) AEADAuthFailures() uint64
- func (dg *DatagramRouteGroup) AddRule(fwd routing.Rule, tp *transport.ManagedTransport, rv routing.Rule)
- func (dg *DatagramRouteGroup) Close() error
- func (dg *DatagramRouteGroup) Handle(packet routing.Packet) error
- func (dg *DatagramRouteGroup) InboundDropped() uint64
- func (dg *DatagramRouteGroup) IsAlive() bool
- func (dg *DatagramRouteGroup) LocalAddr() net.Addr
- func (dg *DatagramRouteGroup) MaxPayload() int
- func (dg *DatagramRouteGroup) ReadFrom(p []byte) (int, net.Addr, error)
- func (dg *DatagramRouteGroup) RemoteAddr() net.Addr
- func (dg *DatagramRouteGroup) SetCiphers(out, in *DatagramCipher)
- func (dg *DatagramRouteGroup) SetDeadline(t time.Time) error
- func (dg *DatagramRouteGroup) SetReadDeadline(t time.Time) error
- func (dg *DatagramRouteGroup) SetWriteDeadline(t time.Time) error
- func (dg *DatagramRouteGroup) WriteTo(data []byte, addr net.Addr) (int, error)
- type DialAdjustment
- type DialError
- type DialHook
- type DialInfo
- type DialOptions
- type DistributionConfig
- type DistributionMode
- type DstTransportOracle
- type EmbeddedSetupNode
- type ForwardQueue
- type Group
- type HealthCheckArgs
- type HealthCheckReply
- type IDReserver
- type IntakeStats
- type LegChange
- type LegChangeHook
- type LegInfo
- type LegStats
- type LegWeight
- type Map
- type MockIDReserver
- func (_m *MockIDReserver) Client(pk cipher.PubKey) *Client
- func (_m *MockIDReserver) Close() error
- func (_m *MockIDReserver) PopID(pk cipher.PubKey) (routing.RouteID, bool)
- func (_m *MockIDReserver) ReserveIDs(ctx context.Context) error
- func (_m *MockIDReserver) ReturnToPool(_ *ClientPool)
- func (_m *MockIDReserver) String() string
- func (_m *MockIDReserver) TotalIDs() int
- type MockRouteGroupDialer
- type MockRouter
- func (_m *MockRouter) AcceptDatagram(ctx context.Context) (*DatagramRouteGroup, routing.Port, error)
- func (_m *MockRouter) AcceptRoutes(_a0 context.Context) (net.Conn, error)
- func (_m *MockRouter) ActiveRouteStatuses() []RouteStatus
- func (_m *MockRouter) AddMuxRouteByHops(_a0 routing.RouteDescriptor, _a1, _a2 []routing.Hop) error
- func (_m *MockRouter) Close() error
- func (_m *MockRouter) CloseRouteGroupsForApp(_a0 string) int
- func (_m *MockRouter) DelRules(_a0 []routing.RouteID)
- func (_m *MockRouter) DialHook() DialHook
- func (_m *MockRouter) DialRoutes(ctx context.Context, rPK cipher.PubKey, lPort routing.Port, rPort routing.Port, ...) (net.Conn, error)
- func (_m *MockRouter) DialRoutesDatagram(ctx context.Context, rPK cipher.PubKey, lPort routing.Port, rPort routing.Port, ...) (net.Conn, *DatagramRouteGroup, error)
- func (_m *MockRouter) EffectiveMinHops(opts *DialOptions) uint16
- func (_m *MockRouter) GetExistingTPOnly() bool
- func (_m *MockRouter) GetForceLocalRoutes() bool
- func (_m *MockRouter) GetLastRouteCalcTime() time.Duration
- func (_m *MockRouter) GetMuxFEC() bool
- func (_m *MockRouter) GetMuxRoutes() int
- func (_m *MockRouter) GrowMuxFromPool(_a0 routing.Port, _a1, _a2 int) (int, error)
- func (_m *MockRouter) GrowMuxRoute(_a0 routing.RouteDescriptor, _a1, _a2 int) (int, error)
- func (_m *MockRouter) IntroduceRules(rules routing.EdgeRules) error
- func (_m *MockRouter) MuxCounters() MuxCounters
- func (_m *MockRouter) MuxEvents() []MuxEvent
- func (_m *MockRouter) NoteTunnelEvent(localPort routing.Port, event string, reason string, role string) bool
- func (_m *MockRouter) PingRoute(ctx context.Context, rPK cipher.PubKey, lPort routing.Port, rPort routing.Port, ...) (net.Conn, error)
- func (_m *MockRouter) RegisterDatagramPort(port routing.Port)
- func (_m *MockRouter) RemoveMuxRouteByTransport(_a0 routing.RouteDescriptor, _a1 uuid.UUID) error
- func (_m *MockRouter) ReserveKeys(n int) ([]routing.RouteID, error)
- func (_m *MockRouter) RouteGroupHops(_a0 routing.RouteDescriptor) []RouteHopInfo
- func (_m *MockRouter) RouteGroupMuxInfo(_a0 routing.RouteDescriptor) (MuxInfo, bool)
- func (_m *MockRouter) RouteGroupMuxInfoAll() []MuxInfo
- func (_m *MockRouter) RouteGroupMuxInfoForApp(_a0 string) []MuxInfo
- func (_m *MockRouter) RoutesCount() int
- func (_m *MockRouter) RoutingTableStats() routing.RoutingTableStats
- func (_m *MockRouter) Rule(_a0 routing.RouteID) (routing.Rule, error)
- func (_m *MockRouter) Rules() []routing.Rule
- func (_m *MockRouter) SaveRoutingRules(rules ...routing.Rule) error
- func (_m *MockRouter) SaveRule(_a0 routing.Rule) error
- func (_m *MockRouter) Serve(_a0 context.Context) error
- func (_m *MockRouter) SetDstTransportOracle(_a0 DstTransportOracle)
- func (_m *MockRouter) SetExistingTPOnly(_a0 bool)
- func (_m *MockRouter) SetForceLocalRoutes(_a0 bool)
- func (_m *MockRouter) SetMinHop(_a0 uint16)
- func (_m *MockRouter) SetMuxDirectionForApp(_a0 string, _a1 byte) (int, error)
- func (_m *MockRouter) SetMuxFEC(v bool)
- func (_m *MockRouter) SetMuxMode(_a0 WeightMode)
- func (_m *MockRouter) SetupIsTrusted(_a0 cipher.PubKey) bool
- func (_m *MockRouter) UnregisterDatagramPort(port routing.Port)
- type MuxCounters
- type MuxEvent
- type MuxInfo
- type MuxLeg
- type MuxLegController
- type MuxNegotiated
- type MuxRecovery
- type MuxShapeMove
- type MuxWeightsView
- type Node
- type NoiseRouteGroup
- func (nrg *NoiseRouteGroup) BandwidthReceived() uint64
- func (nrg *NoiseRouteGroup) BandwidthSent() uint64
- func (nrg *NoiseRouteGroup) Close() error
- func (nrg *NoiseRouteGroup) DownloadSpeed() uint32
- func (nrg *NoiseRouteGroup) GetError() error
- func (nrg *NoiseRouteGroup) IsAlive() bool
- func (nrg *NoiseRouteGroup) Latency() time.Duration
- func (nrg *NoiseRouteGroup) LocalAddr() net.Addr
- func (nrg *NoiseRouteGroup) RemoteAddr() net.Addr
- func (nrg *NoiseRouteGroup) RouteHopDetails() []RouteHopInfo
- func (nrg *NoiseRouteGroup) RouteHops() []cipher.PubKey
- func (nrg *NoiseRouteGroup) SetError(err error)
- func (nrg *NoiseRouteGroup) SetForwardHops(hops []routing.Hop)
- func (nrg *NoiseRouteGroup) UploadSpeed() uint32
- type RPCGateway
- func (r *RPCGateway) AddEdgeRules(rules routing.EdgeRules, ok *bool) error
- func (r *RPCGateway) AddIntermediaryRules(rules []routing.Rule, ok *bool) error
- func (r *RPCGateway) DelRules(routeIDs []routing.RouteID, ok *bool) error
- func (r *RPCGateway) ReserveIDs(n uint8, routeIDs *[]routing.RouteID) error
- type RSNRelayCache
- func (rc *RSNRelayCache) FindRelayTransport(ctx context.Context, rsnPK cipher.PubKey, tm *transport.Manager) (*transport.ManagedTransport, cipher.PubKey, error)
- func (rc *RSNRelayCache) Get(rsnPK cipher.PubKey) []cipher.PubKey
- func (rc *RSNRelayCache) Update(rsnPK cipher.PubKey, relayPeers []cipher.PubKey)
- type RelayPeersArgs
- type RelayPeersReply
- type ReserveError
- type Role
- type RotationAction
- type RotationHook
- type RouteGroup
- func (rg *RouteGroup) AppName() string
- func (rg *RouteGroup) BandwidthReceived() uint64
- func (rg *RouteGroup) BandwidthSent() uint64
- func (rg *RouteGroup) Close() error
- func (rg *RouteGroup) DownloadSpeed() uint32
- func (rg *RouteGroup) GetError() error
- func (rg *RouteGroup) Initiator() bool
- func (rg *RouteGroup) IsAlive() bool
- func (rg *RouteGroup) Latency() time.Duration
- func (rg *RouteGroup) LocalAddr() net.Addr
- func (rg *RouteGroup) MeasureLatency(ctx context.Context, count int) (min, max, avg float64, err error)
- func (rg *RouteGroup) MuxStats() MuxInfo
- func (rg *RouteGroup) Read(p []byte) (n int, err error)
- func (rg *RouteGroup) RemoteAddr() net.Addr
- func (rg *RouteGroup) RouteHopDetails() []RouteHopInfo
- func (rg *RouteGroup) RouteHops() []cipher.PubKey
- func (rg *RouteGroup) SetAppName(name string)
- func (rg *RouteGroup) SetCloseObserver(fn func(age time.Duration, carried uint64))
- func (rg *RouteGroup) SetDeadline(t time.Time) error
- func (rg *RouteGroup) SetDirectionPin(mode byte) error
- func (rg *RouteGroup) SetError(err error)
- func (rg *RouteGroup) SetForwardHops(hops []routing.Hop)
- func (rg *RouteGroup) SetLegChangeHook(hook LegChangeHook, info DialInfo)
- func (rg *RouteGroup) SetReadDeadline(t time.Time) error
- func (rg *RouteGroup) SetRotation(hook RotationHook, applyAdd, applyAddForward func(excludeHops []string), ...)
- func (rg *RouteGroup) SetSelfHeal(applyAdd func(excludeHops []string), target int)
- func (rg *RouteGroup) SetTunnelRole(role string)
- func (rg *RouteGroup) SetWriteDeadline(t time.Time) error
- func (rg *RouteGroup) TunnelRole() string
- func (rg *RouteGroup) UploadSpeed() uint32
- func (rg *RouteGroup) Write(p []byte) (n int, err error)
- type RouteGroupConfig
- type RouteGroupDialer
- type RouteGroupQueue
- type RouteHopInfo
- type RouteSelectingHook
- type RouteSelection
- type RouteSetupHook
- type RouteSourceStats
- type RouteStatus
- type RouteTransport
- type Router
- type RulesMap
- type SelfHealTargeter
- type SetupClient
- func (c *SetupClient) Capabilities(ctx context.Context) ([]string, error)
- func (c *SetupClient) Close() error
- func (c *SetupClient) ConnectedNode() cipher.PubKey
- func (c *SetupClient) DialRouteGroup(ctx context.Context, req routing.BidirectionalRoute) (routing.EdgeRules, error)
- func (c *SetupClient) DialRouteGroupBatch(ctx context.Context, batch routing.BidirectionalRouteBatch) (routing.BidirectionalRouteBatchReply, error)
- func (c *SetupClient) FetchRelayPeers(ctx context.Context) ([]cipher.PubKey, error)
- func (c *SetupClient) RPCClient() *rpc.Client
- func (c *SetupClient) SignTransportQuery(ctx context.Context, src, dst cipher.PubKey) (*TransportQuery, error)
- type SetupConfig
- type SetupPathStats
- type SetupRPCGateway
- func (g *SetupRPCGateway) Capabilities(_ *CapabilitiesArgs, reply *CapabilitiesReply) error
- func (g *SetupRPCGateway) CascadeSignInstall(args *CascadeSignInstallArgs, reply *CascadeSignInstallReply) error
- func (g *SetupRPCGateway) CascadeSignReserve(args *CascadeSignReserveArgs, reply *CascadeSignReserveReply) error
- func (g *SetupRPCGateway) DialRouteGroup(route routing.BidirectionalRoute, rules *routing.EdgeRules) (err error)
- func (g *SetupRPCGateway) DialRouteGroupBatch(batch *routing.BidirectionalRouteBatch, ...) error
- func (g *SetupRPCGateway) HealthCheck(_ *HealthCheckArgs, reply *HealthCheckReply) error
- func (g *SetupRPCGateway) RelayPeers(_ *RelayPeersArgs, reply *RelayPeersReply) error
- func (g *SetupRPCGateway) SignTransportQuery(args *SignTransportQueryArgs, reply *SignTransportQueryReply) error
- type SetupTransportConfig
- type Shape
- type SignTransportQueryArgs
- type SignTransportQueryReply
- type TransportListFetcher
- type TransportQuery
- type TransportQueryDeliverer
- type TransportQueryRPCGateway
- type TransportQueryResponse
- type WeightMode
Constants ¶
const ( // DatagramCounterSize is the on-wire counter prefix length. DatagramCounterSize = 8 // DatagramTagSize is the Poly1305 authentication tag length. DatagramTagSize = chacha20poly1305.Overhead // DatagramOverhead is the total per-datagram wire overhead // (counter prefix + auth tag). Plaintext + DatagramOverhead is // what lands in a DatagramPacket's payload. DatagramOverhead = DatagramCounterSize + DatagramTagSize // MaxDatagramPlaintext is the largest plaintext datagram body // that fits in a single DatagramPacket after AEAD wrapping. // Computed from MaxDatagramPayload (the routing-packet payload // cap) minus DatagramOverhead. MaxDatagramPlaintext = MaxDatagramPayload - DatagramOverhead // DefaultRekeyTimeInterval is the time-based rekey trigger. // WireGuard uses 2 minutes; matching that for operational // familiarity. DefaultRekeyTimeInterval = 2 * time.Minute // DefaultRekeyPacketLimit is the packet-count rekey trigger. // 2^32 = ~4.3B packets, well below the ChaCha 2^60 bound but // generous compared to realistic workloads (a 100 Kpps stream // for ~12 hours). DefaultRekeyPacketLimit uint64 = 1 << 32 )
Datagram AEAD wire constants. Public so Stage 4's MaxPayload computation can subtract the correct overhead.
const ( MuxEventGroupCreated = "group_created" MuxEventGroupClosed = "group_closed" MuxEventLegAdded = "leg_added" MuxEventLegRemoved = "leg_removed" MuxEventLegDropped = "leg_dropped" MuxEventLegParked = "leg_parked" // MuxEventLegPromoted is a warm-standby leg entering the active stripe set — // the operator pinned it (mux add / mux set) or the policy tick promoted it. // The counterpart of MuxEventLegParked, so churn is countable in both // directions. MuxEventLegPromoted = "leg_promoted" // MuxEventParkTrialFailed is a shared-bottleneck park that GOODPUT refuted: // the group's aggregate delivered-bytes rate fell by more than the trial // limit with the leg parked, so the legs were carrying independent capacity // after all. Its Reason names both rates (before → after, B/s) and the // exemption the pair earned, so a bench run can count wrong SBD rulings and // see what each cost. Always followed by the leg_promoted of the unpark. MuxEventParkTrialFailed = "park_trial_failed" // MuxEventSBDRuling is a shared-bottleneck ruling the detector MADE and did // not act on: with demotion off (the default, see sbdDemoteDefault) a leg the // detector judges redundant is recorded and kept. Its Reason names the leg, // the active leg it was judged against, the pair's summary statistics and the // send-path rate behind the reading — everything a bench run needs to score // the detector against what the transfer actually did, without letting it take // a leg away to find out. MuxEventSBDRuling = "sbd_ruling" // MuxEventLegStalled is a leg the data-progress detector found stalled // behind an open reorder gap on a side that does NOT own the active set — // an ACCEPTOR honoring the initiator's CapLegState mirror. The observation // is still worth recording (it names the leg and the gap age), but the park // belongs to the initiator, so no park follows and nothing is mirrored back. MuxEventLegStalled = "leg_stalled" MuxEventLegAddFailed = "leg_add_failed" MuxEventPrimaryRehome = "primary_rehomed" // MuxEventForwardRehomed is the FORWARD direction (client → exit: uploads and // requests) moving to a different leg. It rides ONE leg — the direct leg, or // else the lowest-latency one — so every move is a decision worth a line: its // Reason says whether the leg it held went away (dead, parked to standby, or // outclassed by a direct leg that joined) or a challenger measured far enough // below it for long enough (--forward-switch-margin, two consecutive samples). // A run full of these is the flapping the margin exists to stop. MuxEventForwardRehomed = "forward_rehomed" // MuxEventForwardFanout / ...Confined bracket the FORWARD direction's // load-triggered fan-out: the leg it is confined to sat at its send window // for unidir.fanout_engage without a break and a sibling within // unidir.fanout_max_skew took the overflow, and later the load subsided and // the direction went back to the one leg. An upload that never raises one is // an upload that fitted on a single leg — the confinement working, not the // aggregation missing. MuxEventForwardFanout = "forward_fanout" MuxEventForwardConfined = "forward_confined" // MuxEventReorderWedge / ...Cleared bracket a RECEIVE-side reorder wedge: // the frontier held past reorderTimeout because the sender's retransmit // never refilled the missing sequence, and then it advanced again. Whole- // group events (LegIndex -1) whose Reason names the stuck seq, how long it // stayed stuck and how many packets were dammed behind it — so the wedge // outlives the visor log ring and is readable from `visor state`. MuxEventReorderWedge = "reorder_wedge" MuxEventReorderWedgeCleared = "reorder_wedge_cleared" // MuxEventDialDecision is recorded once per group this visor dialed with // DiversifyTransports set: its Reason is the route-choice trail (sibling // exclusions, candidate filtering, the chosen first hop), so a tunnel that // shares a first hop with its siblings says why, from `visor state`. MuxEventDialDecision = "dial_decision" // MuxEventPoolLegTaken / ...Released bracket the standby-pool arbiter's dual // use of a pooled tunnel (pool_arbiter.go): a LOADED active tunnel took a // pooled chain as one more packet-level mux leg, and gave it back when the // load was gone. The Reason names the load signal and the standby tunnel's // own port, so a leg that is not the group's own dial says where it is from. MuxEventPoolLegTaken = "pool_leg_taken" MuxEventPoolLegReleased = "pool_leg_released" // The TUNNEL-level counterparts of the leg events above, decided by the // APP that holds the tunnels (the skysocks standby pool) and reported into // this ring through NoteTunnelEvent. A tunnel is a whole route group, so // these are group events (LegIndex 0, stamped with the group's first hop) // rather than leg events. // // MuxEventTunnelPromoted is a standby tunnel entering the active set — // after an active tunnel died, or because the promoter judged it better. // MuxEventTunnelParked is its counterpart, an active tunnel going back to // standby, so churn is countable in both directions. MuxEventTunnelRetired // is the death that starts a failover: the app saw no pong and no bytes on // the tunnel for its hard-dead window and closed the session. MuxEventTunnelPromoted = "tunnel_promoted" MuxEventTunnelParked = "tunnel_parked" MuxEventTunnelRetired = "tunnel_retired" // MuxEventTunnelConsumed is a standby tunnel SPENT rather than lost: its // whole built chain was re-homed into an active group as one more mux leg // (leg_rehome.go), so the tunnel's own route group closed on purpose. The // distinction matters to the app: a death re-arms the redial backoff and the // pool fill, and doing that for a tunnel nobody lost is a redial storm. The // Reason names the group the chain went to. MuxEventTunnelConsumed = "tunnel_consumed" // MuxEventTunnelSnubbed / ...Unsnubbed bracket a QUEUE-AWARE no-progress // sit-out, borrowed from bittorrent: a tunnel holding outstanding chunks // that has delivered no byte and no ack for its snub bound is snubbed, its // outstanding work re-issued elsewhere, and it is re-tried with ONE chunk // after the hold. The Reason names the silence and the bound. MuxEventTunnelSnubbed = "tunnel_snubbed" MuxEventTunnelUnsnubbed = "tunnel_unsnubbed" // MuxEventLegProbeOnly / ...LegFullShare bracket the OUTCLASSED-LEG gate: a // leg whose delay basis exceeds the group's best active leg's by more than // --leg-starve-ratio while delivering under 1/that of its goodput is cut to // --leg-probe-bytes per window instead of the // proportional share the round-robin schedule would give it, and restored // when its basis comes back. Neither is a park — the leg keeps its rules and // its probe — so they are recorded apart from leg_parked/leg_promoted. Their // Reason names both bases and the ratio, which is what a bench run scores the // gate by. MuxEventLegProbeOnly = "leg_probe_only" )
Mux event kinds.
const ( MuxByLocal = "local" MuxByRemote = "remote" MuxByOperator = "operator" MuxByAdaptive = "adaptive" MuxByPolicy = "policy" // MuxByPeer is a state change this side did not decide: the REMOTE end parked // or promoted the leg and signaled it over CapLegState, and we mirrored it. // Distinct from MuxByRemote (which marks a remote-initiated group/leg // teardown) so a leg that is standby purely because the peer says so is // attributable from `visor state` — it was previously adopted silently, which // is how a leg stayed parked for five minutes with zero park events logged. MuxByPeer = "peer" )
Mux event initiators (MuxEvent.By).
const ( // DefaultRouteKeepAlive is the default expiration interval for routes. // Routes are refreshed on every data transfer. This timeout only // fires when no data has been sent for this long. Previously 24h, // reduced to 10 minutes so idle routes (e.g. from the resolving // proxy's one-shot HTTP requests) are cleaned up promptly instead // of consuming routing table entries for a full day. DefaultRouteKeepAlive = 10 * time.Minute // DefaultRulesGCInterval is the default duration for garbage collection of routing rules. DefaultRulesGCInterval = 10 * time.Second )
const ( // CapBatchRouteSetup means DialRouteGroupBatch is implemented: N routes to // one destination in one request, with the per-hop work coalesced. CapBatchRouteSetup = "batch-route-setup" // CapCascadeSign means the CascadeSign* RPCs are implemented (the // source-driven cascade, where the node signs and the source injects). CapCascadeSign = "cascade-sign" // CapTransportQuery means SignTransportQuery is implemented (the RSN-oracle // destination-transport query). CapTransportQuery = "transport-query" )
Setup-node capability names. A client sends a newer request shape only when the node has advertised the matching capability, so an un-upgraded setup node never receives a message it cannot decode and no flag has to be flipped on either side (see Capabilities / HealthCheck).
const ( RequestTimeout = time.Second * 60 ReadTimeout = time.Second * 30 )
Various timeouts for setup node.
const DefaultPoolTTL = maxPooledReuseIdle
DefaultPoolTTL is how long an idle connection stays in the pool before the eviction loop reaps it. It MUST stay at or below maxPooledReuseIdle: a connection that idles past the stream read deadline is already shut down, so keeping it pooled only risks Get handing out a dead connection. The RSN's hot destinations are hit every few seconds (lastUsed refreshed on Put) so they never idle out; colder ones re-dial fresh, which is correct since their pooled stream would have died anyway.
const DialTimeout = 10 * time.Second
DialTimeout is the maximum time allowed for a single dial attempt to a remote visor. Without this, dials to unreachable visors block indefinitely in DialStream.readResponse, leaking goroutines. This caps the dial itself; the RPC deadline is set separately. Keep this short to free ephemeral ports quickly under load.
const MakeMapTimeout = 60 * time.Second
MakeMapTimeout is the hard deadline for MakeMap to dial all visors concurrently. This prevents indefinite blocking when multiple visors are unreachable, which was the primary cause of goroutine accumulation in the setup-node (113+ stuck goroutines observed in production pprof dumps).
const MaxDatagramPayload = math.MaxUint16
MaxDatagramPayload is the largest datagram body that fits in a single DatagramPacket on the wire. The packet header carries a uint16 payload-size field, capping the total payload (including any Stage 3 AEAD overhead) at 65535 bytes. Apps consult DatagramRouteGroup.MaxPayload() rather than this constant so the answer accounts for the AEAD tag + nonce overhead Stage 3 will reserve.
const MuxEventForwardDrops = "forward_drops"
MuxEventForwardDrops is recorded the first time a next-hop transport loses transit frames inside a forward.drop_event_window — a whole-transport event (LegIndex -1) naming the peer and the counters, so a wedged transit peer is readable from `visor state` long after the log ring has rolled.
const MuxEventRingSizeDefault = 256
MuxEventRingSize is how many mux events the router keeps.
const RPCName = "RPCGateway"
RPCName is the gobrpc service name the route-setup client dials as "RPCGateway.<Method>".
Variables ¶
var ( // ErrCounterExhausted is returned by Seal when the per-direction // counter has reached its rekey limit and no rekey has happened // yet. The caller should trigger a rekey and retry. ErrCounterExhausted = errors.New("datagram-crypto: counter exhausted, rekey required") // ErrAEADAuth is returned by Open when the auth tag does not // verify, the counter falls outside the replay window, or the // counter was already seen. Indistinguishable to the caller by // design — leaking which failure occurred would expose timing // signal to an attacker probing the window. ErrAEADAuth = errors.New("datagram-crypto: auth failure") // ErrCiphertextTooShort is returned when the input to Open is // shorter than the minimum (counter prefix + tag). ErrCiphertextTooShort = errors.New("datagram-crypto: ciphertext shorter than overhead") // ErrPlaintextTooLong is returned when Seal's input exceeds // MaxDatagramPlaintext. ErrPlaintextTooLong = errors.New("datagram-crypto: plaintext exceeds MaxDatagramPlaintext") )
Errors surfaced by DatagramCipher operations.
var ( // ErrNoTransports is returned when RouteGroup has no transports. ErrNoTransports = errors.New("no transports") // ErrNoRules is returned when RouteGroup has no rules. ErrNoRules = errors.New("no rules") // ErrBadTransport is returned when transport is nil. ErrBadTransport = errors.New("bad transport") // ErrRuleTransportMismatch is returned when number of forward rules does not equal to number of transports. ErrRuleTransportMismatch = errors.New("rule/transport mismatch") // ErrNoSuitableTransport is returned when no suitable transport was found. ErrNoSuitableTransport = errors.New("no suitable transport") // ErrNoRouteFound is return when no route founds after specific tries ErrNoRouteFound = errors.New("no route founds") )
var ( // ErrUnknownPacketType is returned when packet type is unknown. ErrUnknownPacketType = errors.New("unknown packet type") // ErrRemoteEmptyPK occurs when the specified remote public key is empty. ErrRemoteEmptyPK = errors.New("empty remote public key") // ErrDialPolicyDropped is returned by DialRoutes when the // configured DialHook's BeforeDial returns Fallback="drop". // Operators see this as the explicit signal that their // routing policy refused this dial; it's distinct from any // route-finder failure or transport error. ErrDialPolicyDropped = errors.New("dial refused by routing policy") // ErrNoTransportFound is returned when not even one transport is found. ErrNoTransportFound = errors.New("no transport found") )
var ( // ErrTransportQueryUntrustedRSN is returned when the query's RSN public key // is not on the responding visor's trusted-RSN allowlist. Mirrors // routing.ErrCascadeUntrustedRSN for the query path. ErrTransportQueryUntrustedRSN = errors.New("transport-query: RSN public key not trusted") // ErrTransportQuerySigInvalid is returned when the RSN signature does not // verify against the responding visor's own PK. ErrTransportQuerySigInvalid = errors.New("transport-query: RSN signature verification failed") // ErrTransportQueryWrongTarget is returned when the query's TargetPK is not // the responding visor's own PK (a query signed for a different destination // must not be answered by this one). ErrTransportQueryWrongTarget = errors.New("transport-query: target PK mismatch") )
var ErrAdoptUnsupported = fmt.Errorf("%w: leg reserve adoption unavailable", ErrRehomeUnsupported)
ErrAdoptUnsupported is returned when a leg reserve cannot be adopted in place: there is no such reserve, it has no mux that negotiated CapLegRehome, or it holds other than exactly one chain.
var ErrCircuitOpen = fmt.Errorf("route setup: destination circuit breaker open")
ErrCircuitOpen is returned by CreateRouteGroup when the per- destination circuit breaker has short-circuited a setup attempt. It is distinct from a dial failure so callers and stats can tell the difference between "this destination is known bad, skipping" and "we tried and failed again".
var ErrClosed = errors.New("datagram-route-group: closed")
ErrClosed is returned by ReadFrom / WriteTo after Close.
var ErrNoDisjointFirstHop = errors.New("no disjoint first-hop route is free")
ErrNoDisjointFirstHop is returned to a dial that set DialOptions.RequireDisjointFirstHop when the ranked candidate list is exhausted: every route this visor can offer to the destination leaves over a first-hop transport / peer / IP that a sibling route group to the same destination already holds.
It is a SETTLED answer, not a transient failure — the topology is what it is — so the caller must treat it as "stop dialing", never as "retry". The skysocks standby pool stops filling on it and re-arms only when a tunnel dies or the transport set changes; a retry loop against it is exactly the setup-node storm of #4325.
var ErrNoKey = errors.New("id reservoir has no key")
ErrNoKey is returned when key is not found.
var ErrPayloadTooBig = errors.New("datagram-route-group: payload exceeds MaxPayload")
ErrPayloadTooBig is returned by WriteTo when the caller provides a datagram larger than MaxPayload. Mirrors net.UDPConn's behavior on oversized sends but is surfaced explicitly rather than as an opaque EMSGSIZE — apps that consulted MaxPayload() should never see this in practice.
var ErrPolicyAvoidDirect = errors.New("routing policy: avoid direct transport, use overlay")
ErrPolicyAvoidDirect is returned from the direct-dial policy hook when the policy wants to force the overlay path. The caller (tryDirectDial) treats this like any other direct-dial failure and falls through to route setup — no extra plumbing required.
var ErrRehomeNoAck = errors.New("re-home not acknowledged")
ErrRehomeNoAck is the exit not answering a re-home request in time or refusing it: both groups are left intact, so the caller may fall back to dialing the leg on the pool's plan instead of giving up on the take.
var ErrRehomeUnsupported = errors.New("leg re-home unsupported (peer did not negotiate CapLegRehome)")
ErrRehomeUnsupported is returned when a chain cannot be re-homed in place: the peer did not advertise CapLegRehome, or a group involved has no mux. The caller's fallback is to spend the pooled tunnel the DIAL-based way — a pool-sourced leg through the setup node, reusing the pool's route plan and warm transport (see poolSourcedLegFallback).
var ErrSetupNode = errors.New("failed to dial to a setup node")
ErrSetupNode is used when the visor is unable to connect to a setup node
var ErrSplitUnsupported = fmt.Errorf("%w: leg split unavailable", ErrRehomeUnsupported)
ErrSplitUnsupported is returned when a leg cannot be split back out in place: the peer did not negotiate CapLegRehome, the group has no mux, or the group is not registered with a router that can hold the new standby group. The caller's fallback is to close the leg's transport, which is what the release path did before splits existed.
Functions ¶
func BroadcastIntermediaryRules ¶
func BroadcastIntermediaryRules(ctx context.Context, log logrus.FieldLogger, rtIDR IDReserver, interRules RulesMap, installed *installedRules) (err error)
BroadcastIntermediaryRules broadcasts routing rules to the intermediary routers. On each hop's successful install it records the installed RouteIDs into `installed` (when non-nil) so a later setup failure can tear them down instead of orphaning them.
func CreateRouteGroup ¶
func CreateRouteGroup(ctx context.Context, dialer network.Dialer, pool *ClientPool, cascade *CascadeBuilder, biRt routing.BidirectionalRoute, metrics setupmetrics.Metrics) (resp routing.EdgeRules, err error)
CreateRouteGroup creates a route group by communicating with routers used within the bidirectional route. The following steps are taken: * Check the validity of bi route input. * Consult the per-destination circuit breaker; fast-fail if open. * Route IDs are reserved from the routers. * Intermediary rules are broadcasted to the intermediary routers. * Edge rules are broadcasted to the responding router. * Edge rules is returned (to the initiating router).
func DeadRouteHold ¶
DeadRouteHold is the first exclusion window a route that died young is kept out of the diversify search for.
func DeadRouteHoldMax ¶
DeadRouteHoldMax caps the doubling applied on each repeat death.
func DeadRouteYoungAge ¶
DeadRouteYoungAge is the "died right after dial" window: a group that lives longer than this and then closes is a normal teardown, not a dead route.
func DeliveryCRCAdvertised ¶
func DeliveryCRCAdvertised() bool
DeliveryCRCAdvertised reports whether CapDeliveryCRC is advertised on new route groups. It gates OUR sender side: with the knob off we never stamp a frame, and because the peer only strips when we advertised too, the two ends can never disagree about whether the 4-byte trailer is present.
func DialDiversifyCandidates ¶
func DialDiversifyCandidates() int
DialDiversifyCandidates is how many routes a DIVERSIFY dial asks the route finder for. A standby-pool fill is not looking for the best route, it is looking for one the tunnels it already holds do not use — and the finder answers rank-ordered, so the top few are exactly the ones they do.
func DialForegroundMux ¶
func DialForegroundMux() int
DialForegroundMux bounds how many mux legs are established SYNCHRONOUSLY at dial time; the background self-heal fills the rest of the pool.
func DialMuxRouteHeadroom ¶
func DialMuxRouteHeadroom() int
DialMuxRouteHeadroom is the extra routes requested on top of the mux degree, so the disjoint-path pick can still reach the target count when some returned candidates share intermediates — the sibling-diversify count.
func DialPreferPKs ¶
DialPreferPKs returns the operator's prefer-these-peers list — the routes that should be taken FIRST. Nil when unset (the ranking is untouched).
func DialRouteCandidates ¶
func DialRouteCandidates() int
DialRouteCandidates is the floor on how many routes a mux dial asks the route-finder for (the historical default route count).
func DialThroughputPriorScale ¶
func DialThroughputPriorScale() float64
DialThroughputPriorScale multiplies the MEASURED-throughput band penalty in transportCostMs.
func DialTunnelLegs ¶
func DialTunnelLegs() int
DialTunnelLegs is the leg count an APP TUNNEL is dialed with — the fix for a tunnel that asks for a single-route group (MuxRoutes=1) and then has its legs bolted on afterwards by `proxy mux add` / the background self-heal.
0 (default) — dial exactly what the app asked for. Today's behavior.
-1 — follow the visor's mux width (preset.AdaptRevActive), so a
tunnel is dialed with the legs the adaptive engine would have
grown it to anyway.
n > 1 — dial every tunnel with n legs.
Only a dial that asked for a single-route group is raised; a dial that named its own mux degree, a --direct dial and a datagram dial are all untouched.
This is the visor-wide value; dialTunnelLegsFor reads the same knob through an app's overrides, which is what `route settings --app <name> dial.tunnel_legs=N` sets.
func DialTypePriorScale ¶
func DialTypePriorScale() float64
DialTypePriorScale multiplies the transport-TYPE prior (transportTypeCostMs), the penalty a hop carries until its link has a throughput measurement.
func DialUnknownHopPenaltyMs ¶
func DialUnknownHopPenaltyMs() float64
DialUnknownHopPenaltyMs is what one unmeasured hop costs a partially measured path, in milliseconds.
func DialUnknownLatencyCostMs ¶
func DialUnknownLatencyCostMs() float64
DialUnknownLatencyCostMs is the per-hop cost of a hop with no latency measurement, in milliseconds.
func EcfMaxWindowBytes ¶
func EcfMaxWindowBytes() int64
EcfMaxWindowBytes is the ceiling a leg's send window is clamped to — the number the client-side chunk and upload sizes are sized against.
func EcfMinWindowBytes ¶
func EcfMinWindowBytes() int64
EcfMinWindowBytes is the floor a leg's send window is clamped to.
func EcfWindowMargin ¶
func EcfWindowMargin() float64
EcfWindowMargin is the multiplier on SACK-proven delivery-per-RTT that sets a leg's send window.
func FindDirectRSNTransport ¶
FindDirectRSNTransport checks if we have a direct "setup"-labeled transport to the RSN.
func ForwardSpill ¶
func ForwardSpill() bool
ForwardSpill reports whether a FORWARD frame may leave its confined leg when that leg is at its send window.
func ForwardSwitchMargin ¶
func ForwardSwitchMargin() float64
ForwardSwitchMargin is how much lower a challenger leg must measure before the forward direction moves to it.
func ForwardSwitchSamples ¶
func ForwardSwitchSamples() int
ForwardSwitchSamples is how many consecutive refreshes a challenger must clear the margin for before the direction moves.
func HOLRetxAdvertised ¶
func HOLRetxAdvertised() bool
HOLRetxAdvertised reports whether CapHOLRetx is advertised on new route groups. Implies SACK: the mechanism reuses the SACK feedback, so it is never advertised on its own.
func LegDelivAlpha ¶
func LegDelivAlpha() float64
LegDelivAlpha is the weight the newest sample carries in a leg's SACK-proven delivery EWMA — the reading the goodput half of the outclassed-leg gate judges on.
func LegParkMinHold ¶
LegParkMinHold is how long an adaptive park holds before any adaptive promoter may re-admit the leg.
func LegProbeBytes ¶
func LegProbeBytes() int64
LegProbeBytes is how many bytes a leg ruled probe-only may carry per window (the window being its own delay basis, floored at leg.probe_min_window).
func LegStarveRatio ¶
func LegStarveRatio() float64
LegStarveRatio is the one ratio the outclassed-leg ruling uses at both ends: a leg is cut to a probe per window only when its delay basis exceeds the best ready leg's by more than this AND its proven delivery rate is under 1/this of that leg's — so a leg that is slow but productive keeps its share. At or below 1 the gate is off.
func PerFrameNoiseEnabled ¶
func PerFrameNoiseEnabled() bool
PerFrameNoiseEnabled reports whether CapPerFrameNoise is advertised on new route groups.
func PoolBoundRefresh ¶ added in v1.3.99
PoolBoundRefresh is how often a pooled exit's disjoint routes are re-counted; 0 leaves the count to the dials.
func RegisterSetupRPCGateway ¶ added in v1.3.101
func RegisterSetupRPCGateway(rpcSrv *rpc.Server, rpcName string, rpcRcvr *SetupRPCGateway) error
RegisterSetupRPCGateway registers *SetupRPCGateway with the reflection-based net/rpc server.
func ReorderSetupNodes ¶
ReorderSetupNodes moves the given public key to the front of the list. This should be called after a successful connection to prioritize working nodes.
func SACKAdvertised ¶
func SACKAdvertised() bool
SACKAdvertised reports whether CapSACK is advertised on new route groups.
func SBDBackoff ¶
SBDBackoff is how long a pair whose park trial failed is exempt from shared-bottleneck merging.
func SBDDemote ¶
func SBDDemote() bool
SBDDemote reports whether a shared-bottleneck ruling may PARK a leg at all.
func SBDEnabled ¶
func SBDEnabled() bool
SBDEnabled reports whether shared-bottleneck detection runs at all. Off stops the sampling as well as the rulings — the explicit switch the rig used to fake with an unreachable sbd.min_samples.
func SBDMinEvidenceRate ¶
func SBDMinEvidenceRate() int64
SBDMinEvidenceRate is the aggregate goodput a group must be carrying before a shared-bottleneck ruling may park one of its legs.
func SBDMinSamples ¶
func SBDMinSamples() int
SBDMinSamples is how many delay samples a leg needs before its statistics are trusted for shared-bottleneck grouping.
func SBDSampleInterval ¶
SBDSampleInterval is the minimum spacing between two per-SACK delay samples folded into one leg's window.
func SBDTrialLoss ¶
func SBDTrialLoss() float64
SBDTrialLoss is the fraction of aggregate goodput a park may cost before it is undone.
func SBDTrialWindow ¶
SBDTrialWindow is how long a shared-bottleneck park is held as a trial before the aggregate goodput is re-read.
func SendWindowWaitMax ¶
SendWindowWaitMax bounds how long a writer parks when every ready leg is at its window before sending anyway.
func ServeTransportQueryListener ¶
func ServeTransportQueryListener(ctx context.Context, dmsgC *dmsg.Client, gw *TransportQueryRPCGateway, log *logging.Logger) error
ServeTransportQueryListener listens on skyenv.DmsgTransportQueryPort and serves the gateway to any visor that dials it. Blocks until ctx is canceled or the listener fails. The caller starts it in a goroutine (see the visor init wiring). Only invoked when the RSN-oracle path is enabled — a default visor opens no listener here.
func SetDeadRouteHold ¶
SetDeadRouteHold installs the first exclusion window. Non-positive is refused.
func SetDeadRouteHoldMax ¶
SetDeadRouteHoldMax installs the exclusion ceiling. Non-positive is refused.
func SetDeadRouteYoungAge ¶
SetDeadRouteYoungAge installs that window. Non-positive is refused.
func SetDialDiversifyCandidates ¶
SetDialDiversifyCandidates installs that count. Non-positive is refused.
func SetDialForegroundMux ¶
SetDialForegroundMux installs that bound. Non-positive is refused.
func SetDialMuxRouteHeadroom ¶
SetDialMuxRouteHeadroom installs that headroom. Negative is refused; 0 means "ask for exactly the mux degree".
func SetDialPreferPKs ¶
SetDialPreferPKs installs the prefer list. An empty or nil slice clears it. A copy is kept so the caller may reuse its slice.
func SetDialRouteCandidates ¶
SetDialRouteCandidates installs that floor. Non-positive is refused.
func SetDialThroughputPriorScale ¶
SetDialThroughputPriorScale installs that multiplier; 0 removes the term.
func SetDialTunnelLegs ¶
SetDialTunnelLegs installs that count. Values below -1 are refused.
func SetDialTypePriorScale ¶
SetDialTypePriorScale installs that multiplier; 0 removes the term.
func SetDialUnknownHopPenaltyMs ¶
SetDialUnknownHopPenaltyMs installs that penalty. Negative / NaN / Inf refused; 0 drops the penalty.
func SetDialUnknownLatencyCostMs ¶
SetDialUnknownLatencyCostMs installs that cost. Negative / NaN / Inf refused; 0 drops the penalty.
func SetEcfMaxWindowBytes ¶
SetEcfMaxWindowBytes installs the send-window ceiling. Non-positive is refused.
func SetEcfMinWindowBytes ¶
SetEcfMinWindowBytes installs the send-window floor. Non-positive is refused.
func SetEcfWindowMargin ¶
SetEcfWindowMargin installs the send-window margin. Non-positive is refused.
func SetForwardSpill ¶
SetForwardSpill turns forward spill on or off. Always accepted; the bool return keeps the shape of the other setters.
func SetForwardSwitchMargin ¶
SetForwardSwitchMargin installs that margin. Non-positive is refused.
func SetLegDelivAlpha ¶
SetLegDelivAlpha installs that weight. Only a value in (0,1] is accepted.
func SetLegParkMinHold ¶
SetLegParkMinHold installs the adaptive park hold. Non-positive is refused.
func SetLegProbeBytes ¶
SetLegProbeBytes installs that budget. Non-positive is refused: a budget of zero would silence the leg completely, and the point of the probe is that the leg keeps being measured so the ruling can lift.
func SetLegStarveRatio ¶
SetLegStarveRatio installs that ratio. Only a finite value above 1 is accepted — at or below 1 every leg would outclass every other, including itself; use a very large ratio to disable the gate instead.
func SetSBDBackoff ¶
SetSBDBackoff installs that exemption. Non-positive is refused.
func SetSBDDemote ¶
SetSBDDemote turns shared-bottleneck demotion on or off. Always accepted; the bool return keeps the shape of the other setters.
func SetSBDEnabled ¶
SetSBDEnabled turns shared-bottleneck detection on or off. Always accepted.
func SetSBDMinEvidenceRate ¶
SetSBDMinEvidenceRate installs that floor. Non-positive is refused.
func SetSBDMinSamples ¶
SetSBDMinSamples installs the shared-bottleneck sample floor. Non-positive is refused.
func SetSBDSampleInterval ¶
SetSBDSampleInterval installs that spacing. Non-positive is refused.
func SetSBDTrialLoss ¶
SetSBDTrialLoss installs that fraction. Non-positive is refused.
func SetSBDTrialWindow ¶
SetSBDTrialWindow installs the trial window. Non-positive is refused.
func SetSendWindowWaitMax ¶
SetSendWindowWaitMax installs the send-window wait bound. Non-positive is refused.
func SetSetupBatchMax ¶
SetSetupBatchMax installs that cap. Non-positive is refused; values above routing.MaxBatchRoutes are clamped by the sender.
func SetSetupBatchWindow ¶
SetSetupBatchWindow installs that window. Non-positive is refused.
func SetSetupCircuitBreaker ¶
SetSetupCircuitBreaker turns that lockout on or off.
func SetSetupCircuitFailThreshold ¶
SetSetupCircuitFailThreshold installs that threshold. Non-positive is refused.
func SetSetupCircuitFailWindow ¶
SetSetupCircuitFailWindow installs that window. Non-positive is refused.
func SetSetupCircuitMaxOpenDuration ¶
SetSetupCircuitMaxOpenDuration installs that ceiling. Non-positive is refused.
func SetSetupCircuitOpenDuration ¶
SetSetupCircuitOpenDuration installs that duration. Non-positive is refused.
func SetSetupFillInflight ¶
SetSetupFillInflight installs that bound. Non-positive is refused.
func SetSetupFirstHopFilterMax ¶
SetSetupFirstHopFilterMax installs that threshold. Non-positive is refused.
func SetSetupPlanClaimTTL ¶
SetSetupPlanClaimTTL installs that TTL. Non-positive is refused.
func SetWarmPlanBucketCap ¶
SetWarmPlanBucketCap installs that cap. Non-positive is refused.
func SetWarmPlanTTL ¶
SetWarmPlanTTL installs that TTL. Non-positive is refused.
func SetupBatchMax ¶
func SetupBatchMax() int
SetupBatchMax is the most routes one batched setup request carries. 1 means "send singles" — the batch form is never used.
func SetupBatchWindow ¶
SetupBatchWindow is how long a route setup collects sibling dials to the same exit before sending them as one batched request.
func SetupCircuitBreaker ¶
func SetupCircuitBreaker() bool
SetupCircuitBreaker reports whether the route setup node's per-destination circuit breaker may open. Off by default — see setupmetrics for why.
func SetupCircuitFailThreshold ¶
func SetupCircuitFailThreshold() int
SetupCircuitFailThreshold is how many consecutive failures attributed to one destination trip its breaker.
func SetupCircuitFailWindow ¶
SetupCircuitFailWindow is how close together failures must fall to count as consecutive.
func SetupCircuitMaxOpenDuration ¶
SetupCircuitMaxOpenDuration is the total open/half-open time after which a breaker is force-closed.
func SetupCircuitOpenDuration ¶
SetupCircuitOpenDuration is how long a tripped breaker refuses setups before admitting a half-open probe.
func SetupFillInflight ¶
func SetupFillInflight() int
SetupFillInflight is how many standby-pool tunnel dials run concurrently.
func SetupFirstHopFilterMax ¶
func SetupFirstHopFilterMax() int
SetupFirstHopFilterMax is the held-first-hop count beyond which first-hop diversity is a ranking term rather than a filter — under pool.allow_duplicate_route only.
func SetupPlanClaimTTL ¶
SetupPlanClaimTTL is how long a concurrent dial holds its claim on an oracle candidate path, so N dials in one fill take N distinct intermediates.
func WarmPlanBucketCap ¶
func WarmPlanBucketCap() int
WarmPlanBucketCap bounds how many distinct disjoint plans the warm pool holds per exit.
func WarmPlanTTL ¶
WarmPlanTTL bounds staleness of a cached disjoint plan set in the warm route pool.
func WithForceLegacyRouteSetup ¶
WithForceLegacyRouteSetup returns a context that instructs a cascade-capable RouteGroupDialer to bypass the source-driven cascade and use the classic setup-node path for this dial.
Rationale: the source-driven cascade installs rules at the destination over transports, verified by the destination's CascadeHandler via the RSN signature. A destination that does NOT run cascade route-setup only trusts the classic setup nodes (route_setup_nodes) and rejects a cascade-installed route — the route group's reciprocal handshake then never completes and the dial fails. The classic path routes setup through an RSN the destination already trusts, so escalating to it recovers the dial against a mixed fleet.
Types ¶
type BatchSavings ¶
BatchSavings is what one batch actually saved, for the setup node's /stats. Coalesced is the number of per-hop RPCs this batch's shape would have cost as N separate requests; Issued is what it cost as one. Both count the id-reservation and intermediary-install RPCs — the two the batch merges — and exclude the per-member destination edge install, which is not merged.
func CreateRouteGroupBatch ¶
func CreateRouteGroupBatch( ctx context.Context, dialer network.Dialer, pool *ClientPool, batch routing.BidirectionalRouteBatch, metrics setupmetrics.Metrics, ) (results []routing.BatchRouteResult, savings BatchSavings)
CreateRouteGroupBatch installs every route in the batch, coalescing the per-hop work, and returns one result per member in request order.
It never returns a short slice for a non-empty batch: a failure that kills the whole batch (malformed batch, or a shared id reservation that could not be made) is reported as that error on every member, so the caller always learns the fate of every route it asked for.
func (BatchSavings) Saved ¶
func (s BatchSavings) Saved() int
Saved is the per-hop RPC count the coalescing removed.
type CandidateInfo ¶
type CandidateInfo struct {
// Hops is the ordered intermediate PKs (hex) from source
// (excluded) to destination (excluded). Empty for a direct
// transport.
Hops []string
// EstLatencyMs is the sum of per-hop latencies along the
// route, in milliseconds, drawn from the router's existing
// transport-tracker lookup. Zero when no measurements are
// available — treat as "unknown," not "fast."
EstLatencyMs int
}
CandidateInfo is the bare-bones per-candidate description the router hands to RouteSelectingHook.SelectRoute. The hook is responsible for any further enrichment (geo lookup, transport- kind classification) — the router only owns the route-finder response and the latency lookup it already builds for its disjoint-path selection.
type CapabilitiesArgs ¶
type CapabilitiesArgs struct{}
CapabilitiesArgs is the request for the Capabilities RPC.
type CapabilitiesReply ¶
type CapabilitiesReply struct {
Caps []string `json:"caps"`
}
CapabilitiesReply lists the request shapes this setup node understands.
type CascadeBuilder ¶
type CascadeBuilder struct {
// contains filtered or unexported fields
}
CascadeBuilder constructs and sends cascade messages.
It serves two roles depending on how it is constructed:
- RSN-side (NewCascadeBuilder): holds the RSN's rsnPK/rsnSK and SIGNS each per-hop cascade layer. The RSN-side builder is a pure signing oracle — it builds bytes and never sends them itself.
- Source-side (NewSourceCascadeBuilder): holds a zero rsnSK and never signs; it only INJECTS pre-signed cascade bytes (received from the RSN over RPC) into the source's own transports and collects ACKs.
func NewCascadeBuilder ¶
func NewCascadeBuilder( log *logging.Logger, rsnPK cipher.PubKey, rsnSK cipher.SecKey, tm *transport.Manager, ) *CascadeBuilder
NewCascadeBuilder creates a signing builder for the RSN.
func NewSourceCascadeBuilder ¶
func NewSourceCascadeBuilder( log *logging.Logger, tm *transport.Manager, acks *ackRegistry, ) *CascadeBuilder
NewSourceCascadeBuilder creates a send-only builder for the SOURCE visor.
It carries no RSN secret key (it never signs) and shares the ack registry with the visor's CascadeHandler so that ACKs arriving on the visor's single registered cascade handler are routed back to in-flight sends. The source-side builder injects pre-signed cascade bytes — produced by the RSN via the CascadeSign* RPCs — into the source's own transports.
func (*CascadeBuilder) AckRegistry ¶
func (cb *CascadeBuilder) AckRegistry() *ackRegistry
AckRegistry returns the builder's ack registry so a CascadeHandler on the same visor can share it (the source visor has one registered cascade handler that must deliver ACKs to this builder's in-flight sends).
func (*CascadeBuilder) BuildInstallMessage ¶
func (cb *CascadeBuilder) BuildInstallMessage( hops []routing.Hop, sessionID uint64, rulesPerHop map[cipher.PubKey][]routing.Rule, edgeDesc routing.RouteDescriptor, ) ([]byte, error)
BuildInstallMessage constructs a nested install cascade from last hop to first. rulesPerHop maps each visor PK to the rules that should be installed on it. edgeDesc, when non-zero, is stamped onto the TERMINAL hop's layer (the responding destination) so that hop calls router.IntroduceRules — creating a route group — instead of only installing forwarding rules. Pass the zero RouteDescriptor for a path whose terminal must not introduce a route group.
func (*CascadeBuilder) BuildReserveMessage ¶
func (cb *CascadeBuilder) BuildReserveMessage(hops []routing.Hop, reserveN uint8) (uint64, []byte, error)
BuildReserveMessage constructs a nested reserve cascade from last hop to first. hops is the route's hop list: [{From:A, To:C, TpID:tp1}, {From:C, To:D, TpID:tp2}, ...]. The target visors are: hops[0].From (source), hops[i].From (intermediaries), hops[last].To (destination).
func (*CascadeBuilder) HandleAck ¶
func (cb *CascadeBuilder) HandleAck(p routing.Packet, _ *transport.ManagedTransport)
HandleAck is called by the transport manager's cascade handler when an ACK arrives on one of the RSN's transports. Dispatches to the waiting sender.
func (*CascadeBuilder) SendCascade ¶
func (cb *CascadeBuilder) SendCascade(ctx context.Context, firstHopTpID uuid.UUID, sessionID uint64, payload []byte, timeout time.Duration) (*routing.CascadeAck, error)
SendCascade sends a cascade payload to the first hop and waits for the ACK. firstHopTpID is the transport connecting the RSN to the first hop.
func (*CascadeBuilder) SetTimeouts ¶
func (cb *CascadeBuilder) SetTimeouts(reserve, install time.Duration)
SetTimeouts configures per-phase timeouts.
type CascadeConfig ¶
type CascadeConfig struct {
// ReserveTimeout is the per-hop timeout for the reserve phase.
ReserveTimeout time.Duration `json:"reserve_timeout,omitempty"`
// InstallTimeout is the per-hop timeout for the install phase.
InstallTimeout time.Duration `json:"install_timeout,omitempty"`
// SessionTTL is how long to keep session state between phases.
SessionTTL time.Duration `json:"session_ttl,omitempty"`
}
CascadeConfig configures the cascade route setup protocol.
func (*CascadeConfig) SetCascadeDefaults ¶
func (cc *CascadeConfig) SetCascadeDefaults()
SetCascadeDefaults fills in zero cascade config values.
type CascadeHandler ¶
type CascadeHandler struct {
// contains filtered or unexported fields
}
CascadeHandler processes cascade protocol messages arriving on any transport.
func NewCascadeHandler ¶
func NewCascadeHandler( log *logging.Logger, localPK cipher.PubKey, trustedRSNPKs []cipher.PubKey, rt routing.Table, tm *transport.Manager, introduceRules func(routing.EdgeRules) error, ) *CascadeHandler
NewCascadeHandler creates a new cascade handler. introduceRules is the router's IntroduceRules (creates a route group + notifies the launcher) and may be nil where no router is available (RSN, tests) — edge installs then fall back to plain rule saving.
func (*CascadeHandler) AckRegistry ¶
func (ch *CascadeHandler) AckRegistry() *ackRegistry
AckRegistry returns the handler's shared ack registry. The source-side CascadeBuilder is constructed against this registry so ACKs arriving on the visor's single registered cascade handler reach the builder's sends.
func (*CascadeHandler) HandlePacket ¶
func (ch *CascadeHandler) HandlePacket(p routing.Packet, sourceTp *transport.ManagedTransport)
HandlePacket is the callback registered with the transport manager. It dispatches cascade packets to the appropriate handler.
func (*CascadeHandler) ProcessLocalOrigin ¶
func (ch *CascadeHandler) ProcessLocalOrigin(payload []byte) (*routing.CascadeAck, error)
ProcessLocalOrigin processes a cascade layer addressed to THIS visor as the route's origin (source). Unlike handleSetup, the origin has no upstream transport to ACK back on: it verifies the RSN signature against our own PK, performs the reserve/install locally, relays the inner payload to the first hop over our own transport, and RETURNS the collected ACK to the caller (runSourceCascade) instead of writing it to a sourceTp.
This is the crux of source-driven cascade: the RSN builds the nested cascade with the source as the OUTERMOST layer (signed for the source's PK). The source must consume that layer itself — shipping it verbatim to the first hop made the hop reject it ("RSN signature verification failed"), since it was signed for the source's PK, not the hop's.
func (*CascadeHandler) SetNoTransit ¶
func (ch *CascadeHandler) SetNoTransit(v bool)
SetNoTransit makes the handler refuse cascade installs that would make this visor an intermediate hop on another visor's route.
type CascadeSignInstallArgs ¶
type CascadeSignInstallArgs struct {
Route routing.BidirectionalRoute
FwdSessionID uint64
RevSessionID uint64
FwdRouteIDs []routing.RouteID
RevRouteIDs []routing.RouteID
}
CascadeSignInstallArgs is the request for CascadeSignInstall. The source supplies the route, the session IDs from the reserve phase, and the route IDs it collected per hop. The RSN recomputes rules deterministically from the route + IDs; it does NOT trust source-supplied rules.
type CascadeSignInstallReply ¶
type CascadeSignInstallReply struct {
FwdInstallBytes []byte
RevInstallBytes []byte
InitEdge routing.EdgeRules
}
CascadeSignInstallReply carries the RSN-signed install cascades plus the initiating-edge rules the source installs locally and returns to the dialer.
type CascadeSignReserveArgs ¶
type CascadeSignReserveArgs struct {
Route routing.BidirectionalRoute
}
CascadeSignReserveArgs is the request for CascadeSignReserve.
type CascadeSignReserveReply ¶
type CascadeSignReserveReply struct {
FwdSessionID uint64
FwdReserveBytes []byte
RevSessionID uint64
RevReserveBytes []byte
}
CascadeSignReserveReply carries the RSN-signed reserve cascades. The RSN signs but does NOT send — the SOURCE injects these bytes into its own transports (Forward[0].TpID / Reverse[0].TpID) and collects the reserved route-ID ACKs.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is used to interact with the router's API remotely. The setup node uses this.
func NewClientFromRaw ¶
func NewClientFromRaw(conn io.ReadWriteCloser, rPK cipher.PubKey) *Client
NewClientFromRaw creates a new client from a raw connection.
func (*Client) AddEdgeRules ¶
AddEdgeRules adds forward and consume rules to router (forward and reverse).
func (*Client) AddIntermediaryRules ¶
func (c *Client) AddIntermediaryRules(ctx context.Context, rules []routing.Rule) (ok bool, err error)
AddIntermediaryRules adds intermediary rules to router.
type ClientPool ¶
type ClientPool struct {
// contains filtered or unexported fields
}
ClientPool is a thread-safe pool of reusable router RPC clients.
func NewClientPool ¶
func NewClientPool(dialer network.Dialer, ttl time.Duration) *ClientPool
NewClientPool creates a pool that reuses connections to remote visors.
func (*ClientPool) Close ¶
func (p *ClientPool) Close()
Close closes all pooled connections and stops the eviction loop.
func (*ClientPool) Discard ¶
func (p *ClientPool) Discard(client *Client)
Discard closes a client without returning it to the pool. Use this when the connection is known to be broken (RPC error, context cancel).
func (*ClientPool) Get ¶
Get returns an existing pooled client or dials a new one. The returned client is removed from the pool — the caller owns it and must call Put when done (or Close if the connection is broken).
func (*ClientPool) Put ¶
func (p *ClientPool) Put(client *Client)
Put returns a client to the pool for reuse. If the pool already has a connection to this PK (race between concurrent requests to the same destination), the older one is closed.
func (*ClientPool) Size ¶
func (p *ClientPool) Size() int
Size returns the number of pooled connections.
type Config ¶
type Config struct {
Logger *logging.Logger
MasterLogger *logging.MasterLogger
PubKey cipher.PubKey
SecKey cipher.SecKey
TransportManager *transport.Manager
RouteFinder rfclient.Client
// PreferLocalRouteTo, when set and true for dst, tries the local route
// calculation (this visor's transports plus the transport-discovery
// snapshot, which includes any local graph source) before the route
// finder is asked. A hypervisor sets it for its attached visors.
PreferLocalRouteTo func(dst cipher.PubKey) bool
RouteGroupDialer RouteGroupDialer
SetupNodes []cipher.PubKey
RulesGCInterval time.Duration
MinHops uint16
MaxHops uint16
// ExcludeTransportTypes hard-excludes routes traversing any of these transport
// types from the candidate set (see visorconfig.Routing.RouteExcludeTransportTypes).
// Empty = nothing excluded. Lowercased type names.
ExcludeTransportTypes []string
// NoTransit refuses every request to act as an intermediate hop on another
// visor's route (visorconfig.Routing.NoTransit). Routes that originate or
// terminate here are unaffected.
NoTransit bool
// DisableRaceRouteSetup turns OFF the app-dial race (default: race ON).
// Normally, when a dial would create a direct transport to the peer
// (min-hops==1, not existing-tp-only), DialRoutes runs that transport
// creation in the BACKGROUND and concurrently tries to route over EXISTING
// transports — returning as soon as either lands, instead of blocking on the
// (~20s worst-case) transport dial first. Set true to restore the legacy
// synchronous behavior (create-transport-then-route) as a safety valve.
DisableRaceRouteSetup bool
// ParallelRouteSetup is the number of candidate route groups whose
// reciprocal-handshake setup DialRoutes races CONCURRENTLY per dial
// attempt (top-K candidates the route-finder returned, ranked by
// latency/suspect-liveness). The FIRST candidate to complete its
// handshake WINS and immediately becomes the working route — the dial
// returns on it without waiting for a "better" one; the losers are
// canceled and torn down. This collapses establishment from "up to
// N×handshakeAwaitTimeout" (the old one-candidate-at-a-time retry loop,
// where every dead / non-forwarding candidate burned the full ~10s
// handshake await before the next was tried — up to the ~90s dial ceiling,
// leaving the app wedged in "starting") down to roughly one RTT. A dead-hop
// candidate simply loses the race instead of blocking the whole dial, so
// the app converges to a working route (single multihop at min_hops>=2,
// or direct at min_hops==1) instead of wedging.
//
// 1 = strictly-sequential behavior (a clean rollback switch). 0 (unset)
// is normalized to defaultParallelRouteSetup by SetDefaults; values above
// maxParallelRouteSetup are clamped. Losing / failed candidates' intermediate
// hops are marked suspect for a short TTL so the NEXT dial front-loads
// known-good hops (see suspectHopCache; composes with #4063).
ParallelRouteSetup int
// AwaitSetupListener, if non-nil, is used instead of opening a fresh
// listener on DmsgAwaitSetupPort. This lets callers reserve the port
// earlier in init (before the transport manager is ready) so peers
// dialing it during the router-init window don't hit "request has no
// associated listener" warnings.
AwaitSetupListener *dmsg.Listener
// AppLookup, if set, resolves a routing.Port to the originating
// app name. Used to tag rg-scoped log entries with app_name=<n>
// so 'cli proxy start --verbose' can scope to a session. nil
// means no lookup is performed and entries are emitted untagged
// (the existing behavior).
AppLookup func(routing.Port) (string, bool)
// DialHook, when non-nil, is invoked before each DialRoutes
// to let an operator-supplied routing policy adjust the
// per-dial knobs (MuxRoutes, MinHops) or refuse the dial
// entirely. See pkg/router/dial_hook.go for the contract and
// pkg/router/policy/ for the Starlark-backed implementation
// the visor wires up when conf.Routing.PolicyPerDial is set.
//
// When nil (the default) DialRoutes behaves identically to
// its pre-integration shape — zero-cost when no policy is
// configured.
DialHook DialHook
// PolicyOnControlPorts opts the routing policy (DialHook) BACK IN for
// control-plane destination ports (pty, dmsgctrl, setup, hypervisor RPC,
// …). Default false: those small noise-XK control channels bypass the
// policy entirely and take a plain base route, because its per-dial
// evaluation + warm-standby/reshape churn destabilizes their handshakes
// (the pty 32-leg-mux footgun). Set true to let the operator's policy
// manage control-plane routes too (advanced; e.g. a policy purpose-built
// for them). See effectiveDialHook / controlPlanePorts.
PolicyOnControlPorts bool
// MuxFEC advertises CapFEC on mux route groups so repair frames are
// striped alongside data (off by default; see visorconfig Routing.MuxFEC).
MuxFEC bool
// ExcludeSameLANHops, when true (the default the visor wires from
// routing.exclude_same_lan_hops), makes route calculation drop candidate
// INTERMEDIATE hops that sit on this visor's own local network — a peer
// reached over a private/RFC1918 endpoint, or (the NAT-hairpin case) a peer
// reached at this visor's own public IP or its /24. Routing a remote-
// destination path through a same-LAN peer adds a hop but no path diversity
// (same first-mile link, NAT and failure domain) and can fold the path back
// onto the source network; for a mux it also starves genuinely-diverse aux
// legs because the same-LAN peer wins on latency and is re-picked every
// rotation. Same-LAN peers remain fully usable as direct dial DESTINATIONS —
// only their use as intermediates is suppressed. The exclusion is computed
// from live transports via TransportManager.SameLANPeers(SelfPublicIP()).
ExcludeSameLANHops bool
// SelfPublicIP returns this visor's current public IP (from STUN), or nil
// when not yet known. Used only by the ExcludeSameLANHops filter to catch
// the NAT-hairpin case (a same-LAN peer reached at our own public IP). nil
// getter or nil result is fine — the private-IP and local-interface /24
// checks still apply.
SelfPublicIP func() net.IP
}
Config configures Router.
func (*Config) SetDefaults ¶
func (c *Config) SetDefaults()
SetDefaults sets default values for certain empty values.
type DatagramCipher ¶
type DatagramCipher struct {
// contains filtered or unexported fields
}
DatagramCipher is the per-direction AEAD state. Two instances per DatagramRouteGroup: one for sealing outbound, one for opening inbound. They share the same Noise-derived master (Wire-level rekey moves both ciphers atomically) but each tracks its own counter (outbound) or replay window (inbound).
Single-direction; thread-safe.
func NewDatagramCipher ¶
func NewDatagramCipher(masterKey [32]byte, role Role, bindPK cipher.PubKey, cfg *DatagramCipherConfig) (*DatagramCipher, error)
NewDatagramCipher constructs an outbound-or-inbound cipher from a 32-byte Noise master key, the route's role, the binding PK (the destination PK on outbound; the sender's PK on inbound — both go into AAD identically). Optional config overrides time/packet rekey limits; zero values fall back to defaults.
func (*DatagramCipher) MaxPlaintext ¶
func (dc *DatagramCipher) MaxPlaintext() int
MaxPlaintext returns the largest plaintext datagram body that Seal will accept. Surfaced via DatagramRouteGroup.MaxPayload to callers.
func (*DatagramCipher) NeedsRekey ¶
func (dc *DatagramCipher) NeedsRekey() bool
NeedsRekey reports whether the current epoch has exceeded the time-based or packet-count-based rekey trigger. The outer scheduling layer polls this and initiates a Noise rekey when it fires. Idempotent — calling does not change state.
func (*DatagramCipher) Open ¶
func (dc *DatagramCipher) Open(routeID uint32, sealed []byte) ([]byte, error)
Open verifies and decrypts a sealed datagram body. Input format matches Seal's output. Returns ErrAEADAuth on any of: tag mismatch, counter outside the replay window, counter already observed. Indistinguishable failure modes are intentional — leaking which failed would expose timing signal to an attacker.
On success the counter is recorded in the replay window so the same datagram cannot be replayed within the window.
func (*DatagramCipher) Rekey ¶
func (dc *DatagramCipher) Rekey(masterKey [32]byte, cfg *DatagramCipherConfig) error
Rekey replaces the AEAD epoch with one derived from a fresh master key. The counter resets to 0; the replay window clears. Both sides must perform the swap in lockstep (driven by the outer route-setup machinery, not by this type).
func (*DatagramCipher) Seal ¶
func (dc *DatagramCipher) Seal(routeID uint32, plaintext []byte) ([]byte, error)
Seal AEAD-encrypts plaintext and prepends the 8-byte counter. Output: [counter (8)] [ciphertext + 16-byte tag]. AAD is constructed from routeID + dc.destPK. Returns ErrCounterExhausted if the counter has reached the rekey limit — the caller should trigger Rekey and retry.
type DatagramCipherConfig ¶
DatagramCipherConfig tunes the rekey thresholds. Zero values use the package defaults (2 min / 2^32 packets).
type DatagramRouteGroup ¶
type DatagramRouteGroup struct {
// contains filtered or unexported fields
}
DatagramRouteGroup carries UDP-shaped datagrams across a route (or set of routes — multiplexing across multiple paths is a future enhancement, not present in Stage 2). Single-peer: WriteTo only accepts the configured remote address; ReadFrom always returns the same remote.
Lifecycle:
- Constructed via NewDatagramRouteGroup with rules + transports (the same setup-node plumbing that builds RouteGroups can populate either type — Stage 4 wires this).
- Handle(packet) is called by the router whenever a DatagramPacket arrives whose RouteID matches one of this group's rules. The packet's payload is queued on readCh and delivered to the next ReadFrom caller.
- WriteTo wraps the payload in a DatagramPacket (Stage 3 will also AEAD-seal it), picks a transport, and writes it.
- Close releases the readCh and marks the group dead. Idempotent.
func NewDatagramRouteGroup ¶
func NewDatagramRouteGroup(cfg *RouteGroupConfig, rt routing.Table, desc routing.RouteDescriptor, mLogger *logging.MasterLogger) *DatagramRouteGroup
NewDatagramRouteGroup constructs a DatagramRouteGroup. The returned group has no rules or transports yet — those are added by the caller (the setup-node-side plumbing, Stage 4) before the group is registered with the router for dispatch.
Use cfg=nil for defaults.
func (*DatagramRouteGroup) AEADAuthFailures ¶
func (dg *DatagramRouteGroup) AEADAuthFailures() uint64
AEADAuthFailures returns the count of inbound datagrams dropped due to AEAD authentication failure since group construction. Includes both forged datagrams and replay attempts; intentionally not differentiated to avoid leaking timing signal to an attacker. A rising counter under normal operation indicates either a forgery attempt in progress or a peer-side cipher desync (often a missed rekey).
func (*DatagramRouteGroup) AddRule ¶
func (dg *DatagramRouteGroup) AddRule(fwd routing.Rule, tp *transport.ManagedTransport, rv routing.Rule)
AddRule registers a (forward rule, transport, reverse rule) triple. Called by the setup-node integration in Stage 4. Index invariant: tps[i] is the transport for fwd[i]; rvs[i] is the matching consume rule on the receive side.
In Stage 2 this is the only way to populate the group; the router- side automatic plumbing comes later.
func (*DatagramRouteGroup) Close ¶
func (dg *DatagramRouteGroup) Close() error
Close releases the readCh and marks the group dead. Idempotent. In-flight ReadFrom unblocks with ErrClosed; subsequent WriteTo also returns ErrClosed.
func (*DatagramRouteGroup) Handle ¶
func (dg *DatagramRouteGroup) Handle(packet routing.Packet) error
Handle is called by the router (Stage 4) when a DatagramPacket arrives whose RouteID matches one of dg.rvs. The payload is queued on readCh and delivered to the next ReadFrom. If readCh is full, the datagram is dropped (atomic inboundDropped++) — faithful UDP semantics: a slow consumer loses packets.
Returns nil on successful queue, io.ErrClosedPipe if the group is closed (router should de-register the route in that case).
func (*DatagramRouteGroup) InboundDropped ¶
func (dg *DatagramRouteGroup) InboundDropped() uint64
InboundDropped returns the count of inbound datagrams dropped due to a full readCh since group construction. Surfaced for diagnostics; not used in the hot path.
func (*DatagramRouteGroup) IsAlive ¶
func (dg *DatagramRouteGroup) IsAlive() bool
IsAlive returns true if the group is open and recent writes have succeeded. Mirror of RouteGroup.IsAlive's shape so the Group interface stays narrow.
func (*DatagramRouteGroup) LocalAddr ¶
func (dg *DatagramRouteGroup) LocalAddr() net.Addr
LocalAddr returns destination address of underlying RouteDescriptor. Mirrors RouteGroup's convention — the descriptor is built from the dialer's perspective so .Dst is "this side."
func (*DatagramRouteGroup) MaxPayload ¶
func (dg *DatagramRouteGroup) MaxPayload() int
MaxPayload returns the largest plaintext datagram that WriteTo will accept. When AEAD is enabled (Stage 3 cipher installed via SetCiphers) the cap is MaxDatagramPayload minus the 24-byte per-datagram overhead (8-byte counter prefix + 16-byte Poly1305 tag) = 65511. When the cipher is unset the bypass mode returns the raw wire cap.
Apps consult this before sending to avoid the opaque-EMSGSIZE footgun.
func (*DatagramRouteGroup) ReadFrom ¶
ReadFrom delivers the next inbound datagram payload. Blocks until a payload is available, the read deadline fires, or Close runs. The returned addr is always the group's remote (single-peer model). Buffer too small for the datagram is a partial-read situation: copy what fits, drop the rest, return the truncated length and no error — exactly net.UDPConn's behavior on undersized buffers (subject to ENOBUFS-equivalent surfacing at the Stage 5 app-API layer).
func (*DatagramRouteGroup) RemoteAddr ¶
func (dg *DatagramRouteGroup) RemoteAddr() net.Addr
RemoteAddr returns source address of underlying RouteDescriptor.
func (*DatagramRouteGroup) SetCiphers ¶
func (dg *DatagramRouteGroup) SetCiphers(out, in *DatagramCipher)
SetCiphers installs the per-direction AEAD ciphers. Called by the setup-node integration in Stage 4 after the Noise handshake produces the master key.
Nil ciphers leave the AEAD layer bypassed (raw plaintext on the wire) — only used by Stage 2 unit tests; the production path MUST install ciphers before any traffic flows.
func (*DatagramRouteGroup) SetDeadline ¶
func (dg *DatagramRouteGroup) SetDeadline(t time.Time) error
SetDeadline sets both read and write deadlines.
func (*DatagramRouteGroup) SetReadDeadline ¶
func (dg *DatagramRouteGroup) SetReadDeadline(t time.Time) error
SetReadDeadline sets the read deadline.
func (*DatagramRouteGroup) SetWriteDeadline ¶
func (dg *DatagramRouteGroup) SetWriteDeadline(t time.Time) error
SetWriteDeadline sets the write deadline.
func (*DatagramRouteGroup) WriteTo ¶
WriteTo wraps data in a DatagramPacket and sends it over the first available transport. addr is checked against the group's remote (single-peer model); WriteTo on a wrong addr returns an error rather than silently routing elsewhere — apps that use a DatagramRouteGroup as a single-peer net.PacketConn are unlikely to hit this, but app frameworks that pool PacketConns might.
type DialAdjustment ¶
type DialAdjustment struct {
// MuxRoutes, when > 0, overrides DialOptions.MuxRoutes for
// this dial.
MuxRoutes int
// ForwardMuxRoutes and ReverseMuxRoutes are per-direction
// overrides for asymmetric mux. Zero falls back to MuxRoutes.
// Used by policies that want different upstream / downstream
// fan-out (e.g. ForwardMuxRoutes=1 + ReverseMuxRoutes=4 for
// a download-heavy workload).
ForwardMuxRoutes int
ReverseMuxRoutes int
// MinHops, when > 0, overrides DialOptions.MinHops for this
// dial.
MinHops int
// ForwardMinHops and ReverseMinHops are per-direction
// overrides for MinHops. Zero falls back to MinHops.
ForwardMinHops int
ReverseMinHops int
// Fallback drives the no-overlay paths:
// "drop" — refuse the dial with ErrDialPolicyDropped.
// "direct" — skip the route-finder, force the direct-
// transport short-circuit (UseExistingTpOnly=true
// + MinHops=1). If no direct transport exists,
// DialRoutes still fails — the script gets the
// all-or-nothing semantic it asked for.
// Any other value is ignored (the dial proceeds normally).
Fallback string
// Distribution, when Mode != DistributionUnset, overrides
// the route group's mux distribution strategy (which by
// default tracks the router's visor-wide muxMode). The
// policy layer parses the operator's descriptor string
// (Starlark RouteSpec.Distribution) into this struct so
// the router stays free of parser logic.
Distribution DistributionConfig
// RotationIntervalSeconds, when > 0, configures the route
// group's rotation servicePacketLoop to fire OnTick every N
// seconds. The hook gets the current leg snapshot and
// returns a RotationAction (drop legs / add leg / no-op),
// which the route group applies. Zero (default) disables
// rotation. Set by policies that want continuous bandwidth-
// spreading rotation across the eligible-peer set.
RotationIntervalSeconds int
// AvoidDirect, when true, makes the direct-dial fast path
// (VStreamMux / sky_forward_conn) skip the direct transport
// even when one exists — the dial falls through to the
// overlay route-setup path, which is what makes mux /
// min_hops / rotation policies meaningful.
//
// Set by the policy layer implicitly when the script returned
// MinHops / ForwardMinHops / ReverseMinHops >= 2 — `min_hops=2`
// already says "no direct" (direct is 0 intermediates), and
// the alternative would be a separate avoid_direct knob that
// duplicates the same intent. The direct-dial bridge in
// wireDirectDialPolicyHook surfaces ErrPolicyAvoidDirect when
// this is true; the caller (skywire_networker.tryDirectDial)
// treats that as a normal direct-dial failure and falls
// through to route setup.
AvoidDirect bool
}
DialAdjustment is the hook's return value. Each field is a "override this if non-zero" hint; zero values mean "no change for this knob." Fallback can be set to "drop" to refuse the dial entirely.
type DialError ¶
DialError identifies which remote PK failed to dial during route setup. The setup-node metrics layer inspects this to avoid tripping the circuit breaker on the destination when the failure was actually on the source side: id_reservation calls MakeMap which dials every hop (source, dest, intermediaries), and a flaky source visor would otherwise poison the destination's breaker, blocking all requests to a healthy destination.
func (*DialError) DialFailedPK ¶
DialFailedPK reports the remote PK that could not be dialed. The setupmetrics collector probes for this via an anonymous interface to stay free of a router import cycle.
type DialHook ¶
type DialHook interface {
// BeforeDial is invoked synchronously before route setup. Its
// return value adjusts the dial's opts in-place. Returning the
// zero DialAdjustment + nil error means "no override; proceed
// with the caller's opts unchanged."
//
// Implementations MUST return quickly — DialRoutes blocks on
// this call before the route-finder query begins. Budget is
// the same as the policy's per-call timeout (default 50ms).
BeforeDial(ctx context.Context, info DialInfo) (DialAdjustment, error)
}
DialHook is called by DialRoutes before route setup. Returns adjustments that the router applies to the dial's opts.
Implementations live in pkg/router/policy and elsewhere. The interface here keeps pkg/router free of the Starlark machinery.
type DialInfo ¶
type DialInfo struct {
AppName string
PeerPK cipher.PubKey
LPort routing.Port
RPort routing.Port
// CLIOverrides surfaces any DialOptions values the caller
// (CLI / app) populated before BeforeDial fires, so the
// script can honor or override them. Keys mirror DialOptions
// field names in snake_case (mux_routes, min_hops,
// forward_mux_routes, etc.). Only non-zero values appear.
// The policy's adjustment runs AFTER this is captured, so
// the script gets a true "what did the operator pass" view.
CLIOverrides map[string]string
// IsDirectDial is true when the hook is being invoked from
// the sky_forward_conn / VStreamMux direct-dial path (the
// short-circuit that uses an existing direct transport
// instead of going through the route-finder). False for the
// overlay paths (DialRoutes, PingRoute). Lets scripts apply
// killswitch-style rules to direct dials too (e.g.
// "vpn-client must never dial direct over dmsg"). Surfaced
// as `ctx.is_direct_dial` in Starlark.
IsDirectDial bool
// TransportKind is the network type ("stcpr"/"sudph"/"stcp"/
// "dmsg") of the transport the direct dial would use.
// Populated only when IsDirectDial is true. Empty for
// overlay dials. Surfaced as `ctx.transport_kind` so a
// script can branch on the kind even before the overlay's
// candidates are available.
TransportKind string
}
DialInfo summarizes the dial for the hook. It's a flat struct rather than passing DialOptions so the hook stays decoupled from DialOptions' field churn.
type DialOptions ¶
type DialOptions struct {
MinForwardRts int
MaxForwardRts int
MinConsumeRts int
MaxConsumeRts int
Retries int
UseExistingTpOnly bool // If true, only use routes through existing transports, don't create new ones
EnsureDirectTransport bool // If true, create a direct transport to the destination if none exists, then dial direct-only (the `--direct` foot-gun fix). Implies a 1-hop, route-finder-bypassing dial that self-heals when the transport drops.
TransportID uuid.UUID // If set, use this specific transport (skips route calculation for direct transports)
ForwardHops []routing.Hop // If set, use these hops for forward path (skips route calculation)
ReverseHops []routing.Hop // If set, use these hops for reverse path (skips route calculation)
MuxRoutes int // Number of parallel routes to establish (0 or 1 = single route, >1 = mux)
ExcludeTransportIDs []uuid.UUID // Transport IDs to exclude from route calculation (for mux)
// ExcludeRemoteTransportIDs is the DESTINATION-side analog of
// ExcludeTransportIDs: transport IDs the REVERSE path must not leave the
// destination over.
//
// Why it exists: the setup node hands the destination `respEdge.Forward`,
// which is the ForwardRule generated for the REVERSE route's first hop
// (setupnode.go GenerateRules — fwdRules is keyed by each route's
// Hops[0].From, and the reverse route starts at the destination). So the
// destination's route group registers one transport per leg, and that
// transport is exactly `Reverse[0].TpID` of the plan the initiator dialed.
// appendRouteToGroup then refuses any leg whose transport is already in
// that set ("refusing to append mux leg over transport %s already in the
// group"), because two legs over one link break the mux data plane (#3954).
//
// The forward and reverse paths are ranked INDEPENDENTLY (pickDisjointPath),
// and a 0-intermediate direct reverse carries no intermediates to exclude —
// so ExcludeIntermediatePKs cannot keep a new leg's reverse off the direct
// transport the destination is already using for the primary leg. Without
// this field every aux mux leg to such a destination is planned, dialed
// through the setup node (full ID reservation + intermediary rule install)
// and then refused: the churn measured on the live setup nodes.
//
// Populated by the aux-mux-leg planners from the group's live legs
// (RouteGroup.remoteLegTransportIDs). Unset for every non-mux dial, which
// is therefore byte-identical to before.
ExcludeRemoteTransportIDs []uuid.UUID
// ExcludeFirstHopPeers / ExcludeFirstHopIPs are the diversify dial's
// PEER-level first-hop exclusion: the remote visor PKs (and, where the
// underlying transport exposes one, the remote IPs) that a sibling tunnel to
// the same exit already leaves over.
//
// ExcludeTransportIDs alone is not enough, because one peer commonly answers
// on SEVERAL transports: this visor holds an stcpr, a squicr and a sudph
// transport to the same exit host. Excluding only the sibling's transport ID
// left the other two as "free first hops", and a diversify dial took the
// squicr twin — same host, same NIC, same physical path — so the two tunnels
// shared one link and split it instead of summing (measured 2026-09-17: 50 MB
// up 2.57 MB/s, down 5.64 on a 29/21 MB split, against 8.36-8.49 down on two
// genuinely disjoint routes). Excluding the PEER makes every transport to
// that peer unavailable, so the candidates are the other intermediates — the
// disjointness the RFC promises.
//
// The IP list catches the same host reached under a different PK. Like the
// transport-ID exclusion these are a soft preference: a dial that can find
// nothing else still falls back to a shared first hop rather than failing.
ExcludeFirstHopPeers []cipher.PubKey
ExcludeFirstHopIPs []string
// Datagram, when true, asks the dial to build a faithful-UDP
// DatagramRouteGroup sibling over the established route (#2607).
// The route's reliable RouteGroup is still set up exactly as
// usual (and is what carries the one-time Noise handshake whose
// ChannelBinding keys the datagram AEAD); the datagram sibling is
// built alongside it once the handshake completes. The accept
// side opts in independently via RegisterDatagramPort — both ends
// must locally intend datagram mode for it to work.
Datagram bool
// MinHops, when > 0, is the per-call minimum hop count constraint.
// Overrides Config.MinHops for this dial only — needed by callers
// that want a non-direct path even when the visor's global
// min_hops is 1 (e.g. `cli visor ping mux-bw --min-hops 2`, which
// must force every dial through an intermediate to test the
// "mux via intermediates > direct" bandwidth hypothesis without
// requiring the operator to bump visor-global config).
//
// Setting MinHops >= 2 also suppresses the direct-transport
// fast path inside DialRoutes (the existing
// "if r.isTpdExist(rPK) { MinHops = 1 }" downgrade is skipped),
// since the caller has explicitly asked NOT to use direct.
//
// 0 = inherit Config.MinHops (legacy / single-route callers).
MinHops int
// ExcludeIntermediatePKs lists intermediate-visor PKs that route-
// finder paths must NOT contain. Source + destination are never
// excluded (they're endpoints, not intermediates). The mux loop
// populates this with the intermediates of routes already
// established so that subsequent routes are constructed through
// different intermediate paths — disjoint in the intermediate-PK
// set sense. Applied in both the HTTP route-finder path (post-
// filter response) and the local route-calc fallback (filter
// candidates).
//
// Disjoint-intermediate routing is AUTOMATIC for the mux loop
// (MuxRoutes > 1). Callers that want overlapping intermediates
// must dial with explicit ForwardHops/ReverseHops (which bypass
// the route-finder entirely). This field is also exposed so
// callers outside the mux loop can pre-populate exclusions if
// they have constraints to express.
ExcludeIntermediatePKs []cipher.PubKey
ExcludeDMSG bool // Exclude DMSG transports (for mux — DMSG is a relay, not suitable for multiplexing)
// DiversifyTransports opts this dial into visor-side auto-
// diversification for bandwidth aggregation (docs/mux_aggregation_rfc.md
// step 3). When set, DialRoutes looks up THIS visor's currently-live
// route groups to the SAME destination PK:port and seeds
// ExcludeTransportIDs / ExcludeIntermediatePKs with their first-hop
// transports + intermediates, so a new tunnel to an exit leaves over a
// DIFFERENT first-hop transport than the tunnels already established to
// it — their throughputs then SUM instead of splitting one link.
//
// It is bounded and safe by construction: the exclusions are added ONLY
// when >= 1 sibling route group to the exact same dst already exists (the
// skysocks multi-tunnel "tunnel 2..N" case). A lone dial (tunnel 1, or
// any app that dials a dst once) finds no sibling and is byte-identical
// to today. It reuses the mux's own disjoint machinery, but across route
// groups rather than across legs within one group. Set by the
// skysocks-client for its extra tunnels; unset everywhere else, so
// single-dials, control-plane forwards and same-LAN/direct dials are
// untouched. Falls back gracefully — if no disjoint transport is free the
// dial proceeds on a shared path (a shared tunnel beats no tunnel).
DiversifyTransports bool
// RequireDisjointFirstHop turns the diversify PREFERENCE above into a
// REQUIREMENT: rather than conceding a shared first hop when nothing
// disjoint is free, the dial fails with ErrNoDisjointFirstHop. Only
// meaningful together with DiversifyTransports (without siblings there is
// nothing to be disjoint from, and the dial proceeds as usual).
//
// It exists so a caller that is GROWING a pool of sibling tunnels can tell
// "the topology has more disjoint paths to offer" from "it has none left".
// The skysocks standby pool dials with it set and stops filling on the
// first ErrNoDisjointFirstHop — the tunnel-level twin of the mux's
// "no disjoint path available right now; settling at current degree".
// A shared extra tunnel aggregates nothing, so for that caller a shared
// path is not a lesser success but a wrong answer.
RequireDisjointFirstHop bool
// TunnelRole records what the DIALER intends this route group to be:
// "active" (it carries streams) or "standby" (it is held open, measured
// and ready to take over). Set by the skysocks client; empty everywhere
// else, and empty is the only value a non-tunnel route group ever has.
// Surfaced as MuxInfo.TunnelRole. It is the dialing end's own label — the
// accepting end (the exit) has no idea which of its peers' tunnels are in
// standby, so this field is always empty there.
TunnelRole string
// AdoptReservePort, when set, turns the dial into an ADOPTION: instead of
// setting up a new route, DialRoutes takes the leg reserve (a split's
// standalone group, leg_split.go) toward the same peer whose far-end port
// is this one, re-points its chain at the dial's own descriptor and runs
// the ordinary handshake on it (leg_adopt.go). It fails — and the caller
// dials the usual way — when no such reserve exists or the peer does not
// answer. 0 = an ordinary dial.
AdoptReservePort routing.Port
// Distribution overrides the route group's per-packet
// distribution strategy when set (Mode != DistributionUnset).
// Populated either by a routing-policy script (see
// DialAdjustment.Distribution) or directly by a CLI caller
// constructing DialOptions. Zero-value means "use the
// router's visor-wide muxMode default."
Distribution DistributionConfig
KeepAlive time.Duration // Route keepalive (0 = DefaultRouteKeepAlive). Routes idle longer expire.
// ParallelRouteSetup, when > 0, overrides Config.ParallelRouteSetup
// for this dial only — the number of candidate route groups whose
// setup is raced concurrently (first to complete its handshake wins).
// 1 forces sequential setup for this dial; 0 inherits the visor
// config. See Config.ParallelRouteSetup.
ParallelRouteSetup int
// AppName is the originating app's name. Set by appnet's
// DialContextWithOptions when the caller threaded a context
// carrying it (RPCIngressGateway.Dial uses appnet.WithAppName).
// router-side: scopedLog prefers this over AppLookup-by-port,
// since DialRoutes is invoked with an ephemeral source port that
// isn't the app's registered SetAppPort value.
AppName string
// ForwardMinHops / ReverseMinHops are per-direction overrides for
// MinHops. When > 0 they take precedence over the symmetric
// MinHops field for THAT direction only. Use case: bandwidth-
// asymmetric workloads (HTTP GET = tiny upstream, bulk downstream)
// where the user wants e.g. direct stcpr for the forward leg but
// multi-hop for the reverse-direction bulk payload.
//
// 0 (the default) means "inherit MinHops" — back-compat for
// callers that only care about symmetric routes.
//
// Resolution at use site (see resolveDirMinHops): if the per-
// direction override is > 0 it wins; else MinHops; else
// Config.MinHops; else the global default.
ForwardMinHops int
ReverseMinHops int
// ForwardMuxRoutes / ReverseMuxRoutes are per-direction overrides
// for MuxRoutes. When > 1 they take precedence over the symmetric
// MuxRoutes field for THAT direction's route count.
//
// Use case: the operator's "direct upstream + multi-hop downstream"
// test — set ForwardMuxRoutes=1 and ReverseMuxRoutes=4 to open a
// single forward leg (the GET request rides one TCP socket) while
// the reverse direction aggregates the bulk payload across 4
// multi-hop legs. The data plane already supports rg.fwd[] and
// rg.rvs[] being different lengths (the read path is by route-id
// lookup, not slice index), so this is purely a setup-side change.
//
// 0 (the default) means "inherit MuxRoutes" for that direction.
// When the caller wants strictly N=1 on a side they should set it
// to 1 explicitly (not 0) since 0 still falls through to MuxRoutes.
ForwardMuxRoutes int
ReverseMuxRoutes int
// RotationIntervalSeconds, when > 0, instructs the route group
// to fire its rotation hook every N seconds after setup. The
// hook can drop legs and/or add new ones, enabling bandwidth-
// spreading policies that rotate across the eligible-peer set.
// Populated by the policy layer (DialAdjustment) from
// RouteSpec.RotationIntervalSeconds; zero = no rotation.
RotationIntervalSeconds int
// contains filtered or unexported fields
}
DialOptions describes dial options.
func DefaultDialOptions ¶
func DefaultDialOptions() *DialOptions
DefaultDialOptions returns default dial options. Used by default if nil is passed as options.
func (*DialOptions) AnyMinHopsConstraint ¶
func (o *DialOptions) AnyMinHopsConstraint() bool
AnyMinHopsConstraint reports whether the caller has set any min-hops constraint (symmetric or per-direction). Used at the direct-tp downgrade site in DialRoutes: if either direction wants multi-hop, the "transport-exists → drop MinHops to 1" optimization should be suppressed (mirroring the existing opts.MinHops > 1 check).
func (*DialOptions) EffectiveMinHops ¶
func (o *DialOptions) EffectiveMinHops(forward bool) int
EffectiveMinHops returns the per-direction min-hops constraint: forward=true → ForwardMinHops if > 0 else MinHops; forward=false → ReverseMinHops if > 0 else MinHops. Callers (the route-finder query site + the local-BFS fallback) read this once per direction to support bandwidth-asymmetric workloads where forward (small uplink) can use direct/short paths while reverse (bulk downlink) is forced onto multi-hop.
Returns 0 when opts is nil or both per-direction + symmetric fields are 0 — preserves the caller's "inherit Config.MinHops" semantic.
func (*DialOptions) EffectiveMuxRoutes ¶
func (o *DialOptions) EffectiveMuxRoutes(forward bool) int
EffectiveMuxRoutes returns the per-direction mux-route count: forward=true → ForwardMuxRoutes if > 0 else MuxRoutes; forward=false → ReverseMuxRoutes if > 0 else MuxRoutes. Callers (establishMuxRoutes, the dial-shape logger) read this once per direction to support asymmetric mux topologies — most commonly 1 forward + N reverse for download-heavy workloads.
Returns 0 when opts is nil or all relevant fields are 0; preserves the "single route" default behavior.
type DistributionConfig ¶
type DistributionConfig struct {
// Mode selects the per-packet algorithm. DistributionUnset
// means "no override."
Mode DistributionMode
// Weights are the operator-supplied fractional weights for
// DistributionWeighted. Must be the same length as the route
// group's leg count; values are normalized into an integer
// schedule by the selector. Empty for non-weighted modes.
Weights []float64
// SizeThreshold is the payload-size boundary for
// DistributionSizeThreshold. Packets larger than this go to
// leg 0 (assumed to be the wider pipe — the first leg the
// route-finder returned, ranked by latency); smaller packets
// round-robin across the remaining legs.
SizeThreshold int
// DSCPThreshold is the IPv4 DSCP boundary for
// DistributionDSCPPriority. Packets with DSCP >= this value
// take leg 0; others round-robin across the rest. Common
// values: 0x2E (EF — VoIP), 0x10 (AF — interactive).
DSCPThreshold int
}
DistributionConfig drives the per-packet leg-selection strategy for a mux-enabled route group. Set on DialAdjustment (per-dial, from a routing-policy script) or on DialOptions (per-call from a CLI flag); zero-value (Mode == DistributionUnset) means "no override — use the router's visor-wide muxMode."
See pkg/router/policy/distribution.go for the descriptor grammar that policy scripts emit.
type DistributionMode ¶
type DistributionMode int
DistributionMode enumerates the per-packet distribution strategies the operator can pick from. Names match the vocabulary the RFC's Starlark descriptor parses to.
const ( // DistributionUnset means "no policy override." The route // group uses the router's visor-wide muxMode (currently // WeightModeAuto: latency-weighted with round-robin fallback). DistributionUnset DistributionMode = iota // DistributionRoundRobin distributes packets evenly across // all legs, ignoring latency. Same effect as the existing // WeightModeEqual. DistributionRoundRobin // DistributionAuto picks WeightModeAuto explicitly — useful // when a script wants to override an operator's globally- // configured Equal mode back to latency-weighted for one app. DistributionAuto // DistributionWeighted uses the operator-supplied fractional // weights in Weights[]. Length must match the leg count. DistributionWeighted // DistributionSizeThreshold routes packets by payload size: // > SizeThreshold goes to leg 0, ≤ SizeThreshold round-robins // across the rest. Useful for bulk + control mixes (VPN, // terminal multiplexing) where large packets should claim // the wider pipe and small packets stay on the low-latency // leg. DistributionSizeThreshold // DistributionSticky5Tuple pins each flow (identified by the // IPv4 5-tuple — src/dst IP + src/dst port + protocol) to a // single leg for the flow's lifetime. Same flow → same leg // every packet, deterministic. Meaningful for VPN traffic // (vpn-client, vpn-server) where the payload is an IPv4 // packet; for other apps the selector falls back to hashing // the payload prefix (still deterministic by content but no // flow semantics). Useful for TCP-over-overlay where leg // reordering hurts congestion control. DistributionSticky5Tuple // DistributionLatencyAdaptive picks the leg with the lowest // current transport-layer latency for every packet. Differs // from DistributionAuto (latency-weighted schedule, built // every Rebuild cycle): adaptive evaluates per-packet, so a // leg whose RTT spikes mid-session loses traffic // immediately. Cost: one linear scan over the leg array per // packet (typically <=4 legs, sub-µs). DistributionLatencyAdaptive // DistributionDSCPPriority routes packets by the IPv4 DSCP // field (upper 6 bits of the ToS byte at IP-header offset 1). // Packets with DSCP >= the configured threshold (commonly // 0x2E for EF / VoIP, or 0x10+ for AF / interactive) take // leg 0 — the low-latency leg. Lower-priority packets // round-robin across the rest. Like sticky:5tuple, meaningful // for VPN apps; non-IPv4 payloads default to round-robin. DistributionDSCPPriority // DistributionCapacity spreads packets in proportion to each // leg's recently-measured throughput (bytes carried since the // last weight rebuild), with an exploration floor so every // live leg keeps carrying (and so keeps being measured). It // bootstraps as equal round-robin until legs have moved bytes. // Unlike DistributionAuto (weights by inverse LATENCY, which // dumps a bulk flow onto the single lowest-latency leg and // starves higher-latency-but-fat legs), capacity weighting // FILLS each leg toward its throughput — the right strategy // for aggregating bandwidth across a disjoint multi-leg route. // This is the adaptive default's bulk-spread mode. DistributionCapacity // DistributionECF is the predictive Earliest-Completion-First // hold-back scheduler (router.WeightModeECF). Unlike // DistributionCapacity, which sprays a fraction of frames onto // slower legs in proportion to their goodput — and so keeps // head-of-line-stalling the in-order reorder buffer on those slow // legs — ECF sends on the fastest leg while it has send capacity // and only spills onto a slower leg when the slow leg would deliver // its frame sooner than the fast leg can drain its own backlog. It // is the mode that actually aggregates across heterogeneous legs // without paying the slow-leg HoL cost. Per-packet, O(legs). DistributionECF // DistributionOTIAS is the Out-of-order Transmission for In-order // Arrival scheduler (router.WeightModeOTIAS). It reuses ECF's per-leg // estimators but assigns each frame to the leg whose estimated ARRIVAL // (backlog drain time + one-way delay) is soonest, deliberately handing // later frames to slower-but-idle legs; the reorder buffer restores order. DistributionOTIAS // DistributionSTMS is the Slide Together Multipath Scheduler // (router.WeightModeSTMS). It keeps the head of the stream on the fast // leg (filling its send window first) and slides later data onto the // soonest-arriving slower leg so pieces converge in order — without ECF's // hold-back decline once a leg is in the active set. DistributionSTMS )
func (DistributionMode) String ¶
func (m DistributionMode) String() string
String returns the stable label for a DistributionMode. Used by router-side log fields so operator-facing log lines say "round-robin" instead of "1".
type DstTransportOracle ¶
type DstTransportOracle interface {
DstTransports(ctx context.Context, src, dst cipher.PubKey) ([]*transport.Entry, error)
}
DstTransportOracle fetches a destination visor's OWN transport list authoritatively from the destination. It is the router-facing seam for the RSN-oracle 2-hop route path: the router calls DstTransports(src, dst) and computes the 2-hop route locally from the result. The visor implements it (typically via fetchDstTransportsViaOracle / NewDmsgRSNOracle) since it owns the RSN RPC connection and the S->D delivery transport; the router stays decoupled from both. Declared here (unbuilt-tagged) so the Router interface referencing it compiles on every target.
func NewDmsgRSNOracle ¶
func NewDmsgRSNOracle(dmsgC *dmsg.Client, setupNodes []cipher.PubKey, log *logging.Logger) DstTransportOracle
NewDmsgRSNOracle builds the production DstTransportOracle: RSN signs over dmsg, query delivered to the destination over dmsg. setupNodes is the trusted-RSN list (conf.EffectiveRouteSetupNodes). Returns nil when no setup nodes are configured, so the caller can leave the oracle unset (path stays inert).
func NewDstTransportOracle ¶
func NewDstTransportOracle(rsnRPC *rpc.Client, deliverer TransportQueryDeliverer) DstTransportOracle
NewDstTransportOracle builds the reference DstTransportOracle from an RSN RPC client and a query deliverer.
type EmbeddedSetupNode ¶
type EmbeddedSetupNode interface {
// CreateRouteGroup creates a route group using the embedded setup-node's dmsg client.
CreateRouteGroup(ctx context.Context, biRt routing.BidirectionalRoute) (routing.EdgeRules, error)
// PK returns the public key of the embedded setup-node.
PK() cipher.PubKey
// DmsgClient returns the dmsg client used by the embedded setup-node.
DmsgClient() *dmsg.Client
}
EmbeddedSetupNode is an interface for embedded route setup-nodes that can create route groups locally without dialing a remote setup-node.
type ForwardQueue ¶
type ForwardQueue struct {
TpID uuid.UUID `json:"tp_id"`
TpType string `json:"tp_type,omitempty"`
Remote cipher.PubKey `json:"remote_pk,omitempty"`
Queue int `json:"queue"`
Capacity int `json:"capacity"`
// QueuedBytes is what the frames waiting in it currently hold, against
// forward.queue_bytes when that bound is set.
QueuedBytes int64 `json:"queued_bytes"`
Sent uint64 `json:"sent"`
// The named drop reasons, one counter each.
DropsQueueFull uint64 `json:"forward_drop_queue_full"`
DropsWriteTimeout uint64 `json:"forward_drop_write_timeout"`
DropsWriteError uint64 `json:"forward_drop_write_error"`
}
ForwardQueue is one next-hop transport's transit write queue: what this visor is relaying for other people's routes, and what it had to drop. A climbing DropsQueueFull or DropsWriteTimeout on one transport with healthy transfers elsewhere is the expected shape of a single wedged transit peer — which is exactly what it must look like, since that peer used to take the whole dataplane down with it.
type Group ¶
Group is the narrow interface every route-group variant shares. Stream-shaped (RouteGroup, net.Conn) and datagram-shaped (DatagramRouteGroup, net.PacketConn) both satisfy it. Read/Write vs ReadFrom/WriteTo deliberately stay on the concrete types — keeping the shared interface narrow avoids leaking stream vs datagram semantics into call sites that only care about lifecycle.
type HealthCheckArgs ¶
type HealthCheckArgs struct{}
HealthCheckArgs is an empty struct for the health check call.
type HealthCheckReply ¶
type HealthCheckReply struct {
Status string `json:"status"`
// Caps lists the request shapes this node understands, the same list the
// Capabilities RPC returns — carried here too so a health probe doubles as
// the negotiation for a client that is already making one.
Caps []string `json:"caps,omitempty"`
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
Date string `json:"date,omitempty"`
}
HealthCheckReply is returned by the HealthCheck RPC method.
type IDReserver ¶
type IDReserver interface {
io.Closer
fmt.Stringer
// ReserveIDs reserves route IDs from the router clients.
// It uses an internal map to know how many ids to reserve from each router.
ReserveIDs(ctx context.Context) error
// PopID pops a reserved route ID from the ID stack of the given public key.
PopID(pk cipher.PubKey) (routing.RouteID, bool)
// TotalIDs returns the total number of route IDs we have reserved from the routers.
TotalIDs() int
// Client returns a router client of given public key.
Client(pk cipher.PubKey) *Client
// ReturnToPool returns connections to a pool instead of closing them.
ReturnToPool(pool *ClientPool)
}
IDReserver reserves route IDs from remote routers. It takes in a slice of paths where each path is a slice of hops and each hop has src and dst public keys and also a transport ID.
func NewIDReserver ¶
func NewIDReserver(ctx context.Context, dialer network.Dialer, pool *ClientPool, paths [][]routing.Hop) (IDReserver, error)
NewIDReserver creates a new route ID reserver from a dialer and a slice of paths. The exact number of route IDs to reserve from each router is determined from the slice of paths. If pool is non-nil, connections are borrowed from the pool instead of dialed fresh.
func ReserveRouteIDs ¶
func ReserveRouteIDs(ctx context.Context, log logrus.FieldLogger, dialer network.Dialer, pool *ClientPool, route routing.BidirectionalRoute) (idR IDReserver, err error)
ReserveRouteIDs dials to all routers and reserves required route IDs from them. The number of route IDs to be reserved per router, is extrapolated from the 'route' input.
type IntakeStats ¶
type IntakeStats struct {
// UnknownPacketTypes counts packets dropped as ErrUnknownPacketType, by
// type name. Non-zero means a peer sends a type this build does not
// route, or a transport lost its route-ID-0 handler (#4725).
UnknownPacketTypes map[string]int64 `json:"unknown_packet_types,omitempty"`
// ControlPlaneToRouter counts route-ID-0 control packets that reached
// the router instead of being intercepted on the transport, by type.
ControlPlaneToRouter map[string]int64 `json:"control_plane_to_router,omitempty"`
// StaleRouteDrops counts frames for rules that no longer exist (normal
// during teardown; a climbing count on an idle visor is not).
StaleRouteDrops int64 `json:"stale_route_drops"`
// RouteGroupQueues is the two inbound queues of every active route group:
// the app queue (queue/capacity) and the intake queue between the router's
// shared read loop and the group's own worker (intake_*).
//
// An app queue at capacity is an app that stopped reading. That used to
// block the shared loop — up to 30 s per packet, stalling every transport
// on the visor; now it backs up into that group's intake queue instead,
// and a climbing intake_drops is where the loss shows up. Non-zero
// intake_drops on one group and healthy transfers elsewhere is the
// expected shape of a single wedged app.
RouteGroupQueues []RouteGroupQueue `json:"route_group_queues,omitempty"`
// ForwardQueues is the TRANSIT write side: one entry per next-hop
// transport this visor is relaying frames to. Non-zero drops name a peer
// that stopped draining; they used to be a visor-wide freeze instead
// (see router_forward.go).
ForwardQueues []ForwardQueue `json:"forward_queues,omitempty"`
}
IntakeStats is the router's inbound-path view.
type LegChange ¶
LegChange describes the mutation that triggered the OnLegChange callback. Event is "added" or "dropped"; LegIndex is the leg that changed (the new leg's index for "added", the dropped leg's last-known index for "dropped").
type LegChangeHook ¶
type LegChangeHook interface {
OnLegChange(info DialInfo, legs []LegInfo, change LegChange) DistributionConfig
}
LegChangeHook is an optional hook fired by the route group whenever its set of mux legs mutates — a new leg added via the establishment loop or appendRouteToGroup, or a leg dropping due to a transport close. The hook can return a new DistributionConfig to re-balance the per-packet selector against the updated leg count; returning the zero value (Mode == DistributionUnset) leaves the existing distribution in place.
The route group calls OnLegChange synchronously from its leg- mutation paths so the schedule rebuild stays consistent with the leg set. Implementations must return quickly — the call budget mirrors the dial-time policy timeout (step ceiling).
type LegInfo ¶
type LegInfo struct {
Index int
Kind string
// TransportID is the leg transport's stable UUID
// (ManagedTransport.Entry.ID) as a string. Unlike Index — which
// shifts when legs are dropped/added and the tps[] slice compacts
// — TransportID identifies the same underlying transport across
// ticks, so an on_tick policy can key per-leg state (e.g. an EWMA
// of latency) by it and smooth signals over time. Empty for a nil
// leg slot.
TransportID string
LatencyMs int
Alive bool
// Standby is true when the leg is a WARM STANDBY: rules installed and
// kept alive, but not selected for sending. Lets an on_tick policy see the
// current gate state so it can promote a warm spare instead of a cold add,
// or demote a leg it wants to keep warm. See docs/warm_standby_legs_rfc.md.
Standby bool
// Direct is true when this leg is the DIRECT (forward-only) leg of a
// UNIDIRECTIONAL route group: its transport goes straight to a route-group
// endpoint (1 hop, no intermediary) and it carries only the forward/upload
// direction. Sourced from routeMux.legIsDirectTp, which is false for a
// non-directional (symmetric) group — there every leg carries both
// directions. Lets the adaptive on_tick maintain a floor of active REVERSE
// (download) legs independent of the direct leg, so a download never falls
// back onto warm-standby legs for want of an active reverse path.
Direct bool
// Flipped is the route group's current unidirectional flip state (the same
// value on every leg of the group), sourced from routeMux.dirState. Because a
// leg's send-side direction is an ASSIGNMENT the flip controller can swap —
// not an intrinsic property — the download-direction class is Direct ==
// Flipped (unflipped: download rides the multihop legs; flipped: it rides the
// direct leg). The adaptive on_tick keeps its active-download floor on that
// class, whichever way the flip currently sits. Always false for a
// non-directional (symmetric) group.
Flipped bool
// SentBytes / RecvBytes are the leg transport's cumulative
// payload-byte counters (ManagedTransport.GetBandwidth). Lets a
// policy's on_tick rotate a leg after it carries a byte threshold
// rather than purely on elapsed time.
SentBytes uint64
RecvBytes uint64
// Retransmits is how many SACK retransmit packets this leg has carried.
// A high retransmits-to-traffic ratio marks a lossy leg, letting an
// on_tick policy shed lossy intermediates (not just slow ones).
Retransmits uint64
// Hops is the intermediate-PK chain this leg traverses (forward,
// src->dst, endpoints excluded). Lets on_tick build a precise
// ExcludeHops set so a rotated-in leg avoids the rotated-out leg's
// intermediates. Empty for a direct (0-intermediate) leg.
Hops []string
}
LegInfo describes one mux leg's current state. Index is the position in the route group's tps[] slice; Kind is the transport type ("stcpr" / "sudph" / "stcp" / "dmsg" — though DMSG is filtered out of mux groups at setup so it shouldn't appear here). LatencyMs is the most recent measurement, 0 for "unknown." Alive reflects whether the transport is currently serving (not closed / nil).
type LegStats ¶
type LegStats struct {
// Index in the rg's tps[] slice. Stable for the rg's lifetime
// (transports are appended, never re-ordered) so the index is
// also a stable identifier for runtime add/remove operations.
Index int
// SentBytes / SentPackets are what THIS leg carried outbound.
SentBytes uint64
SentPackets uint64
// RecvBytes / RecvPackets are what THIS leg carried inbound.
// Resolved from the reverse-rule's KeyRouteID at packet-handle
// time, so it reflects what actually arrived on this transport,
// not what the peer said it sent.
RecvBytes uint64
RecvPackets uint64
// PayloadBytes is the UNIQUE in-order payload this leg delivered (each seq
// counted once, on the leg it first arrived on) — retransmits/duplicates
// excluded, so the per-leg values sum to the transfer size and give a
// confound-free per-direction attribution (which legs carried the download vs
// the upload), unlike RecvBytes which includes retransmit/duplicate inflation.
PayloadBytes uint64
// DupBytes is inbound DUPLICATE data this leg carried (seqs already
// delivered/buffered on arrival) — the peer's spurious-retransmit waste,
// which selectFastestTransport concentrates on the fastest leg. RepairBytes
// is inbound FEC repair frames (deliberate overhead). Together with
// PayloadBytes they decompose RecvBytes so "why is this (standby) leg
// receiving" is answerable from telemetry.
DupBytes uint64
RepairBytes uint64
// Retransmits is how many SACK retransmit packets THIS leg has
// carried. A high retransmits:sentPackets ratio marks a lossy leg —
// the signal a routing policy needs to shed lossy intermediates and
// the scheduler needs to deweight them.
Retransmits uint64
// GoodputUpBps / GoodputDownBps are this leg's recent goodput split by
// direction — the EWMA of the SENT (up) and RECV (down) byte deltas per
// second over the telemetry refresh window (~1s for the status page).
// Distinct from the cumulative SentBytes/RecvBytes counters above: they
// are the RATE each direction is moving now, not the lifetime total. 0
// until a second sample lands. Sampled in snapshotLegs.
GoodputUpBps float64
GoodputDownBps float64
// GoodputBps is the combined (up+down) recent goodput, retained as the
// sum of GoodputUpBps+GoodputDownBps for back-compat with callers that
// want a single figure.
GoodputBps float64
}
LegStats is a snapshot of per-mux-leg traffic counters at a point in time. One entry per active route in the rg's tps[] order.
Counters are cumulative since route-group creation (atomic uint64); callers wanting rates take two snapshots and divide by elapsed time. Used by 'cli proxy mux-info' to show where bandwidth is going across the mux'd routes — the missing piece for verifying that adding more routes actually aggregates throughput rather than just trading share.
type LegWeight ¶
LegWeight is one leg's explicit send weight: the first-hop transport that identifies the leg, and the fraction of the send schedule it is to carry. Weights are normalized by the selector, so {2,1} and {0.667,0.333} are the same instruction.
type Map ¶
Map is a map of router RPC clients associated with the router's visor PK.
func MakeMap ¶
MakeMap makes a Map of the router clients, where the key is the router's visor public key. It creates these router clients by dialing to them concurrently. A hard timeout of MakeMapTimeout is applied to prevent indefinite blocking when visors are unreachable. The results channel is buffered so that goroutines completing after cancellation do not leak waiting to send.
func MakePooledMap ¶
MakePooledMap is like MakeMap but borrows connections from a ClientPool instead of dialing fresh ones. Clients that were successfully used should be returned to the pool via ReturnToPool; broken ones via DiscardFromPool.
func (Map) DiscardFromPool ¶
func (cm Map) DiscardFromPool(pool *ClientPool)
DiscardFromPool closes all clients without pooling them.
func (Map) ReturnToPool ¶
func (cm Map) ReturnToPool(pool *ClientPool)
ReturnToPool returns all clients in the Map to the given pool for reuse.
type MockIDReserver ¶
MockIDReserver is an autogenerated mock type for the IDReserver type
func NewMockIDReserver ¶
func NewMockIDReserver(t interface {
mock.TestingT
Cleanup(func())
}) *MockIDReserver
NewMockIDReserver creates a new instance of MockIDReserver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. The first argument is typically a *testing.T value.
func (*MockIDReserver) Client ¶
func (_m *MockIDReserver) Client(pk cipher.PubKey) *Client
Client provides a mock function with given fields: pk
func (*MockIDReserver) Close ¶
func (_m *MockIDReserver) Close() error
Close provides a mock function with no fields
func (*MockIDReserver) ReserveIDs ¶
func (_m *MockIDReserver) ReserveIDs(ctx context.Context) error
ReserveIDs provides a mock function with given fields: ctx
func (*MockIDReserver) ReturnToPool ¶
func (_m *MockIDReserver) ReturnToPool(_ *ClientPool)
ReturnToPool provides a mock function for returning connections to a pool.
func (*MockIDReserver) String ¶
func (_m *MockIDReserver) String() string
String provides a mock function with no fields
func (*MockIDReserver) TotalIDs ¶
func (_m *MockIDReserver) TotalIDs() int
TotalIDs provides a mock function with no fields
type MockRouteGroupDialer ¶
MockRouteGroupDialer is an autogenerated mock type for the RouteGroupDialer type
func NewMockRouteGroupDialer ¶
func NewMockRouteGroupDialer(t interface {
mock.TestingT
Cleanup(func())
}) *MockRouteGroupDialer
NewMockRouteGroupDialer creates a new instance of MockRouteGroupDialer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. The first argument is typically a *testing.T value.
func (*MockRouteGroupDialer) Dial ¶
func (_m *MockRouteGroupDialer) Dial(ctx context.Context, log *logging.Logger, dmsgC *dmsg.Client, setupNodes []cipher.PubKey, req routing.BidirectionalRoute) (routing.EdgeRules, cipher.PubKey, error)
Dial provides a mock function with given fields: ctx, log, dmsgC, setupNodes, req
type MockRouter ¶
MockRouter is an autogenerated mock type for the Router type
func NewMockRouter ¶
func NewMockRouter(t interface {
mock.TestingT
Cleanup(func())
}) *MockRouter
NewMockRouter creates a new instance of MockRouter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. The first argument is typically a *testing.T value.
func (*MockRouter) AcceptDatagram ¶
func (_m *MockRouter) AcceptDatagram(ctx context.Context) (*DatagramRouteGroup, routing.Port, error)
AcceptDatagram provides a mock function with given fields: ctx
func (*MockRouter) AcceptRoutes ¶
AcceptRoutes provides a mock function with given fields: _a0
func (*MockRouter) ActiveRouteStatuses ¶
func (_m *MockRouter) ActiveRouteStatuses() []RouteStatus
ActiveRouteStatuses provides a mock function with no fields
func (*MockRouter) AddMuxRouteByHops ¶
func (_m *MockRouter) AddMuxRouteByHops(_a0 routing.RouteDescriptor, _a1, _a2 []routing.Hop) error
AddMuxRouteByHops provides a mock function
func (*MockRouter) Close ¶
func (_m *MockRouter) Close() error
Close provides a mock function with no fields
func (*MockRouter) CloseRouteGroupsForApp ¶
func (_m *MockRouter) CloseRouteGroupsForApp(_a0 string) int
CloseRouteGroupsForApp provides a mock function
func (*MockRouter) DelRules ¶
func (_m *MockRouter) DelRules(_a0 []routing.RouteID)
DelRules provides a mock function with given fields: _a0
func (*MockRouter) DialHook ¶
func (_m *MockRouter) DialHook() DialHook
DialHook provides a mock function with given fields:
func (*MockRouter) DialRoutes ¶
func (_m *MockRouter) DialRoutes(ctx context.Context, rPK cipher.PubKey, lPort routing.Port, rPort routing.Port, opts *DialOptions) (net.Conn, error)
DialRoutes provides a mock function with given fields: ctx, rPK, lPort, rPort, opts
func (*MockRouter) DialRoutesDatagram ¶
func (_m *MockRouter) DialRoutesDatagram(ctx context.Context, rPK cipher.PubKey, lPort routing.Port, rPort routing.Port, opts *DialOptions) (net.Conn, *DatagramRouteGroup, error)
DialRoutesDatagram provides a mock function with given fields: ctx, rPK, lPort, rPort, opts
func (*MockRouter) EffectiveMinHops ¶
func (_m *MockRouter) EffectiveMinHops(opts *DialOptions) uint16
EffectiveMinHops provides a mock function with given fields: opts
func (*MockRouter) GetExistingTPOnly ¶
func (_m *MockRouter) GetExistingTPOnly() bool
GetExistingTPOnly provides a mock function
func (*MockRouter) GetForceLocalRoutes ¶
func (_m *MockRouter) GetForceLocalRoutes() bool
GetForceLocalRoutes provides a mock function
func (*MockRouter) GetLastRouteCalcTime ¶
func (_m *MockRouter) GetLastRouteCalcTime() time.Duration
GetLastRouteCalcTime provides a mock function with no fields
func (*MockRouter) GetMuxFEC ¶
func (_m *MockRouter) GetMuxFEC() bool
GetMuxFEC provides a mock function; see SetMuxFEC.
func (*MockRouter) GetMuxRoutes ¶
func (_m *MockRouter) GetMuxRoutes() int
GetMuxRoutes provides a mock function
func (*MockRouter) GrowMuxFromPool ¶
GrowMuxFromPool provides a mock function
func (*MockRouter) GrowMuxRoute ¶
func (_m *MockRouter) GrowMuxRoute(_a0 routing.RouteDescriptor, _a1, _a2 int) (int, error)
GrowMuxRoute provides a mock function
func (*MockRouter) IntroduceRules ¶
func (_m *MockRouter) IntroduceRules(rules routing.EdgeRules) error
IntroduceRules provides a mock function with given fields: rules
func (*MockRouter) MuxCounters ¶
func (_m *MockRouter) MuxCounters() MuxCounters
MuxCounters provides a mock function with no fields
func (*MockRouter) MuxEvents ¶
func (_m *MockRouter) MuxEvents() []MuxEvent
MuxEvents provides a mock function with no fields
func (*MockRouter) NoteTunnelEvent ¶
func (_m *MockRouter) NoteTunnelEvent(localPort routing.Port, event string, reason string, role string) bool
NoteTunnelEvent provides a mock function
func (*MockRouter) PingRoute ¶
func (_m *MockRouter) PingRoute(ctx context.Context, rPK cipher.PubKey, lPort routing.Port, rPort routing.Port, opts *DialOptions) (net.Conn, error)
PingRoute provides a mock function with given fields: ctx, rPK, lPort, rPort, opts
func (*MockRouter) RegisterDatagramPort ¶
func (_m *MockRouter) RegisterDatagramPort(port routing.Port)
RegisterDatagramPort provides a mock function with given fields: port
func (*MockRouter) RemoveMuxRouteByTransport ¶
func (_m *MockRouter) RemoveMuxRouteByTransport(_a0 routing.RouteDescriptor, _a1 uuid.UUID) error
RemoveMuxRouteByTransport provides a mock function
func (*MockRouter) ReserveKeys ¶
func (_m *MockRouter) ReserveKeys(n int) ([]routing.RouteID, error)
ReserveKeys provides a mock function with given fields: n
func (*MockRouter) RouteGroupHops ¶
func (_m *MockRouter) RouteGroupHops(_a0 routing.RouteDescriptor) []RouteHopInfo
RouteGroupHops provides a mock function
func (*MockRouter) RouteGroupMuxInfo ¶
func (_m *MockRouter) RouteGroupMuxInfo(_a0 routing.RouteDescriptor) (MuxInfo, bool)
RouteGroupMuxInfo provides a mock function
func (*MockRouter) RouteGroupMuxInfoAll ¶
func (_m *MockRouter) RouteGroupMuxInfoAll() []MuxInfo
RouteGroupMuxInfoAll provides a mock function with no fields
func (*MockRouter) RouteGroupMuxInfoForApp ¶
func (_m *MockRouter) RouteGroupMuxInfoForApp(_a0 string) []MuxInfo
RouteGroupMuxInfoForApp provides a mock function
func (*MockRouter) RoutesCount ¶
func (_m *MockRouter) RoutesCount() int
RoutesCount provides a mock function with no fields
func (*MockRouter) RoutingTableStats ¶
func (_m *MockRouter) RoutingTableStats() routing.RoutingTableStats
RoutingTableStats provides a mock function with no fields
func (*MockRouter) Rules ¶
func (_m *MockRouter) Rules() []routing.Rule
Rules provides a mock function with no fields
func (*MockRouter) SaveRoutingRules ¶
func (_m *MockRouter) SaveRoutingRules(rules ...routing.Rule) error
SaveRoutingRules provides a mock function with given fields: rules
func (*MockRouter) SaveRule ¶
func (_m *MockRouter) SaveRule(_a0 routing.Rule) error
SaveRule provides a mock function with given fields: _a0
func (*MockRouter) Serve ¶
func (_m *MockRouter) Serve(_a0 context.Context) error
Serve provides a mock function with given fields: _a0
func (*MockRouter) SetDstTransportOracle ¶
func (_m *MockRouter) SetDstTransportOracle(_a0 DstTransportOracle)
SetDstTransportOracle provides a mock function with given fields: _a0
func (*MockRouter) SetExistingTPOnly ¶
func (_m *MockRouter) SetExistingTPOnly(_a0 bool)
SetExistingTPOnly provides a mock function with given fields: _a0
func (*MockRouter) SetForceLocalRoutes ¶
func (_m *MockRouter) SetForceLocalRoutes(_a0 bool)
SetForceLocalRoutes provides a mock function with given fields: _a0
func (*MockRouter) SetMinHop ¶
func (_m *MockRouter) SetMinHop(_a0 uint16)
SetMinHop provides a mock function with given fields: _a0
func (*MockRouter) SetMuxDirectionForApp ¶
func (_m *MockRouter) SetMuxDirectionForApp(_a0 string, _a1 byte) (int, error)
SetMuxDirectionForApp provides a mock function
func (*MockRouter) SetMuxFEC ¶
func (_m *MockRouter) SetMuxFEC(v bool)
SetMuxFEC provides a mock function backed by a plain field: the value carries no behaviour worth asserting, and a Called() would panic in every existing MockRouter test that reaches the setting.
func (*MockRouter) SetMuxMode ¶
func (_m *MockRouter) SetMuxMode(_a0 WeightMode)
SetMuxMode provides a mock function with given fields: _a0
func (*MockRouter) SetupIsTrusted ¶
func (_m *MockRouter) SetupIsTrusted(_a0 cipher.PubKey) bool
SetupIsTrusted provides a mock function with given fields: _a0
func (*MockRouter) UnregisterDatagramPort ¶
func (_m *MockRouter) UnregisterDatagramPort(port routing.Port)
UnregisterDatagramPort provides a mock function with given fields: port
type MuxCounters ¶
type MuxCounters struct {
// TunnelPromotions counts standby-tunnel promotions to active (a pool
// tunnel flip) — every MuxEventTunnelPromoted recorded by noteTunnelEvent.
TunnelPromotions uint64 `json:"tunnel_promotions"`
// LegRehomesSent/Acked count this visor's OWN outbound leg re-homes
// (leg_rehome.go rehomeChain): Sent when the request lands on the wire,
// Acked when the peer's ack completes the in-place move. LegRehomesFailed
// adds the initiator's own timeout/refusal outcomes to the acceptor's
// refusals counted below — it does not cover a failure AFTER a peer's ack
// (adopting the leg locally), which is rarer and separately logged as a
// MuxEvent. LegRehomesReceived counts an incoming re-home REQUEST this
// visor accepted or refused (leg_rehome.go acceptRehome).
LegRehomesSent uint64 `json:"leg_rehomes_sent"`
LegRehomesReceived uint64 `json:"leg_rehomes_received"`
LegRehomesAcked uint64 `json:"leg_rehomes_acked"`
LegRehomesFailed uint64 `json:"leg_rehomes_failed"`
// LegSplitsSent/Received/Acked/Failed count the REVERSE move
// (leg_split.go): a leg handed back OUT of a group into a standalone
// standby group of its own, instead of its transport being closed.
// Sent/Acked/Failed are this visor's own outbound splits, Received an
// incoming split request it accepted or refused.
// LegRehomesForwarded/LegSplitsForwarded count re-home and split control
// frames this visor RELAYED as an intermediate hop (router_packet.go
// forwardPacket). They are what makes a transited re-home observable at all:
// on the initiator a chain through a relay that does not forward the type is
// indistinguishable from an exit that refused, and the relay itself had no
// tally of its own. A sent-but-never-acked re-home whose first hop reports 0
// here died at that hop.
LegRehomesForwarded uint64 `json:"leg_rehomes_forwarded"`
LegSplitsForwarded uint64 `json:"leg_splits_forwarded"`
// LegControlsInBandSent/LegControlsInBand count the IN-BAND leg control
// frames (mux_control_frame.go) this visor sent and received: the re-home
// and split messages carried inside a DataPacket, which every relay
// forwards whatever it runs. A re-home that is sent but never acked with
// these rising on both edges is an exit that refused, not a hop that
// dropped the control frame.
LegControlsInBandSent uint64 `json:"leg_controls_inband_sent"`
LegControlsInBand uint64 `json:"leg_controls_inband_received"`
LegSplitsSent uint64 `json:"leg_splits_sent"`
LegSplitsReceived uint64 `json:"leg_splits_received"`
LegSplitsAcked uint64 `json:"leg_splits_acked"`
LegSplitsFailed uint64 `json:"leg_splits_failed"`
// ForwardFanoutEngaged/Released count noteForwardFanout(on=true/false):
// the forward direction's fan-out-under-load latch (unidir.go) turning on
// (MuxEventForwardFanout) or off (MuxEventForwardConfined).
ForwardFanoutEngaged uint64 `json:"forward_fanout_engaged"`
ForwardFanoutReleased uint64 `json:"forward_fanout_released"`
// AEADFailures counts per-frame AEAD (CapPerFrameNoise) open failures
// across ALL route groups — the stream RouteGroup's mux path and the
// datagram group's, which also keeps its own per-group tally. A nonzero
// value is tampering, a stale duplicate after a rekey, or wire corruption
// the transport did not catch; the frame is always dropped, never delivered.
AEADFailures uint64 `json:"aead_failures"`
// DeliveryCRCFailures counts frames dropped at DELIVERY because their
// CapDeliveryCRC trailer did not match the CRC32C of (seq ‖ payload) — see
// delivery_crc.go. Unlike AEADFailures this one is the reassembly check: it
// catches a reorder/flush defect that would otherwise only surface as an
// application hash failure. It must be 0 on a healthy mux.
DeliveryCRCFailures uint64 `json:"delivery_crc_failures"`
}
MuxCounters is a snapshot of the whole-router mux counters, surfaced via `visor state --select mux` (StateSnapshot.MuxCounters) and `proxy mux info`.
func MuxCountersSnapshot ¶
func MuxCountersSnapshot() MuxCounters
MuxCountersSnapshot reads the process-wide mux counters without a router instance. The counters are global (see globalMuxCounters), and the emulator bench asserts the integrity ones cell by cell.
type MuxEvent ¶
type MuxEvent struct {
At time.Time `json:"at"`
Event string `json:"event"`
// App is the app name the route group was tagged with at dial time
// (SetAppName), e.g. "via0281a102". Empty on the accept side.
App string `json:"app,omitempty"`
// Desc identifies the route group the event belongs to.
Desc routing.RouteDescriptorFields `json:"desc"`
// By names who decided: "local", "remote", "operator", "adaptive"
// (liveness/self-heal) or "policy" (routing-policy rotation hook).
By string `json:"by,omitempty"`
// LegIndex is the leg's index at the moment of the event (-1 for
// whole-group events); Legs is how many legs the group had AFTER it.
LegIndex int `json:"leg_index"`
Legs int `json:"legs"`
// TpID/TpType/Remote describe the leg's FIRST hop — the transport this
// visor owns. Zero for whole-group events.
TpID uuid.UUID `json:"tp_id,omitempty"`
TpType string `json:"tp_type,omitempty"`
Remote cipher.PubKey `json:"remote_pk,omitempty"`
// Hops is the leg's full forward path when the route group recorded one
// (recordLegRoute); nil for a leg whose path was never stored.
Hops []RouteHopInfo `json:"hops,omitempty"`
// Reason is what decided this event, in the words of the code that did
// ("operator: mux rm", "transport closed", "liveness: no echo for 3
// probes", "peer retired the leg", …).
Reason string `json:"reason,omitempty"`
}
MuxEvent is one entry in the router's bounded history of route-group and mux-leg changes, with the reason each one happened. It exists because the question "who took that leg away" had no answer: a pinned two-hop proxy session's `proxy mux info` went from one leg to none mid-measurement while its first-hop transport stayed open, and neither `visor state` nor diag.transport_events (#4919) recorded anything — the visor log ring is the only other witness and it holds minutes. `visor state --select diag` carries the last MuxEventRingSize of these, and `proxy mux info --json` carries each group's most recent few next to its legs.
type MuxInfo ¶
type MuxInfo struct {
// Desc identifies the route group.
Desc routing.RouteDescriptor
// FarEndPK is the OTHER end of Desc — the peer this group's chains reach,
// never this visor. The setup node hands EACH edge the descriptor that
// points AT that edge, so Desc.DstPK() is the LOCAL visor on BOTH sides
// (which is why LocalAddr is desc.Dst() and RemoteAddr is desc.Src());
// reading DstPK as the "destination"/"exit" therefore printed this visor's
// own key as the far end of every dialed group. Consumers render this
// instead of deriving an end from Desc.
FarEndPK cipher.PubKey
// MuxEnabled is false for non-mux'd rg's; the per-leg counters
// are still populated for the single transport in that case.
MuxEnabled bool
// SACKEnabled is true when the peers negotiated SACK retx.
SACKEnabled bool
// HOLRetxEnabled is true when the peers negotiated CapHOLRetx (proactive
// head-of-line retransmit — the frontier-blocking seq is fast-retransmitted on
// the fastest leg after ~one fast-leg RTT instead of the reactive waits).
HOLRetxEnabled bool
// PerFrameNoise is true when both edges negotiated CapPerFrameNoise and the
// mux is sealing/opening each DATA frame under its own sequence-nonce (the
// inverse-multiplexer path, network.EncryptConn bypassed). False means the
// group runs the classic stream-noise wrap.
PerFrameNoise bool
// AppName is the app that DIALED this route group — the key its session's
// shape and move history are held under. Distinct from KnobApp below,
// which is only ever set when that app has an override of its own.
AppName string
// KnobApp names the app whose `route settings --app <name>` override set this
// group resolved against; empty means it runs the visor-wide values. It is
// how a paired subject/reference run proves the two clients really did get
// different knobs.
KnobApp string
// Directional is true when unidirectional send selection (CapUniDir) is
// active on this mux: each direction rides a disjoint leg class instead of
// both striping every leg. Flipped reports the current direction->leg-class
// mapping. DEFAULT (Flipped=false): this end sends its FORWARD/upload on the
// DIRECT (1-hop) leg and its REVERSE/download on the MULTIHOP mux legs.
// FLIPPED=true: the mapping is swapped (the heavy direction took the mux).
// With the per-leg Direct flag, a reader can tell which class carries which
// direction without log-grepping. Both false on a non-directional mux.
Directional bool
Flipped bool
// FlipPinned is the operator's MANUAL direction pin on a directional group:
// "auto" (the flip controller is in charge — the default), "default" or
// "flipped" (the mapping is pinned to that state and the controller is
// dormant until released via mode auto). Empty on a non-directional mux.
FlipPinned string
// Distribution names how the mux spreads outbound packets across its legs
// (the transportSelector weight mode: "auto", "round-robin", "weighted",
// "capacity", "latency-adaptive", "sticky:5tuple", "size-threshold",
// "dscp-priority"). Empty for a non-mux group.
Distribution string
// ReorderPending is the number of received packets currently buffered
// out-of-order (head-of-line-blocking depth); ReorderGapAge is how long the
// current reorder frontier gap has stayed open (0 when contiguous). A rising
// pending count with a growing gap age is a stalled/black-holing leg.
ReorderPending int
ReorderGapAge time.Duration
// WriteSeq is the total DATA frames this mux has emitted outbound — a cheap
// aggregate send-progress counter across all legs.
WriteSeq uint32
// FEC telemetry (see routeMux fec* counters). FECEnabled is true when both
// peers negotiated CapFEC. FECRepairBytesSent/Recv are cumulative repair-frame
// bytes — the TRUE FEC overhead, separable from data and retransmit.
// FECReconstructs counts frontier frames recovered from repair.
FECEnabled bool
FECRepairBytesSent uint64
FECRepairBytesRecv uint64
FECReconstructs uint64
// Legs is in tps[] order. One entry per active mux leg.
Legs []MuxLeg
// Events is this group's most recent leg/group changes with their reasons,
// oldest first (the last muxEventsPerGroup of the router's ring). It puts
// the churn next to the legs, so a `proxy mux info --json` that shows a leg
// missing also shows who took it and why. The full ring is
// diag.mux_events in `visor state`.
Events []MuxEvent
// Recovery is the loss-recovery view (sender retransmit machinery +
// receiver reorder frontier). Nil for a group with no mux.
Recovery *MuxRecovery
// TunnelRole is the DIALING app's label for this route group — "active"
// (it carries streams) or "standby" (held open and measured, ready to take
// over). Empty for any group that is not one of a multi-tunnel app's
// tunnels, and ALWAYS empty on the accepting end: an exit knows how many
// tunnels a client holds but not which of them are in standby, so read the
// role on the local end.
// AgeMS is how long this route group has existed. For a pooled STANDBY
// tunnel it is its AUDITION age — how long it has been held open, pinged
// and measured without carrying a stream.
AgeMS float64
TunnelRole string
// DisjointRoutes is how many routes to this group.s far end share no
// intermediate and no first hop (router.disjointRouteBound). Set by the
// router on the snapshots it hands out; 0 when not yet counted.
DisjointRoutes int
// LegReserve is a standby group a leg split built: a chain the pool may
// compose back in as a leg, but not a tunnel the app can promote.
LegReserve bool
// Shape is the SESSION's measured multiplexing shape — every ACTIVE
// tunnel this app holds to this exit with its live leg count, written
// "<k>x<n>" when the tunnels are the same width and "n1,n2,…" when they
// are not. "4x1" is pure stream level, "1x4" pure packet level, "2x2"
// the default. ShapeTarget is the shape the session is being held at and
// ShapeSource where that target came from: "auto" (tunnel.count tunnels
// at pool.active_width legs, today's rule) or "mux.shape" when the knob
// names one. The target is ADVISORY — reported, not yet converged toward
// (docs/design/mux-shape-axis.md).
//
// All three are empty on any group that is not an active tunnel: a
// standby is the pool rather than the shape, and an accepting visor
// cannot see which of a client's tunnels are in standby.
Shape string
ShapeTarget string
ShapeSource string
// ShapeTunnels is the k the target asks for — the number of ACTIVE tunnels
// the session is converging to. 0 under mux.shape=auto, where the count is
// the app's own tunnel.count and nothing here overrides it. It is published
// so the dialing app can promote toward the shape rather than toward
// tunnel.count (docs/design/mux-shape-axis.md step 6).
ShapeTunnels int
// LastMove is the most recent shape move this session made and MoveCounts
// the running tally, keyed by the four move names (pool_leg_taken,
// pool_leg_released, tunnel_promoted, tunnel_parked). They make a shape
// history readable without walking the event ring. Both nil on a session
// that has never moved.
LastMove *MuxShapeMove
MoveCounts map[string]uint64
}
MuxInfo is a point-in-time snapshot of one route group's multiplexing state — per-leg byte/packet counters plus the transport identity each leg maps to. Returned by Router.MuxInfo for 'cli proxy mux-info'.
type MuxLeg ¶
type MuxLeg struct {
LegStats
TransportID string `json:"transport_id"`
TpType string `json:"tp_type"`
RemotePK string `json:"remote_pk"`
// Source names where a leg that is NOT this group's own dial came from —
// "standby :4, re-homed in place" for a leg the pool arbiter took
// (pool_arbiter.go). Empty for an ordinary dialed leg.
Source string `json:"source,omitempty"`
// LatencyMS is the FIRST-HOP transport RTT in ms (the same value
// 'tp ls' shows). For a multihop leg this is only the near edge, NOT
// the whole path — use RouteLatencyMS for the end-to-end route.
LatencyMS float64 `json:"latency_ms"`
// RouteLatencyMS is the leg's TRUE end-to-end route latency in ms —
// the EWMA-smoothed round-trip of the per-leg liveness pong across
// ALL hops (from legE2ELatency). This is the number a routing policy
// judges a leg by; it can differ sharply from LatencyMS on a multihop
// leg. Zero until the first pong lands.
RouteLatencyMS float64 `json:"route_latency_ms"`
// AckDelayMS is this leg's own EWMA send→ack delay in ms (0 = no sample):
// the loaded feedback delay its retransmit threshold is judged by
// (rackThresholdFor), distinct from the idle RTTs above.
AckDelayMS float64 `json:"ack_delay_ms"`
// InflightBytes / WindowBytes: the leg's real unacknowledged bytes and the
// per-leg send window they are bounded by (0 when not a predictive mode).
InflightBytes float64 `json:"inflight_bytes"`
WindowBytes float64 `json:"window_bytes"`
// Direct is true when this leg's first hop goes straight to the route
// group's destination (a 1-hop/direct route); false means the leg is
// multihop (relayed through one or more intermediates). Lets a viewer
// tell the direct leg from the multihop legs at a glance.
Direct bool `json:"direct"`
// Alive is false once the leg's transport is closed. Standby is true
// when the leg is a WARM STANDBY: rules kept alive but not selected
// for sending (see docs/warm_standby_legs_rfc.md). Together they are
// the leg's gate_state for the per-leg telemetry harness.
Alive bool `json:"alive"`
Standby bool `json:"standby"`
// Hops is this leg's FULL forward route — every hop from this visor to
// the destination, with full (untruncated) From/To PKs and per-hop
// transport type. Per-hop LatencyMS is filled where known (first hop
// owned; single-intermediate far hop derived from route−transport RTT).
// Empty when the route path wasn't recorded (legacy/accepted routes).
Hops []RouteHopInfo `json:"hops,omitempty"`
// CapacityPriorBps is the pool arbiter's PRIOR throughput estimate for
// this leg's transport (throughputPrior: the transport's live measured
// rate if it has one, else its catalog entry) — the number a standby
// tunnel is ranked by BEFORE it is measured. Distinct from GoodputBps,
// which is the leg's own live EWMA once it has carried traffic.
CapacityPriorBps float64 `json:"capacity_prior_bps,omitempty"`
}
MuxLeg pairs the per-leg counters with the transport identity. The transport's own Latency / SentBytes / RecvBytes counters are the *transport-level* totals (across all rg's that share the transport); the LegStats here are *rg-scoped* (only what this rg sent through this leg).
type MuxLegController ¶
type MuxLegController interface {
SetMuxExplicitWeights(desc routing.RouteDescriptor, weights map[uuid.UUID]float64) (MuxWeightsView, error)
ClearMuxExplicitWeights(desc routing.RouteDescriptor) (MuxWeightsView, error)
MuxWeightsFor(desc routing.RouteDescriptor) (MuxWeightsView, error)
AddMuxRouteByHopsForward(desc routing.RouteDescriptor, fwd, rev []routing.Hop) error
MuxNegotiatedForApp(appName string) []MuxNegotiated
// RehomeStandbyLeg moves a STANDBY group's whole built chain into the
// target group as one more mux leg, in place, with no setup-node dial.
// Returns ErrRehomeUnsupported when the peer did not negotiate
// CapLegRehome. See leg_rehome.go.
RehomeStandbyLeg(target, standby routing.RouteDescriptor) error
}
MuxLegController is the optional router capability the visor asserts for the per-leg operator controls. Implemented by *router.
type MuxNegotiated ¶
type MuxNegotiated struct {
DstPort uint16 `json:"dst_port"`
SrcPort uint16 `json:"src_port"`
Remote string `json:"remote"`
// AppName is the dialing app's tag on the group, when it has one.
AppName string `json:"app_name,omitempty"`
Legs int `json:"legs"`
// Negotiated capabilities — both ends advertised these.
MuxEnabled bool `json:"mux_enabled"`
SACKEnabled bool `json:"sack_enabled"`
HOLRetxEnabled bool `json:"hol_retx_enabled"`
PerFrameNoise bool `json:"per_frame_noise"`
FECEnabled bool `json:"fec_enabled"`
Directional bool `json:"directional"`
// Scheduler in force, and the explicit weights when it is that mode.
Distribution string `json:"distribution"`
ExplicitWeights []LegWeight `json:"explicit_weights,omitempty"`
// Send-window shape in effect (the live router settings this end clamps to).
EcfMinWindowBytes int64 `json:"ecf_min_window_bytes"`
EcfMaxWindowBytes int64 `json:"ecf_max_window_bytes"`
EcfWindowMargin float64 `json:"ecf_window_margin"`
SendWindowWaitMax string `json:"send_window_wait_max"`
ForwardSpill bool `json:"forward_spill"`
ForwardSwitchMargin float64 `json:"forward_switch_margin"`
// PerLegWindowBytes is each leg's CURRENT send window, in tps[] order —
// the negotiated shape as the scheduler is actually applying it.
PerLegWindowBytes []float64 `json:"per_leg_window_bytes,omitempty"`
}
MuxNegotiated is the per-group view of what the two ends actually agreed on and what the sender is applying — the answer to "which caps are in effect on THIS group, and with which window".
The capability flags are negotiated on the wire (both ends must advertise); the window and margin values are this end's live router settings, which is what the send side is actually clamping to. Reporting them per group rather than only as visor-wide settings is the point: a sweep that moves --ecf-max-window mid-campaign otherwise leaves no record on the group it changed.
type MuxRecovery ¶
type MuxRecovery struct {
// Sender side. WriteSeq is the next outgoing DATA sequence; RetxHeld /
// RetxMinSeq / RetxMaxSeq are the unacknowledged window still retransmittable
// (a peer's stuck frontier BELOW RetxMinSeq can never be healed); RetxSent is
// retransmit packets put on the wire (summed across legs);
// RetxSkippedMissing counts resend requests for sequences no longer held;
// RetxSendErrors counts resends that failed to reach any leg; TLPProbes counts
// tail-loss probes; SACKsRecv / LastSACKRecvMsAgo / LastSACKRecvContig are the
// inbound feedback (ms-ago is -1 when no SACK has ever arrived).
WriteSeq uint32 `json:"write_seq"`
RetxHeld int `json:"retx_held"`
RetxMinSeq uint32 `json:"retx_min_seq"`
RetxMaxSeq uint32 `json:"retx_max_seq"`
RetxSent uint64 `json:"retx_sent"`
RetxSkippedMissing uint64 `json:"retx_skipped_missing"`
RetxReqSACK uint64 `json:"retx_req_sack"`
RetxReqHOL uint64 `json:"retx_req_hol"`
RetxReqFlush uint64 `json:"retx_req_flush"`
// RetxDeferredYoung counts the holes a SACK named that were NOT resent
// because the frame is still younger than the delay basis of the leg it
// rode — the duplicate bytes kept off the wire.
RetxDeferredYoung uint64 `json:"retx_deferred_young"`
RetxSendErrors uint64 `json:"retx_send_errors"`
// SendWindowWaits / SendWindowTimeouts: writers parked because every ready
// leg was at its per-leg send window, and the parks that gave up after
// sendWindowWaitMax and sent anyway.
SendWindowWaits uint64 `json:"send_window_waits"`
SendWindowTimeouts uint64 `json:"send_window_timeouts"`
TLPProbes uint64 `json:"tlp_probes"`
SACKsRecv uint64 `json:"sacks_recv"`
LastSACKRecvMsAgo float64 `json:"last_sack_recv_ms_ago"`
LastSACKRecvContig uint32 `json:"last_sack_recv_contig"`
// Receiver side. ReorderNextSeq is the frontier this end waits on,
// ReorderPending how many packets are dammed behind it, GapAgeMS how long it
// has been stuck. SACKsSent / SACKSendErrors / LastSACKSentMsAgo are the
// outbound feedback (ms-ago -1 when none ever sent). WedgeTicks is the
// CURRENT wedge's stall-tick count (0 = not wedged), Wedges how many have
// cleared on this group, LongestWedgeMS the worst one seen. ReorderDrops
// counts arrivals the reorder buffer discarded because the gap already held
// a full window — they are deliberately NOT SACKed, so each one is owed a
// retransmit.
ReorderNextSeq uint32 `json:"reorder_next_seq"`
ReorderPending int `json:"reorder_pending"`
ReorderDrops uint64 `json:"reorder_drops"`
GapAgeMS float64 `json:"gap_age_ms"`
SACKsSent uint64 `json:"sacks_sent"`
SACKSendErrors uint64 `json:"sack_send_errors"`
LastSACKSentMsAgo float64 `json:"last_sack_sent_ms_ago"`
WedgeTicks int64 `json:"wedge_ticks"`
Wedges uint64 `json:"wedges"`
LongestWedgeMS int64 `json:"longest_wedge_ms"`
}
MuxRecovery is a route group's LOSS-RECOVERY state: the sender-side retransmit machinery and the receiver-side reorder frontier, side by side.
It exists because a reorder WEDGE is a two-ended failure with a one-ended witness. Observed live: a pinned two-hop proxy session went dark for two minutes while the EXIT logged "reorder-WEDGE: frontier stuck at seq=56 … sender retransmit not refilling the gap"; our end — the sender — had nothing to show at all, so the two candidate causes were indistinguishable. Either the exit's SACKs never reached us (SACKsRecv flat, LastSACKRecvMsAgo growing) or they reached us naming a sequence the retx buffer had already evicted (RetxSkippedMissing climbing, and RetxMinSeq above the frontier the peer is stuck on). Every field here is a cheap atomic read; nothing is added to a hot path.
type MuxShapeMove ¶
type MuxShapeMove struct {
Move string `json:"move"`
From string `json:"from"`
To string `json:"to"`
Reason string `json:"reason"`
At time.Time `json:"at"`
}
MuxShapeMove is the last shape move a session made, reported as MuxInfo.LastMove. Move is one of the four mux event names (pool_leg_taken / pool_leg_released / tunnel_promoted / tunnel_parked), From and To are the shapes it moved between, and Reason is the sentence the Info log carried.
type MuxWeightsView ¶
type MuxWeightsView struct {
DstPort uint16 `json:"dst_port"`
SrcPort uint16 `json:"src_port"`
Mode string `json:"mode"`
Weights []LegWeight `json:"weights,omitempty"`
}
MuxWeightsView is the weights state of one route group after (or without) a change: the scheduler mode in force and, when it is the explicit one, the per-leg weights it is spreading by.
type Node ¶
type Node struct {
// contains filtered or unexported fields
}
Node performs routes setup operations over messaging channel.
func NewNode ¶
func NewNode(ctx context.Context, conf *SetupConfig) (*Node, error)
NewNode constructs a new SetupNode. It gives up when ctx ends before the node reaches the dmsg network.
func (*Node) DmsgClient ¶
DmsgClient returns the setup node's DMSG client. This allows other components (e.g. DMSG HTTP health) to share the same client instead of creating a second one with the same PK, which causes "error 306 - no associated listener" when streams route to the wrong client.
func (*Node) InitCascade ¶
func (sn *Node) InitCascade(conf *SetupConfig, tm *transport.Manager)
InitCascade initializes the cascade builder with a transport manager. Must be called after the RSN's transport manager is set up by the caller. This separation exists because the transport manager requires deployment- specific configuration (STCPR listen address, AR client, etc.) that the RSN's core doesn't control.
func (*Node) Pool ¶
func (sn *Node) Pool() *ClientPool
Pool returns the setup node's connection pool.
type NoiseRouteGroup ¶
NoiseRouteGroup is a route group wrapped with noise. Implements net.Conn.
func (*NoiseRouteGroup) BandwidthReceived ¶
func (nrg *NoiseRouteGroup) BandwidthReceived() uint64
BandwidthReceived returns amount of bandwidth received (bytes).
func (*NoiseRouteGroup) BandwidthSent ¶
func (nrg *NoiseRouteGroup) BandwidthSent() uint64
BandwidthSent returns amount of bandwidth sent (bytes).
func (*NoiseRouteGroup) Close ¶
func (nrg *NoiseRouteGroup) Close() error
Close closes the connection, which chains through to close the underlying RouteGroup. The close chain properly stops the RouteGroup's keep-alive loop.
func (*NoiseRouteGroup) DownloadSpeed ¶
func (nrg *NoiseRouteGroup) DownloadSpeed() uint32
DownloadSpeed returns upload speed (bytes/s).
func (*NoiseRouteGroup) GetError ¶
func (nrg *NoiseRouteGroup) GetError() error
GetError gets the close error.
func (*NoiseRouteGroup) IsAlive ¶
func (nrg *NoiseRouteGroup) IsAlive() bool
IsAlive checks if connection is alive.
func (*NoiseRouteGroup) Latency ¶
func (nrg *NoiseRouteGroup) Latency() time.Duration
Latency returns latency till remote (ms).
func (*NoiseRouteGroup) LocalAddr ¶
func (nrg *NoiseRouteGroup) LocalAddr() net.Addr
LocalAddr returns local address.
func (*NoiseRouteGroup) RemoteAddr ¶
func (nrg *NoiseRouteGroup) RemoteAddr() net.Addr
RemoteAddr returns remote address.
func (*NoiseRouteGroup) RouteHopDetails ¶
func (nrg *NoiseRouteGroup) RouteHopDetails() []RouteHopInfo
RouteHopDetails returns detailed information about each hop in the route.
func (*NoiseRouteGroup) RouteHops ¶
func (nrg *NoiseRouteGroup) RouteHops() []cipher.PubKey
RouteHops returns the list of visor public keys that form the route path.
func (*NoiseRouteGroup) SetError ¶
func (nrg *NoiseRouteGroup) SetError(err error)
SetError sets the close error.
func (*NoiseRouteGroup) SetForwardHops ¶
func (nrg *NoiseRouteGroup) SetForwardHops(hops []routing.Hop)
SetForwardHops sets the complete forward route hops.
func (*NoiseRouteGroup) UploadSpeed ¶
func (nrg *NoiseRouteGroup) UploadSpeed() uint32
UploadSpeed returns upload speed (bytes/s).
type RPCGateway ¶
type RPCGateway struct {
// contains filtered or unexported fields
}
RPCGateway is a RPC interface for router.
func NewRPCGateway ¶
func NewRPCGateway(router Router, mLog *logging.MasterLogger, noTransit bool) *RPCGateway
NewRPCGateway creates a new RPCGateway.
func (*RPCGateway) AddEdgeRules ¶
func (r *RPCGateway) AddEdgeRules(rules routing.EdgeRules, ok *bool) error
AddEdgeRules adds edge rules.
func (*RPCGateway) AddIntermediaryRules ¶
func (r *RPCGateway) AddIntermediaryRules(rules []routing.Rule, ok *bool) error
AddIntermediaryRules adds intermediary rules. This RPC exists only to make this visor a transit hop on someone else's route, so refusing it outright is exactly what routing.no_transit means. Edge rules arrive by AddEdgeRules and are untouched, so routes that start or end here still work.
func (*RPCGateway) DelRules ¶
func (r *RPCGateway) DelRules(routeIDs []routing.RouteID, ok *bool) error
DelRules removes the routing rules identified by routeIDs from this router. The setup-node calls this to tear down rules it installed on intermediary / edge hops when a later step of route-group setup fails, so a partially built route does not orphan rules (each orphan pins its NextTransportID against transport teardown until the ~10-min keepalive GC reaps it). Router.DelRules logs per-id "rule not found" internally and cannot fail, so this is best-effort by contract and always reports ok.
func (*RPCGateway) ReserveIDs ¶
func (r *RPCGateway) ReserveIDs(n uint8, routeIDs *[]routing.RouteID) error
ReserveIDs reserves route IDs.
type RSNRelayCache ¶
type RSNRelayCache struct {
// contains filtered or unexported fields
}
RSNRelayCache caches relay peer lists received from the RSN. Used by visors to find a transport neighbor that can relay to the RSN.
func NewRSNRelayCache ¶
func NewRSNRelayCache(log *logging.Logger) *RSNRelayCache
NewRSNRelayCache creates an empty relay cache.
func (*RSNRelayCache) FindRelayTransport ¶
func (rc *RSNRelayCache) FindRelayTransport( ctx context.Context, rsnPK cipher.PubKey, tm *transport.Manager, ) (*transport.ManagedTransport, cipher.PubKey, error)
FindRelayTransport finds a transport to a relay peer that can reach the RSN. Returns the transport and relay peer PK, or an error if no relay path exists.
type RelayPeersArgs ¶
type RelayPeersArgs struct{}
RelayPeersArgs is the request for the RelayPeers RPC.
type RelayPeersReply ¶
RelayPeersReply contains the RSN's transport peer PKs so visors can cache them for future relay-based route setup without DMSG.
type ReserveError ¶
ReserveError names the remote whose route-ID reservation failed. The message is byte for byte what the id reserver used to format by hand, so log greps and the setup node's own-view parser keep working; what is new is that the PK is carried structurally, which is what lets the setupmetrics collector attribute the failure to the hop that actually failed instead of to the destination.
Without this the whole error was an untyped fmt.Errorf: failedDialPK found no PK, fell through to "blame the destination", and three reservations that failed at some INTERMEDIATE tripped the destination's breaker — observed live on 2026-09-18 against an exit the source visor held 32 live route groups to.
func (*ReserveError) DialFailedPK ¶
func (e *ReserveError) DialFailedPK() cipher.PubKey
DialFailedPK reports the remote whose reservation failed, through the same anonymous interface setupmetrics probes for on DialError.
func (*ReserveError) Error ¶
func (e *ReserveError) Error() string
Error preserves the legacy "reserve routeID from <pk> failed: <inner>" wording.
func (*ReserveError) Unwrap ¶
func (e *ReserveError) Unwrap() error
Unwrap exposes the inner error for errors.Is / errors.As.
type Role ¶
type Role uint8
Role identifies which direction's subkey this cipher serves. A DatagramRouteGroup holds two ciphers: one for outbound (seal with the direction matching the local end's role at handshake) and one for inbound (open with the peer's direction).
type RotationAction ¶
type RotationAction struct {
// DropLegs is the list of current-tick leg indices to close.
// Indices reference the legs slice passed to the hook —
// they're NOT stable across ticks because the prune path
// compacts the slice when a leg closes.
DropLegs []int
// AddLeg requests one more aux forward leg with ExcludeHops
// as the disjoint-intermediate filter.
AddLeg bool
ExcludeHops []string
// DemoteToStandby / PromoteFromStandby move legs between the active set
// and warm standby WITHOUT teardown: a demoted leg keeps its rules (the
// keepalive/liveness loops keep them alive) but is no longer selected for
// sending; promoting clears that instantly, with no route setup. This is
// the cheap alternative to Drop+re-dial for policy-driven leg moves — see
// docs/warm_standby_legs_rfc.md. Indices are current-tick leg indices, like
// DropLegs. Applied before DropLegs (which compacts) so the indices stay
// valid.
DemoteToStandby []int
PromoteFromStandby []int
// AddForwardLeg requests one more FORWARD-ONLY aux leg — appended
// addFwd=true / addRev=false so it adds upstream send capacity without
// enlarging the reverse/download set. The rotation loop dials it via the
// forward-only add callback (router.addOneAuxSendLeg). The forward-
// direction mirror of AddLeg; emitted by the adaptive preset under
// sustained upload (SentBytes) saturation. ExcludeHops applies to it too.
AddForwardLeg bool
}
RotationAction is the structured return value from the policy's on_tick hook. The route group's rotation servicePacketLoop fires the hook periodically (cadence configured at dial time) and applies this action: drop the listed leg indices, then optionally add a fresh aux leg whose path avoids ExcludeHops. Zero value = no-op.
type RotationHook ¶
type RotationHook interface {
OnTick(info DialInfo, legs []LegInfo) RotationAction
}
RotationHook fires periodically per active route group, giving the routing policy a chance to mutate the leg set without re- dialing the whole group. Policies use it to spread bandwidth across the eligible-peer set (drop oldest leg, add new one excluding current hops, repeat each tick).
type RouteGroup ¶
type RouteGroup struct {
// contains filtered or unexported fields
}
RouteGroup should implement 'io.ReadWriteCloser'. It implements 'net.Conn'.
func NewRouteGroup ¶
func NewRouteGroup(cfg *RouteGroupConfig, rt routing.Table, desc routing.RouteDescriptor, mLoggger *logging.MasterLogger) *RouteGroup
NewRouteGroup creates a new RouteGroup.
func (*RouteGroup) AppName ¶
func (rg *RouteGroup) AppName() string
AppName returns the rg's stored app name (set via SetAppName). Empty when the rg was created without app context (accept-side).
func (*RouteGroup) BandwidthReceived ¶
func (rg *RouteGroup) BandwidthReceived() uint64
BandwidthReceived returns amount of bandwidth received (bytes).
func (*RouteGroup) BandwidthSent ¶
func (rg *RouteGroup) BandwidthSent() uint64
BandwidthSent returns amount of bandwidth sent (bytes).
func (*RouteGroup) DownloadSpeed ¶
func (rg *RouteGroup) DownloadSpeed() uint32
DownloadSpeed returns download speed (bytes/s).
func (*RouteGroup) Initiator ¶
func (rg *RouteGroup) Initiator() bool
Initiator reports whether this visor dialed the remote end of the route (true) or accepted the route from a setup-node request (false).
func (*RouteGroup) IsAlive ¶
func (rg *RouteGroup) IsAlive() bool
IsAlive checks whether connection is alive.
func (*RouteGroup) Latency ¶
func (rg *RouteGroup) Latency() time.Duration
Latency returns latency till remote (ms).
func (*RouteGroup) LocalAddr ¶
func (rg *RouteGroup) LocalAddr() net.Addr
LocalAddr returns destination address of underlying RouteDescriptor.
func (*RouteGroup) MeasureLatency ¶
func (rg *RouteGroup) MeasureLatency(ctx context.Context, count int) (min, max, avg float64, err error)
MeasureLatency performs multiple ping/pong measurements and returns statistics. It sends 'count' pings, waits for pongs, and calculates min/max/avg latency. Returns the stats and any error. Partial results are returned if some pings fail.
func (*RouteGroup) MuxStats ¶
func (rg *RouteGroup) MuxStats() MuxInfo
MuxStats returns a point-in-time snapshot of the rg's per-leg counters paired with each leg's transport identity.
func (*RouteGroup) Read ¶
func (rg *RouteGroup) Read(p []byte) (n int, err error)
Read reads the next packet payload of a RouteGroup. The Router, via transport.Manager, is responsible for reading incoming packets and pushing it to the appropriate RouteGroup via (*RouteGroup).readCh.
func (*RouteGroup) RemoteAddr ¶
func (rg *RouteGroup) RemoteAddr() net.Addr
RemoteAddr returns source address of underlying RouteDescriptor.
func (*RouteGroup) RouteHopDetails ¶
func (rg *RouteGroup) RouteHopDetails() []RouteHopInfo
RouteHopDetails returns detailed information about each hop in the route, including transport IDs and types.
func (*RouteGroup) RouteHops ¶
func (rg *RouteGroup) RouteHops() []cipher.PubKey
RouteHops returns the list of visor public keys that form the route path. The first element is the first hop from the source, and the last element is the destination visor.
func (*RouteGroup) SetAppName ¶
func (rg *RouteGroup) SetAppName(name string)
SetAppName attaches app_name=<n> as a logrus field on the route group's logger AND records it on the rg for later lookups (mux info by app, etc.). Used by the router-side rg saver so 'cli proxy start --verbose' can scope rg-internal events to the originating app's session. No-op when name is empty.
func (*RouteGroup) SetCloseObserver ¶
func (rg *RouteGroup) SetCloseObserver(fn func(age time.Duration, carried uint64))
SetCloseObserver installs a one-shot callback fired when this group closes, carrying the group's age and the total payload it received. The dialer uses it to record a route that died young without ever moving a byte (see dead_route_cache.go). Safe to call at most once per group; a nil fn clears it.
func (*RouteGroup) SetDeadline ¶
func (rg *RouteGroup) SetDeadline(t time.Time) error
SetDeadline sets both read and write deadlines.
func (*RouteGroup) SetDirectionPin ¶
func (rg *RouteGroup) SetDirectionPin(mode byte) error
SetDirectionPin applies the operator's MANUAL direction pin to this route group and signals it to the peer. mode is routing.DirectionAuto (release — the flip controller resumes on both ends), DirectionPinDefault (initiator sends on the direct leg / download on the multihop mux) or DirectionPinFlipped (the swapped mapping). Errors unless the group is directional (CapUniDir negotiated) — a pin on a symmetric mux would never be read.
The pin is coordinated over the wire (DirectionPacket) because a one-sided pin would DESYNC the ends: both could end up sending on the same leg class. It is sent on the PRIMARY leg (leg 0's rule/transport): the pin addresses the GROUP, not one leg, and leg 0 is the one leg that is never standby — the signal always rides an installed, carrying path. Best-effort: an old peer (no DirectionPacket) ignores the frame, leaving the pin local-only — its controller may then fight the pinned mapping, which is why the caller-facing docs call a pin against an old peer best-effort.
func (*RouteGroup) SetError ¶
func (rg *RouteGroup) SetError(err error)
SetError sets the close error.
func (*RouteGroup) SetForwardHops ¶
func (rg *RouteGroup) SetForwardHops(hops []routing.Hop)
SetForwardHops sets the complete forward route hops. This should be called after route setup to store the full route path.
func (*RouteGroup) SetLegChangeHook ¶
func (rg *RouteGroup) SetLegChangeHook(hook LegChangeHook, info DialInfo)
SetLegChangeHook attaches a leg-change callback to the route group. Called once at construction time (from saveRouteGroupRules) by the dialing side; accept-side route groups are unaffected because they don't run policies.
func (*RouteGroup) SetReadDeadline ¶
func (rg *RouteGroup) SetReadDeadline(t time.Time) error
SetReadDeadline sets read deadline.
func (*RouteGroup) SetRotation ¶
func (rg *RouteGroup) SetRotation(hook RotationHook, applyAdd, applyAddForward func(excludeHops []string), interval time.Duration)
SetRotation attaches the rotation hook + apply-add callback + tick interval. Called once at construction time when the policy's decide_route returned a non-zero RotationIntervalSeconds. The actual rotation goroutine starts via startOffServiceLoops; calling SetRotation after that point is racy and unsupported.
applyAdd is the router-side callback that dials one more aux forward leg with the policy's ExcludeHops as the disjoint- intermediate filter. It runs in the rotation goroutine's own context so a slow setup-node dial doesn't block other route groups' rotation. applyAddForward is the FORWARD-ONLY analog (appendRouteAsymmetric addFwd=true/addRev=false), dialed for a RotationAction.AddForwardLeg; nil disables forward widening.
func (*RouteGroup) SetSelfHeal ¶
func (rg *RouteGroup) SetSelfHeal(applyAdd func(excludeHops []string), target int)
SetSelfHeal wires the background degree-restoration callback for a multiplexed route group. When a leg is pruned and the live degree drops below target, the route group dials replacement aux legs (via applyAdd) without blocking traffic — surviving legs carry the load meanwhile. target is the requested mux degree (legs the group should maintain). A target of 0 or 1 disables self-heal (a single-leg group has nothing to spread to).
func (*RouteGroup) SetTunnelRole ¶
func (rg *RouteGroup) SetTunnelRole(role string)
SetTunnelRole records the dialing app's label for this route group — "active" or "standby". No-op when role is empty, so a route group that is not one of a multi-tunnel app's tunnels keeps an empty role and the field is simply absent from its JSON. Guarded by rg.mu because MuxStats reads it from whichever goroutine is serving `visor state`.
func (*RouteGroup) SetWriteDeadline ¶
func (rg *RouteGroup) SetWriteDeadline(t time.Time) error
SetWriteDeadline sets write deadline.
func (*RouteGroup) TunnelRole ¶
func (rg *RouteGroup) TunnelRole() string
TunnelRole returns the dialing app's label for this route group ("active" / "standby"), or "" when it has none.
func (*RouteGroup) UploadSpeed ¶
func (rg *RouteGroup) UploadSpeed() uint32
UploadSpeed returns upload speed (bytes/s).
func (*RouteGroup) Write ¶
func (rg *RouteGroup) Write(p []byte) (n int, err error)
Write writes payload to a RouteGroup, splitting it into as many data frames as the wire format needs. A routing packet's payload length is a uint16, so one frame carries at most maxWritePayload bytes of application data. Before per-frame noise the stream-noise wrapper (EncryptConn) sliced every write into ≤4 KiB frames, so this path never saw a large payload; with per-frame noise the group receives the application's raw writes (net/rpc hands a >64 KiB gob reply down in a single call), and a payload over the frame limit has to be segmented here rather than rejected — the rejection burned a sequence number, and the peer's no-skip reorder buffer then waited on that hole forever (#4484 stage 2: hypervisor RPC over skynet stalled after the first 4 KiB of a Summary reply). Each segment goes through leg selection on its own, so a mux stripes them.
type RouteGroupConfig ¶
type RouteGroupConfig struct {
ReadChBufSize int
KeepAliveInterval time.Duration
// FEC advertises CapFEC in the mux handshake so repair frames are striped
// alongside data. Off by default (router Config.MuxFEC).
FEC bool
}
RouteGroupConfig configures RouteGroup.
func DefaultRouteGroupConfig ¶
func DefaultRouteGroupConfig() *RouteGroupConfig
DefaultRouteGroupConfig returns default RouteGroup config. Used by default if config is nil.
type RouteGroupDialer ¶
type RouteGroupDialer interface {
Dial(
ctx context.Context,
log *logging.Logger,
dmsgC *dmsg.Client,
setupNodes []cipher.PubKey,
req routing.BidirectionalRoute,
) (routing.EdgeRules, cipher.PubKey, error) // Returns rules and the connected setup node PK
}
RouteGroupDialer is an interface for RouteGroup dialers.
It lives in this net/http-free, untagged file (separate from the route-setup-side implementations in wrappers.go, which are //go:build !tinygo) so the Config field that references it compiles under the TinyGo js/wasm target. On TinyGo, NewSetupNodeDialer() returns a stub whose Dial reports that route dialing is unavailable (an edge-only browser visor receives routes but does not initiate route setup).
func NewSetupNodeDialer ¶
func NewSetupNodeDialer() RouteGroupDialer
NewSetupNodeDialer returns a wrapper for (*Client).DialRouteGroup.
func NewSetupNodeDialerFull ¶
func NewSetupNodeDialerFull(embedded EmbeddedSetupNode, relayCache *RSNRelayCache, tm *transport.Manager, forceLegacy bool) RouteGroupDialer
NewSetupNodeDialerFull returns a dialer with all capabilities: embedded RSN, transport relay, DMSG fallback, and source-driven cascade.
When forceLegacy is true the source-driven cascade is left nil, so every dial uses the legacy path: the setup node dials each hop directly over dmsg to reserve IDs and install rules. This is the default for now (the cascade has a multihop data-plane bug); operators opt back into the cascade via Routing.enable_cascade_route_setup once it is fixed.
func NewSetupNodeDialerWithEmbedded ¶
func NewSetupNodeDialerWithEmbedded(embedded EmbeddedSetupNode) RouteGroupDialer
NewSetupNodeDialerWithEmbedded returns a dialer that uses the embedded setup-node when available, falling back to remote setup-nodes.
type RouteGroupQueue ¶
type RouteGroupQueue struct {
Desc string `json:"desc"`
App string `json:"app,omitempty"`
Queue int `json:"queue"`
Capacity int `json:"capacity"`
// IntakeQueue/IntakeCapacity are the group's intake queue (see
// RouteGroup.inCh), and IntakeDrops counts packets the router dropped
// because it was full.
IntakeQueue int `json:"intake_queue"`
IntakeCapacity int `json:"intake_capacity"`
IntakeDrops uint64 `json:"intake_drops"`
}
RouteGroupQueue is one route group's inbound queue depths.
type RouteHopInfo ¶
type RouteHopInfo struct {
TpID string `json:"tp_id"` // Transport ID
From string `json:"from"` // Source public key (full, never truncated)
To string `json:"to"` // Destination public key (full, never truncated)
TpType string `json:"tp_type"` // Transport type (stcpr, sudph, dmsg)
// LatencyMS is this hop's transport RTT in ms, when known. The first
// hop is owned locally (tp.GetLatency). For a single-intermediate leg
// the far hop is derived (route RTT − first-hop RTT). Deeper hops are
// 0 here until sourced from TPD / the setup node's per-transport data.
LatencyMS float64 `json:"latency_ms,omitempty"`
}
RouteHopInfo contains detailed information about a single hop in a route.
type RouteSelectingHook ¶
type RouteSelectingHook interface {
DialHook
// SelectRoute is invoked synchronously after the route-finder
// returns candidates and before route setup. forward and
// reverse are the per-direction candidate lists (the same
// path returned for both directions when the route-finder
// query was symmetric). The hook may return Chosen / ReverseChosen
// indices (into the respective lists) or -1 to defer per
// direction. Drop=true refuses the dial with ErrDialPolicyDropped.
SelectRoute(ctx context.Context, info DialInfo, forward, reverse []CandidateInfo) (RouteSelection, error)
}
RouteSelectingHook is an optional secondary interface that a DialHook can implement to influence route selection AFTER the route-finder has returned its candidates. The router type- asserts for this interface; implementers that don't need candidate filtering simply omit SelectRoute and continue using the simpler BeforeDial path.
Both hook points fire per dial:
- BeforeDial (sync, before route-finder) — adjusts opts
- SelectRoute (sync, after route-finder) — picks a route
type RouteSelection ¶
type RouteSelection struct {
// Chosen is the forward-direction index into the forward
// candidates slice passed to SelectRoute, or -1 to defer.
Chosen int
// ReverseChosen is the reverse-direction index into the
// reverse candidates slice, or -1 to defer (router uses its
// built-in pickBestDirection latency rank for reverse).
// Independent of Chosen — a script can override forward but
// defer reverse, or vice versa.
ReverseChosen int
Drop bool
Distribution DistributionConfig
}
RouteSelection is the SelectRoute return value. Chosen is an index into the candidates slice passed in; -1 means "no preference, fall back to the router's built-in disjoint-path pick." Drop=true overrides everything and refuses the dial.
Distribution lets a SelectRoute hook reconsider the per-packet distribution descriptor after seeing the chosen candidate's transport-kinds / latency / hops_geo. The hook can branch on the candidate it picked and return a tailored distribution (e.g. "if route has stcpr+sudph legs: weighted 3,1 else: auto"). Mode == DistributionUnset means "no override at SelectRoute time" — falls back to whatever BeforeDial set on DialOptions.
type RouteSetupHook ¶
RouteSetupHook is an alias for a function that takes remote public key and a reference to transport manager in order to setup i.e: 1. If a transport of any type to the remote already exists, continue with route creation over it. 2. Otherwise, if the remote advertises stcpr or sudph, establish that transport and continue. 3. Otherwise try the remaining direct types in preference order, and a dmsg transport last.
type RouteSourceStats ¶
type RouteSourceStats struct {
// Requests is fetchBestRoutes calls; DirectHops is how many were served
// by a transport this visor already holds.
Requests int64 `json:"requests"`
DirectHops int64 `json:"direct_hops"`
// OracleAttempts is 2-hop calculations from the destination's own list
// through the setup node; OracleRoutes is how many found a route.
OracleAttempts int64 `json:"oracle_attempts"`
OracleRoutes int64 `json:"oracle_routes"`
// LocalAttached is routes built from the local graph before the route
// finder was asked (Config.PreferLocalRouteTo): each one is a route-finder
// query that did not happen.
LocalAttached int64 `json:"local_attached"`
// RouteFinderQueries is route-finder requests made (one per attempt).
RouteFinderQueries int64 `json:"route_finder_queries"`
// LocalFallback is routes built locally after the route finder missed or
// ran out of retries.
LocalFallback int64 `json:"local_fallback"`
// DirectSetups is one-hop routes set up with the peer, without a setup node.
DirectSetups int64 `json:"direct_setups"`
// DirectSetupFallbacks is one-hop routes the peer could not set up
// directly, sent to a setup node instead.
DirectSetupFallbacks int64 `json:"direct_setup_fallbacks"`
// ListAttempts is 3-hop calculations from other visors' own transport
// lists; ListRoutes is how many found a route. The ListNo* counters say
// why the rest did not: no usable first hop, no list from the destination,
// no list from any neighbor, or no path through the lists that came back.
ListAttempts int64 `json:"list_attempts"`
ListRoutes int64 `json:"list_routes"`
ListNoFirstHop int64 `json:"list_no_first_hop"`
ListNoDstList int64 `json:"list_no_dst_list"`
ListNoNeighborList int64 `json:"list_no_neighbor_list"`
ListNoPath int64 `json:"list_no_path"`
// ListExcluded is list routes found but every candidate was a held or
// recently dead first hop.
ListExcluded int64 `json:"list_excluded"`
// ListFetches is neighbor lists asked for; ListFetchFails is how many
// did not come back.
ListFetches int64 `json:"list_fetches"`
ListFetchFails int64 `json:"list_fetch_fails"`
}
RouteSourceStats counts fetchBestRoutes outcomes since the router started.
type RouteStatus ¶
type RouteStatus struct {
// Connection identity
LocalPK cipher.PubKey `json:"local_pk"`
RemotePK cipher.PubKey `json:"remote_pk"`
LocalPort routing.Port `json:"local_port"`
RemotePort routing.Port `json:"remote_port"`
// Initiator is true when this visor dialed the route (called
// router.DialRoutes). False when this visor accepted the route
// from a remote setup-node request.
Initiator bool `json:"initiator"`
// Status
Alive bool `json:"alive"`
Latency time.Duration `json:"latency_ns"`
// Traffic stats
BandwidthSent uint64 `json:"bandwidth_sent"`
BandwidthReceived uint64 `json:"bandwidth_received"`
UploadSpeed uint32 `json:"upload_speed"`
DownloadSpeed uint32 `json:"download_speed"`
// Mux info
MuxEnabled bool `json:"mux_enabled"`
Transports []RouteTransport `json:"transports"`
// Hops is the full forward path for this route group, populated from
// rg.forwardHops. Empty if the path wasn't recorded (older routes or
// the fallback reconstruction).
Hops []RouteHopInfo `json:"hops,omitempty"`
}
RouteStatus contains detailed status of a single active route connection, including all mux transports and live traffic statistics.
type RouteTransport ¶
type RouteTransport struct {
ID uuid.UUID `json:"id"`
Type string `json:"type"`
RemotePK cipher.PubKey `json:"remote_pk"`
Latency float64 `json:"latency_ms"`
FwdRuleID routing.RouteID `json:"fwd_rule_id"`
// RvsRuleID is the matching reverse (Consume) rule on this side.
// Paired by index with FwdRuleID.
RvsRuleID routing.RouteID `json:"rvs_rule_id"`
}
RouteTransport describes one transport within a muxed route.
type Router ¶
type Router interface {
io.Closer
// DialRoutes dials to a given visor of 'rPK'.
// 'lPort'/'rPort' specifies the local/remote ports respectively.
// A nil 'opts' input results in a value of '1' for all DialOptions fields.
// A single call to DialRoutes should perform the following:
// - Find routes via RouteFinder (in one call).
// - Setup routes via SetupNode (in one call).
// - Save to routing.Table and internal RouteGroup map.
// - Return RouteGroup if successful.
DialRoutes(ctx context.Context, rPK cipher.PubKey, lPort, rPort routing.Port, opts *DialOptions) (net.Conn, error)
PingRoute(ctx context.Context, rPK cipher.PubKey, lPort, rPort routing.Port, opts *DialOptions) (net.Conn, error)
// DialRoutesDatagram dials a faithful-UDP (#2607) route. It performs a
// normal DialRoutes (forcing opts.Datagram=true so the reliable route's
// Noise handshake also keys a DatagramRouteGroup sibling), then returns
// both the reliable conn (which the caller must keep alive — closing it
// tears down the route and the sibling) and the datagram sibling. Errors
// if the peer did not also register datagram intent for the port (no
// sibling registered).
DialRoutesDatagram(ctx context.Context, rPK cipher.PubKey, lPort, rPort routing.Port, opts *DialOptions) (net.Conn, *DatagramRouteGroup, error)
// RegisterDatagramPort / UnregisterDatagramPort mark a local port as
// faithful-UDP-capable (#2607). The accept side builds a datagram
// sibling only for routes whose local port is registered.
RegisterDatagramPort(port routing.Port)
UnregisterDatagramPort(port routing.Port)
// AcceptDatagram blocks until an accept-side datagram sibling is
// available (or ctx/visor is done), returning it and the local port
// it targets. Drained by the forwarded_ports.udp server loop.
AcceptDatagram(ctx context.Context) (*DatagramRouteGroup, routing.Port, error)
// AcceptRoutes should block until we receive an AddRules packet from SetupNode
// that contains ConsumeRule(s) or ForwardRule(s).
// Then the following should happen:
// - Save to routing.Table and internal RouteGroup map.
// - Return the RoutingGroup.
AcceptRoutes(context.Context) (net.Conn, error)
SaveRoutingRules(rules ...routing.Rule) error
ReserveKeys(n int) ([]routing.RouteID, error)
IntroduceRules(rules routing.EdgeRules) error
Serve(context.Context) error
SetupIsTrusted(cipher.PubKey) bool
SetMinHop(uint16)
// EffectiveMinHops resolves a dial's min-hops constraint from the
// per-dial options and the visor-global setting together. Exposed
// because callers OUTSIDE the router take dial shortcuts that bypass
// route setup — appnet's direct dial above all — and cannot honor the
// operator's constraint without being able to ask what it is.
EffectiveMinHops(opts *DialOptions) uint16
SetExistingTPOnly(bool)
SetForceLocalRoutes(bool)
// SetDstTransportOracle wires the RSN-oracle 2-hop route path's
// destination-transport oracle (see rsn_oracle_routes.go). nil leaves the
// path inert.
SetDstTransportOracle(DstTransportOracle)
SetMuxMode(WeightMode)
// SetMuxFEC / GetMuxFEC mirror Config.MuxFEC at runtime; the setting is
// read when a mux route group is BUILT, so it reaches new groups only.
SetMuxFEC(bool)
GetMuxFEC() bool
GetExistingTPOnly() bool
GetForceLocalRoutes() bool
GetLastRouteCalcTime() time.Duration
ActiveRouteStatuses() []RouteStatus
RoutingTableStats() routing.RoutingTableStats
AddMuxRouteByHops(desc routing.RouteDescriptor, fwd, rev []routing.Hop) error
GrowMuxRoute(desc routing.RouteDescriptor, target, minHops int) (int, error)
// GrowMuxFromPool grows the tunnel route group this visor dialed from
// localPort by up to legs additional mux legs, taking the plans from the
// app's STANDBY tunnels to the same exit (already ranked, first-hop
// transport already up) and falling back to GrowMuxRoute's route-finder
// path for whatever the pool cannot cover. See pool_legs.go.
GrowMuxFromPool(localPort routing.Port, legs, minHops int) (int, error)
RemoveMuxRouteByTransport(desc routing.RouteDescriptor, tpID uuid.UUID) error
// SetMuxDirectionForApp applies a manual unidirectional direction pin
// (routing.DirectionAuto / DirectionPinDefault / DirectionPinFlipped) to
// EVERY active directional route group tagged with the named app (a proxy
// session can hold several concurrent rg's). Returns how many groups the
// pin was applied to; errors when the app has no active directional group.
SetMuxDirectionForApp(appName string, mode byte) (int, error)
// RouteGroupHops returns the stored forward route hops for the route group
// matching the given descriptor (or its inversion). Returns nil if no
// matching route group is found.
RouteGroupHops(desc routing.RouteDescriptor) []RouteHopInfo
// DialHook returns the configured routing-policy hook (nil
// if none configured). Visor wiring uses this to bridge the
// policy into non-router dial paths like VStreamMux's direct
// dial — the policy script's BeforeDial fires there too with
// ctx.is_direct_dial=true.
DialHook() DialHook
// RouteGroupMuxInfo returns a snapshot of per-leg mux state for
// the rg matching desc (or its inversion). Returns false when no
// matching active rg is found. Used by 'cli proxy mux-info' to
// surface per-leg bandwidth + latency for diagnostic purposes.
RouteGroupMuxInfo(desc routing.RouteDescriptor) (MuxInfo, bool)
// RouteGroupMuxInfoForApp returns mux snapshots for every active
// rg whose source port maps (via AppLookup) to the named app.
// One app may have multiple rg's at once (e.g. each SOCKS5
// client connection to skysocks-client gets its own rg). Returns
// an empty slice if no rg's are active for the app.
RouteGroupMuxInfoForApp(appName string) []MuxInfo
// RouteGroupMuxInfoAll returns mux snapshots for EVERY active
// route group, regardless of the app it is tagged with. Powers the
// whole-runtime `cli visor state` view where the per-leg mux shape
// of all route groups (not just one app's) is wanted at once.
RouteGroupMuxInfoAll() []MuxInfo
// CloseRouteGroupsForApp closes and de-registers every route group
// tagged with appName — the app's own tunnels plus the sibling groups
// a multi-tunnel dial (--tunnels N) created alongside them. Returns
// the number of groups closed. Called when the app stops, so a group
// the app will never read from again does not keep refreshing its
// rules by keep-alive and does not show up in `proxy mux info` (or
// the --route reconcile count) for a stopped app.
CloseRouteGroupsForApp(appName string) int
// MuxEvents returns the bounded history of route-group and mux-leg
// changes — each add, removal, park, re-home and group close with the
// reason the code gave and who initiated it. This is where to look when
// a leg that was in `proxy mux info` is gone: the visor log ring holds
// minutes, this holds the events.
MuxEvents() []MuxEvent
// MuxCounters returns the whole-router cumulative counters (tunnel
// promotions, leg re-homes sent/received/acked/failed, forward fan-out
// engage/release) that the bounded MuxEvents ring above does not retain
// once it wraps.
MuxCounters() MuxCounters
// NoteTunnelEvent records an event the APP that holds a tunnel decided —
// a standby tunnel promoted into the active set, an active one parked, a
// dead one retired — on the route group dialed from localPort, and
// updates that group's TunnelRole when role is non-empty.
//
// It exists because the two halves of the answer live in different
// processes. Only the app knows which of its tunnels carries streams;
// only the router knows which transport and route the tunnel leaves over,
// and only the router owns the event ring `visor state --select diag`
// reads. The app names the tunnel by the local port it dialed from — the
// one identifier it is handed at dial time (DialResp.LocalPort) — and the
// router fills in the rest.
//
// Reports whether a route group with that local port was found. Both
// sides of the descriptor are tried, so it works for a group keyed by
// either the dialing or the mirrored descriptor.
NoteTunnelEvent(localPort routing.Port, event, reason, role string) bool
// Routing table related methods
RoutesCount() int
Rules() []routing.Rule
Rule(routing.RouteID) (routing.Rule, error)
SaveRule(routing.Rule) error
DelRules([]routing.RouteID)
}
Router is responsible for creating and keeping track of routes. Internally, it uses the routing table, route finder client and setup client.
type RulesMap ¶
RulesMap associates a slice of rules to a visor's string of `public key:port“.
func GenerateRules ¶
func GenerateRules(idR IDReserver, routes []routing.Route) (fwdRules, revRules, interRules RulesMap, err error)
GenerateRules generates rules for given forward and reverse routes. The outputs are as follows: - maps that relate slices of forward, consume and intermediary routing rules to a given visor's public key. - an error (if any).
type SelfHealTargeter ¶
SelfHealTargeter is an OPTIONAL interface a RotationHook may implement to override the route group's self-heal degree at runtime. A group's self-heal target is otherwise fixed at dial time (SetSelfHeal), but an adaptive preset's pool size is a LIVE tunable (mux width / warm-standby reserve, retuned over the mux-control RPC). A hook that implements this pushes the CURRENT target on every tick, so lowering the pool at runtime actually re- caps the self-heal instead of re-dialing back toward the stale dial-time value. ok=false means "leave the dial-time target unchanged" (a non-adaptive preset, whose target is a fixed policy width).
type SetupClient ¶
type SetupClient struct {
// contains filtered or unexported fields
}
SetupClient is an RPC client for setup node.
func NewSetupClient ¶
func NewSetupClient(ctx context.Context, log *logging.Logger, dmsgC *dmsg.Client, setupNodes []cipher.PubKey) (*SetupClient, error)
NewSetupClient creates a new SetupClient.
func (*SetupClient) Capabilities ¶
func (c *SetupClient) Capabilities(ctx context.Context) ([]string, error)
Capabilities asks the setup node which request shapes it understands, and caches the answer for the life of this client (one client is one connection, and a node's capabilities do not change under a live connection).
A setup node that predates the Capabilities RPC answers with net/rpc's "can't find method"; that is not an error here, it is the answer — an empty capability list, meaning only the original DialRouteGroup. This is what lets the batched form ship without a flag on either side.
func (*SetupClient) ConnectedNode ¶
func (c *SetupClient) ConnectedNode() cipher.PubKey
ConnectedNode returns the public key of the setup node that was successfully connected.
func (*SetupClient) DialRouteGroup ¶
func (c *SetupClient) DialRouteGroup(ctx context.Context, req routing.BidirectionalRoute) (routing.EdgeRules, error)
DialRouteGroup generates rules for routes from a visor and sends them to visors.
func (*SetupClient) DialRouteGroupBatch ¶
func (c *SetupClient) DialRouteGroupBatch(ctx context.Context, batch routing.BidirectionalRouteBatch) (routing.BidirectionalRouteBatchReply, error)
DialRouteGroupBatch sets up every route in the batch in ONE request. Only call it when Capabilities advertised CapBatchRouteSetup.
A nil error does NOT mean every route installed: the reply carries a result per route and partial success is the normal outcome of a batch whose members leave over different intermediates.
func (*SetupClient) FetchRelayPeers ¶
FetchRelayPeers queries the RSN for its transport peers. The visor caches these to enable relay-based route setup without DMSG.
func (*SetupClient) RPCClient ¶
func (c *SetupClient) RPCClient() *rpc.Client
RPCClient returns the underlying net/rpc client so the source-driven cascade orchestrator can issue the CascadeSign* RPCs over the same DMSG connection used for the legacy DialRouteGroup path.
func (*SetupClient) SignTransportQuery ¶
func (c *SetupClient) SignTransportQuery(ctx context.Context, src, dst cipher.PubKey) (*TransportQuery, error)
SignTransportQuery asks the RSN to sign a transport-query capability targeting dst on behalf of src (see the RSN-oracle 2-hop route path). The signed query is carried by the source to dst, which verifies it against its trusted-RSN allowlist before returning its transport list.
type SetupConfig ¶
type SetupConfig struct {
PK cipher.PubKey `json:"public_key"`
SK cipher.SecKey `json:"secret_key"`
Dmsg dmsgc.DmsgConfig `json:"dmsg"`
TransportDiscovery string `json:"transport_discovery"`
LogLevel string `json:"log_level"`
// Cascade configures the cascade route setup protocol.
// When set, the RSN attempts to install routing rules via transport-level
// cascade (route ID 0) before falling back to DMSG-based setup.
Cascade *CascadeConfig `json:"cascade,omitempty"`
// Transport configures the RSN's own transport manager.
// When set, the RSN creates STCPR transports with label "setup" and
// uses them for cascade route setup.
Transport *SetupTransportConfig `json:"transport,omitempty"`
}
SetupConfig defines configuration parameters for setup Node.
type SetupPathStats ¶
type SetupPathStats struct {
// Batched is how many routes left inside a batched setup request.
Batched uint64 `json:"batched"`
// Singles is how many left on their own because no sibling dial to the
// same exit was in the collection window.
Singles uint64 `json:"singles"`
// Fallback is how many fell back to a single request because the setup
// node did not advertise batch-route-setup.
Fallback uint64 `json:"fallback"`
// OracleQueries is how many signed destination-transport queries were
// actually issued, OracleShared how many dials rode one another's query,
// and OracleHits how many read a cached candidate set outright. A fill of
// N tunnels should show ONE query and N-1 shared/hit.
OracleQueries uint64 `json:"oracle_queries"`
OracleHits uint64 `json:"oracle_hits"`
// OracleClaimed is how many dials were handed a candidate no sibling held;
// OracleCrowded how many found every candidate claimed and fell back to the
// best one. A healthy fill is all claimed.
OracleClaimed uint64 `json:"oracle_claimed"`
OracleCrowded uint64 `json:"oracle_crowded"`
// OracleExits / OracleLegs are the cache's current size: exits with a live
// candidate set, and total candidates across them.
OracleExits int `json:"oracle_exits"`
OracleLegs int `json:"oracle_legs"`
// WarmHits / WarmMisses / WarmStored are the shared plan pool's counters —
// the aux-leg side of the same planning work.
WarmHits uint64 `json:"warm_hits"`
WarmMisses uint64 `json:"warm_misses"`
WarmStored uint64 `json:"warm_stored"`
// Cascade outcomes, split by whether the request reached the setup node
// over dmsg or over a transport. Legacy is a DialRouteGroup, where the
// setup node installs the rules on each hop itself.
CascadeOKDmsg uint64 `json:"cascade_ok_dmsg"`
CascadeOKTransport uint64 `json:"cascade_ok_transport"`
CascadeFailedDmsg uint64 `json:"cascade_failed_dmsg"`
CascadeFailedTransport uint64 `json:"cascade_failed_transport"`
LegacyDmsg uint64 `json:"legacy_dmsg"`
LegacyTransport uint64 `json:"legacy_transport"`
}
SetupPathStats is the visor-side companion to the setup node's requests_by_kind: how this visor's own route setups were issued.
type SetupRPCGateway ¶
type SetupRPCGateway struct {
Metrics setupmetrics.Metrics
Ctx context.Context
Conn net.Conn
ReqPK cipher.PubKey
Dialer network.Dialer
Pool *ClientPool // optional: reuse connections across requests
Cascade *CascadeBuilder // optional: cascade route setup
Timeout time.Duration
}
SetupRPCGateway is a RPC interface for setup node.
func (*SetupRPCGateway) Capabilities ¶
func (g *SetupRPCGateway) Capabilities(_ *CapabilitiesArgs, reply *CapabilitiesReply) error
Capabilities is the handshake a client uses before sending a newer request shape. A setup node that predates this method answers with net/rpc's "can't find method" error, which the client reads as "no capabilities beyond the original DialRouteGroup" — so the negotiation needs no version number and no flag.
func (*SetupRPCGateway) CascadeSignInstall ¶
func (g *SetupRPCGateway) CascadeSignInstall(args *CascadeSignInstallArgs, reply *CascadeSignInstallReply) error
CascadeSignInstall is the RSN-side half of the cascade install phase. It reconstructs the IDReserver from the source-collected route IDs, recomputes the routing rules deterministically (GenerateRules), and builds+signs the nested per-hop install cascades. It returns the install bytes for the source to inject, plus the initiating EdgeRules.
func (*SetupRPCGateway) CascadeSignReserve ¶
func (g *SetupRPCGateway) CascadeSignReserve(args *CascadeSignReserveArgs, reply *CascadeSignReserveReply) error
CascadeSignReserve is the RSN-side half of the cascade reserve phase. The RSN is a pure signing oracle: it builds and signs the nested per-hop reserve cascades for the bidirectional route and returns the bytes plus the session IDs. It never dials hops or sends anything itself.
func (*SetupRPCGateway) DialRouteGroup ¶
func (g *SetupRPCGateway) DialRouteGroup(route routing.BidirectionalRoute, rules *routing.EdgeRules) (err error)
DialRouteGroup dials RouteGroups for route and rules.
func (*SetupRPCGateway) DialRouteGroupBatch ¶
func (g *SetupRPCGateway) DialRouteGroupBatch(batch *routing.BidirectionalRouteBatch, reply *routing.BidirectionalRouteBatchReply) error
DialRouteGroupBatch sets up every route in the batch in ONE request, with the per-hop work coalesced: one id reservation per distinct hop covering every route that traverses it, one intermediary-rule install per distinct hop carrying that hop's rules for every route.
Partial success is normal and is NOT an RPC error: a member whose intermediate is unreachable comes back with a per-route Error while its siblings come back installed. An error return means the batch as a whole was refused (malformed, or oversized).
func (*SetupRPCGateway) HealthCheck ¶
func (g *SetupRPCGateway) HealthCheck(_ *HealthCheckArgs, reply *HealthCheckReply) error
HealthCheck to test if the setup node is responsive.
func (*SetupRPCGateway) RelayPeers ¶
func (g *SetupRPCGateway) RelayPeers(_ *RelayPeersArgs, reply *RelayPeersReply) error
RelayPeers returns the PKs of visors that have direct transports to this RSN. Visors call this to populate their relay cache so subsequent route setup requests can be relayed through these peers instead of using DMSG.
func (*SetupRPCGateway) SignTransportQuery ¶
func (g *SetupRPCGateway) SignTransportQuery(args *SignTransportQueryArgs, reply *SignTransportQueryReply) error
SignTransportQuery is the RSN-side signing endpoint for the RSN-oracle 2-hop route path. It mints a nonce, signs a TransportQuery targeting args.TargetPK on behalf of args.RequesterPK, and returns it. Reuses the RSN keypair the cascade builder already holds (g.Cascade.rsnPK / rsnSK), so the same trusted-RSN authorization that gates cascade route setup gates the query.
type SetupTransportConfig ¶
type SetupTransportConfig struct {
// STCPRAddr is the listen address for STCPR (e.g. ":7780").
STCPRAddr string `json:"stcpr_addr,omitempty"`
// AddressResolver is the AR URL. Empty = no AR registration (RSN-only outbound).
AddressResolver string `json:"address_resolver,omitempty"`
// MaxTransports limits the number of transports the RSN maintains.
MaxTransports int `json:"max_transports,omitempty"`
// DefaultLabel is the transport label (default: "setup").
DefaultLabel string `json:"label,omitempty"`
}
SetupTransportConfig configures transport connectivity for the RSN.
type Shape ¶
type Shape struct {
// Legs is per-tunnel, widest first, so two sessions holding the same
// multiset of tunnels print and compare the same.
Legs []int
}
Shape is a session's multiplexing shape: one entry per ACTIVE tunnel, holding that tunnel's leg count. The zero Shape is "no active tunnel".
func (Shape) Chains ¶
Chains is Σ n_i — the chains the shape spends on tunnels. The session's full chain budget is this plus its standby pool, and every shape MOVE conserves that total.
type SignTransportQueryArgs ¶
type SignTransportQueryArgs struct {
RequesterPK cipher.PubKey // source S making the request
TargetPK cipher.PubKey // destination D whose transports are requested
}
SignTransportQueryArgs is the request for SignTransportQuery: the source asks the RSN to sign a transport-query capability targeting a destination.
type SignTransportQueryReply ¶
type SignTransportQueryReply struct {
Query *TransportQuery
}
SignTransportQueryReply carries the RSN-signed query. The RSN is a pure signing oracle here too: it signs the (RSN, target, requester, nonce) tuple and returns it; it does not contact the destination. The SOURCE carries the signed query to the destination (phase-1: dmsg-direct) and the destination verifies it against its trusted-RSN allowlist before responding.
type TransportListFetcher ¶ added in v1.3.100
type TransportListFetcher interface {
FetchTransportList(ctx context.Context, pk cipher.PubKey) (*transport.SignedList, error)
}
TransportListFetcher fetches another visor's signed transport list over a skywire transport (GET /transports on its :80). The visor implements it.
type TransportQuery ¶
type TransportQuery struct {
RSNPK cipher.PubKey `json:"rsn_pk"` // RSN that authorized this query
TargetPK cipher.PubKey `json:"target_pk"` // destination D whose transports are requested
RequesterPK cipher.PubKey `json:"requester_pk"` // source S making the request
Nonce uint64 `json:"nonce"` // anti-replay, unique per query
RSNSig cipher.Sig `json:"rsn_sig"` // RSN signature over SignablePayload
}
TransportQuery is an RSN-signed capability that authorizes the requester S to learn destination D's transport list. The RSN signs (RSNPK, TargetPK, RequesterPK, Nonce); D verifies the signature against its OWN PK and its trusted-RSN allowlist before responding. The signature binds the query to a single destination and requester, so it cannot be replayed against another visor or claimed by another requester.
func UnmarshalTransportQuery ¶
func UnmarshalTransportQuery(data []byte) (*TransportQuery, error)
UnmarshalTransportQuery deserializes a query from JSON.
func (*TransportQuery) Marshal ¶
func (q *TransportQuery) Marshal() ([]byte, error)
Marshal serializes the query as JSON.
func (*TransportQuery) Sign ¶
func (q *TransportQuery) Sign(rsnSK cipher.SecKey) error
Sign signs the query with the RSN's secret key. Reuses the cascade signing primitive: SHA256 of the signable payload, signed by the RSN.
func (*TransportQuery) SignablePayload ¶
func (q *TransportQuery) SignablePayload() []byte
SignablePayload returns the bytes signed/verified for this query: the RSN, target and requester PKs plus the nonce. The layout mirrors CascadeSetup.SignablePayload so the same cipher.Sign/Verify discipline applies.
func (*TransportQuery) Verify ¶
func (q *TransportQuery) Verify(localPK cipher.PubKey, trustedRSNs map[cipher.PubKey]struct{}) error
Verify checks the query against the responding visor's own PK and its trusted-RSN allowlist. It enforces, in order: (1) TargetPK is us, (2) the RSN is trusted, (3) the signature verifies. Same authorization model as CascadeHandler.handleSetup.
type TransportQueryDeliverer ¶
type TransportQueryDeliverer interface {
DeliverTransportQuery(ctx context.Context, dst cipher.PubKey, q *TransportQuery) (*TransportQueryResponse, error)
}
TransportQueryDeliverer carries a signed transport-query to the destination visor and returns its response. PHASE-1 delivery is dmsg-direct to D (the mechanism `tp --remote` already uses to reach a remote visor); PHASE-2 is carrying the query over transports via intermediates like the cascade itself. The interface keeps computeDisjoint2HopRoutes' caller (fetchDstTransportsViaOracle) independent of the transport used, so the delivery seam can be filled in without touching the route computation.
func NewDmsgTransportQueryDeliverer ¶
func NewDmsgTransportQueryDeliverer(dmsgC *dmsg.Client, log *logging.Logger) TransportQueryDeliverer
NewDmsgTransportQueryDeliverer builds a dmsg-direct query deliverer.
type TransportQueryRPCGateway ¶
type TransportQueryRPCGateway struct {
LocalPK cipher.PubKey
// TrustedRSNs returns the current trusted-RSN public keys (typically
// conf.EffectiveRouteSetupNodes) so allowlist edits at runtime are honored.
TrustedRSNs func() []cipher.PubKey
TM *transport.Manager
Log *logging.Logger
}
TransportQueryRPCGateway is the DESTINATION-side (D) RPC gateway that answers RSN-oracle transport-list queries. It verifies the RSN signature against the visor's trusted-RSN allowlist (via TrustedRSNs) and returns D's own transports in compact form.
func (*TransportQueryRPCGateway) Query ¶
func (g *TransportQueryRPCGateway) Query(q *TransportQuery, reply *TransportQueryResponse) error
Query is the net/rpc method D exposes. It verifies the signed query and returns D's transport list. Errors (untrusted RSN, bad signature, wrong target) propagate to the requester as RPC errors.
type TransportQueryResponse ¶
type TransportQueryResponse struct {
TargetPK cipher.PubKey `json:"target_pk"`
Entries []transport.CompactEntry `json:"entries"`
}
TransportQueryResponse carries destination D's OWN transport list in the compact wire form (remote edge + type; see transport.CompactEntry). The requester reconstructs full entries with transport.EntryFromCompact(TargetPK, entry) — the reporter being D itself.
func BuildTransportQueryResponse ¶
func BuildTransportQueryResponse( q *TransportQuery, localPK cipher.PubKey, trustedRSNs map[cipher.PubKey]struct{}, tm *transport.Manager, ) (*TransportQueryResponse, error)
BuildTransportQueryResponse is the DESTINATION-side (D) handler. Given a signed query, the responding visor's PK, its trusted-RSN allowlist and its transport manager, it verifies the RSN authorization and returns D's own live data-plane transports in compact form.
It applies the same exclusions the local route calculation uses: closed / black-holed transports and LabelSetup (RSN control-plane) transports are never advertised as route-able edges. The response is built by walking D's transports and compacting each relative to D's own PK, so every entry reconstructs (via EntryFromCompact) to the canonical Entry both endpoints agree on.
func UnmarshalTransportQueryResponse ¶
func UnmarshalTransportQueryResponse(data []byte) (*TransportQueryResponse, error)
UnmarshalTransportQueryResponse deserializes a response from JSON.
func (*TransportQueryResponse) Marshal ¶
func (r *TransportQueryResponse) Marshal() ([]byte, error)
Marshal serializes the response as JSON.
type WeightMode ¶
type WeightMode int
WeightMode controls how the transport selector distributes packets.
const ( // WeightModeAuto uses latency-based weighting (faster transports get more packets). // Falls back to equal round-robin when no latency data is available. WeightModeAuto WeightMode = iota // WeightModeEqual distributes packets equally across all transports (round-robin). WeightModeEqual // WeightModeExplicit uses operator-supplied fractional // weights stored in explicitWeights. Normalized into an // integer schedule at Rebuild time. Set via the routing- // policy DSL's `distribution="weighted: f1, f2, ..."`. WeightModeExplicit // WeightModeSizeThreshold routes packets by payload size, // not by a pre-built schedule: > sizeThreshold goes to leg // 0, smaller packets RR across the rest. The Select() // fallback path returns 0 for callers that don't supply a // size (handshake / control packets); SelectForSize is the // primary entry point. WeightModeSizeThreshold // WeightModeSticky5Tuple hashes the IPv4 5-tuple of the // payload (src/dst IP + src/dst port + protocol) and picks // a leg by hash % live-leg-count. Same flow → same leg // always, deterministic. SelectForPayload is the entry // point; non-IPv4 payloads fall back to a payload-prefix // FNV hash. WeightModeSticky5Tuple // WeightModeLatencyAdaptive picks the live leg with the // lowest current GetLatency() per packet. Differs from // WeightModeAuto (latency-weighted schedule, rebuilt // periodically): adaptive evaluates per-packet, so a leg // whose RTT spikes mid-session loses traffic immediately. // Cost is one linear scan over the leg array per packet // (small constant for typical mux counts). WeightModeLatencyAdaptive // WeightModeDSCPPriority reads the IPv4 DSCP (upper 6 bits // of payload[1]) and routes packets >= threshold to leg 0, // others round-robin across the rest. Non-IPv4 payloads // fall back to round-robin. WeightModeDSCPPriority // WeightModeCapacity weights each leg by its recently-measured // throughput (capacityWeights, set by the mux from per-leg // byte deltas), with a per-leg exploration floor so every live // leg keeps a share. Bootstraps as equal round-robin until the // mux has throughput samples. Contrast WeightModeAuto, which // weights by inverse LATENCY and so starves a fat-but-slow leg; // capacity fills each leg toward its bandwidth — the mode that // actually aggregates throughput across a disjoint mux. WeightModeCapacity // WeightModeECF is a PREDICTIVE hold-back scheduler adapted from ECF // (Earliest Completion First, Lim et al., CoNEXT 2017). Capacity/auto // weighting keeps assigning a share of in-order frames to slower legs; in // a reorder buffer that must deliver strictly in order, every frame on a // slow leg is a head-of-line stall, so goodput/latency-proportional // spraying does NOT aggregate under path heterogeneity (~25% of ideal in // the MPTCP/MP-QUIC literature). ECF instead sends on the fastest leg // while it has send capacity, and spills onto a slower leg ONLY when the // slow leg would deliver its frame sooner than the fast leg can drain its // own backlog — otherwise it holds the frame on the fast leg. skywire has // no TCP cwnd, so the cwnd/"has capacity" test is adapted to a per-leg // bandwidth-delay-product (rate*RTT) and a selector-maintained in-flight // byte estimate; see ecfPick and SelectECF. This is the aggregation mode // that stops a heterogeneous mux from HoL-stalling on its slow legs. WeightModeECF // WeightModeOTIAS is the Out-of-order Transmission for In-order Arrival // scheduler (Yang et al.). It reasons over the SAME per-leg estimator // snapshot as ECF (ecfLegState), but instead of ECF's fast-leg-with-hold-back // rule it assigns each segment to the leg whose ESTIMATED ARRIVAL is soonest: // queueing delay (current backlog / send rate) + one-way propagation (RTT/2). // Charging the chosen leg makes successive picks fan out by arrival time, so // OTIAS deliberately hands a later segment to a slower-but-idle leg once the // fast leg's queue would make it arrive later. See otiasPick / SelectOTIAS. WeightModeOTIAS // WeightModeSTMS is the Slide Together Multipath Scheduler (Shi et al., // USENIX ATC 2018). It keeps the head of the stream on the fast leg — filling // that leg's BDP send window first so EARLIER data always rides the fast path // — and only once the fast window is full places the following (later) data // on the soonest-arriving slower leg, so the pieces converge in order. Unlike // ECF, STMS does not decline a slow leg once it is in the active set (ECF's // hold-back predicate); it commits later data to it and lets the reorder // buffer absorb the gap. See stmsPick / SelectSTMS. WeightModeSTMS )
func (WeightMode) String ¶
func (m WeightMode) String() string
String renders the weight mode as the same lowercase token the routing- policy DSL's DistributionMode uses, so an operator reading `visor state` sees exactly the value they would set via a policy's `distribution=`.
Source Files
¶
- bottleneck.go
- cascade_ack_registry.go
- cascade_builder.go
- cascade_handler.go
- cascade_relay.go
- cascade_route.go
- cascade_source.go
- cascade_source_iface.go
- client_pool.go
- datagram_crypto.go
- datagram_keys.go
- datagram_route_group.go
- datagram_setup.go
- dead_route_cache.go
- delivery_crc.go
- dial_first_hop_holds.go
- dial_hook.go
- dial_tunnel_legs.go
- direct_setup.go
- direct_setup_rpc_native.go
- dmsg_wrapper.go
- fec.go
- fec_flush.go
- fec_mux.go
- hol_retx.go
- id_reserver.go
- leg_adopt.go
- leg_hops_match.go
- leg_rehome.go
- leg_rtt_window.go
- leg_split.go
- local_route_bfs.go
- map.go
- mock_id_reserver.go
- mock_route_group_dialer.go
- mock_router.go
- mux_control_frame.go
- mux_counters.go
- mux_events.go
- mux_scheduler.go
- mux_shape.go
- mux_shape_converge.go
- mux_shape_retry.go
- network_stats.go
- noise_route_group.go
- oracle_plan_cache.go
- parallel_route_config.go
- parallel_route_setup.go
- park_hysteresis.go
- pk_sort.go
- pool_arbiter.go
- pool_legs.go
- rack_tlp.go
- reorder.go
- route_group.go
- route_group_close.go
- route_group_dialer.go
- route_group_hops.go
- route_group_intake.go
- route_group_legs.go
- route_group_noise.go
- route_group_ping.go
- route_group_sack.go
- route_group_services.go
- route_group_stats.go
- route_mux.go
- route_mux_ack.go
- route_mux_delivery.go
- route_mux_legs.go
- route_mux_record.go
- route_mux_select.go
- route_mux_window.go
- route_status.go
- router.go
- router_config.go
- router_dial.go
- router_dial_filter.go
- router_dial_local.go
- router_dial_mux.go
- router_dial_select.go
- router_forward.go
- router_gc.go
- router_intake.go
- router_leg_controls.go
- router_mux_ops.go
- router_packet.go
- router_pending.go
- router_pending_legs.go
- router_route_source.go
- router_rules.go
- router_serve.go
- routerclient.go
- rpc_gateway.go
- rpc_register_native.go
- rsn_oracle_routes.go
- rules_map.go
- sack.go
- sbd_trial.go
- service_gate.go
- settings.go
- settings_defaults.go
- settings_dial.go
- settings_group.go
- setup_batch.go
- setup_batch_client.go
- setup_path_stats.go
- setup_rpc_gateway.go
- setupclient.go
- setupconfig.go
- setupnode.go
- tpd_cache.go
- transport_close.go
- transport_list_routes.go
- transport_query.go
- transport_query_dmsg.go
- transport_selector.go
- unidir.go
- warm_route_pool.go
- wrappers.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package emu pkg/router/emu/conn.go
|
Package emu pkg/router/emu/conn.go |
|
Package policy pkg/router/policy/bridge.go c2-net-routing RoutingContext / Candidate / RouteSpec to and from Starlark values, and assembles the pre-declared module set the policy script sees on load.
|
Package policy pkg/router/policy/bridge.go c2-net-routing RoutingContext / Candidate / RouteSpec to and from Starlark values, and assembles the pre-declared module set the policy script sees on load. |
|
preset
Package preset pkg/router/policy/preset/engines.go c2-net-routing
|
Package preset pkg/router/policy/preset/engines.go c2-net-routing |
|
presethook
Package presethook pkg/router/policy/presethook/presethook.go c2-net-routing adapts a built-in routing-policy preset (pkg/router/policy/preset) to the router.DialHook / RouteSelectingHook / RotationHook interfaces WITHOUT the starlark evaluator or the wazero runtime.
|
Package presethook pkg/router/policy/presethook/presethook.go c2-net-routing adapts a built-in routing-policy preset (pkg/router/policy/preset) to the router.DialHook / RouteSelectingHook / RotationHook interfaces WITHOUT the starlark evaluator or the wazero runtime. |
|
presets
Package presets pkg/router/policy/presets/presets.go c2-net-routing embedded in the skywire binary, selectable by name without writing or compiling a policy file.
|
Package presets pkg/router/policy/presets/presets.go c2-net-routing embedded in the skywire binary, selectable by name without writing or compiling a policy file. |
|
wasm
Package wasm pkg/router/policy/wasm/abi.go c2-net-routing for the WASM-host ABI.
|
Package wasm pkg/router/policy/wasm/abi.go c2-net-routing for the WASM-host ABI. |
|
wasm/presets
Package presets pkg/router/policy/wasm/presets/presets.go c2-net-routing is the WASM analog of pkg/router/policy/presets: a registry of precompiled TinyGo routing-policy modules embedded in the skywire binary and selectable by name via a config value of the form "preset:<name>" (see pkg/visor/policy_loader.go), exactly like the Starlark presets.
|
Package presets pkg/router/policy/wasm/presets/presets.go c2-net-routing is the WASM analog of pkg/router/policy/presets: a registry of precompiled TinyGo routing-policy modules embedded in the skywire binary and selectable by name via a config value of the form "preset:<name>" (see pkg/visor/policy_loader.go), exactly like the Starlark presets. |
|
Package routersettings pkg/router/routersettings/catalog.go c2-net-routing
|
Package routersettings pkg/router/routersettings/catalog.go c2-net-routing |
|
Package setupmetrics pkg/router/setupmetrics/empty.go c2-net-routing
|
Package setupmetrics pkg/router/setupmetrics/empty.go c2-net-routing |