feat(cli): v3.3 circuit rotation — background rebuild every N seconds
Adds RotatingCircuit: the multi-hop circuit is silently torn down and
rebuilt on a configurable interval (default off) so a long-running
client periodically rotates its on-wire path. Application packets never
see the swap.
- RotatingCircuit::new(hops, udp_opts, interval) seeds an initial
CircuitConnection synchronously (errors surface), then spawns a
background rotator that every `interval`:
1. dial_circuit(&hops, udp_opts) -> next: CircuitConnection
2. std::mem::replace inside Arc<RwLock<Arc<CircuitConnection>>>
3. old Arc dropped when its last in-flight Arc clone is released
(its Drop aborts forwarders / closes outers).
send_packet/recv_packet grab a cheap snapshot of the current Arc
before awaiting, so reads/writes never block under the rotator.
- [client.circuit] rotation_interval_secs: u64 (default 0 = disabled);
serde(default) keeps old configs working. When 0, the path is exactly
the v3.2 dial_circuit + optional CellPaddingConn wrap (back-compat).
- CellPaddingConn wraps RotatingCircuit on the OUTSIDE so every new
circuit shares the same cell_size — on-wire size signature stays
stable across rotations.
- Integration test multihop_rotation::rotating_circuit_swaps_inner_
under_traffic: 6 s of 100-ms ping/echo at interval=1.5s -> 37 sent,
37 received, 2 rotations counted via test-only AtomicU64 counter.
- Synchronous-failure test confirms initial dial errors bubble up from
::new without spawning the rotator task.
Workspace: 297 tests passed (+4), clippy -D warnings clean, fmt clean.
293 baseline tests unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -386,6 +386,16 @@ pub struct CircuitSection {
|
||||
/// `[server.relay] cell_size`.
|
||||
#[serde(default = "default_cell_size")]
|
||||
pub cell_size: usize,
|
||||
/// v3.3: background rotation interval in seconds. When greater than zero, the client wraps
|
||||
/// the dialed circuit in a [`crate::circuit::RotatingCircuit`] that silently rebuilds the
|
||||
/// N-hop chain every `rotation_interval_secs` seconds — new outer handshakes, fresh AEAD
|
||||
/// keys, and (with v3.2 per-hop client certs) rotated CNs.
|
||||
///
|
||||
/// `0` (the default) keeps the v3.2 behaviour: the circuit is dialed once and reused for the
|
||||
/// lifetime of the session. Recommended values: 300–900 seconds (5–15 min). Very low values
|
||||
/// (< 60 s) hammer the entry-relay's accept path and risk wedging the circuit on flaky links.
|
||||
#[serde(default)]
|
||||
pub rotation_interval_secs: u64,
|
||||
}
|
||||
|
||||
/// One entry in `[[client.circuit.hops]]`. Accepts either a flat `"IP:port"` string (v3.1 back
|
||||
|
||||
Reference in New Issue
Block a user