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:
@@ -0,0 +1,78 @@
|
||||
//! v3.3 config-parsing smoke test for `[client.circuit] rotation_interval_secs`.
|
||||
//!
|
||||
//! Asserts that:
|
||||
//! 1. A `client.toml` with `rotation_interval_secs = N` parses and surfaces `N` on the
|
||||
//! [`ClientConfigFile`].
|
||||
//! 2. Omitting the key keeps the v3.2-compatible default of `0` (i.e. rotation off).
|
||||
//!
|
||||
//! Pure TOML parsing — no networking, no actors. This is the back-compat smoke test the v3.3
|
||||
//! direction memory calls for.
|
||||
|
||||
use aura_cli::config::ClientConfigFile;
|
||||
|
||||
const TOML_WITH_ROTATION: &str = r#"
|
||||
[client]
|
||||
name = "laptop"
|
||||
server_addr = "203.0.113.10:443"
|
||||
sni = "cdn.example.com"
|
||||
|
||||
[client.circuit]
|
||||
enabled = true
|
||||
hops = ["198.51.100.5:443", "203.0.113.10:443"]
|
||||
cell_padding = true
|
||||
cell_size = 1280
|
||||
rotation_interval_secs = 600
|
||||
|
||||
[pki]
|
||||
ca_cert = "~/.aura/ca.crt"
|
||||
cert = "~/.aura/client.crt"
|
||||
key = "~/.aura/client.key"
|
||||
|
||||
[tunnel]
|
||||
local_ip = "10.7.0.2"
|
||||
"#;
|
||||
|
||||
const TOML_NO_ROTATION: &str = r#"
|
||||
[client]
|
||||
name = "laptop"
|
||||
server_addr = "203.0.113.10:443"
|
||||
sni = "cdn.example.com"
|
||||
|
||||
[client.circuit]
|
||||
enabled = true
|
||||
hops = ["198.51.100.5:443", "203.0.113.10:443"]
|
||||
|
||||
[pki]
|
||||
ca_cert = "~/.aura/ca.crt"
|
||||
cert = "~/.aura/client.crt"
|
||||
key = "~/.aura/client.key"
|
||||
|
||||
[tunnel]
|
||||
local_ip = "10.7.0.2"
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn rotation_interval_secs_parses_when_set() {
|
||||
let cfg = ClientConfigFile::parse(TOML_WITH_ROTATION).expect("parse client.toml with rotation");
|
||||
let circuit = cfg.circuit();
|
||||
assert!(circuit.enabled, "circuit must be enabled");
|
||||
assert_eq!(circuit.hops.len(), 2);
|
||||
assert!(circuit.cell_padding);
|
||||
assert_eq!(circuit.cell_size, 1280);
|
||||
assert_eq!(
|
||||
circuit.rotation_interval_secs, 600,
|
||||
"rotation_interval_secs surfaces the TOML value"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotation_interval_secs_defaults_to_zero_back_compat() {
|
||||
let cfg =
|
||||
ClientConfigFile::parse(TOML_NO_ROTATION).expect("parse client.toml without rotation");
|
||||
let circuit = cfg.circuit();
|
||||
assert!(circuit.enabled);
|
||||
assert_eq!(
|
||||
circuit.rotation_interval_secs, 0,
|
||||
"default is 0 = rotation off; preserves v3.2 single-dial behaviour"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user