Commit Graph
10 Commits
Author SHA1 Message Date
Johnathan Corgan 13c0b70dc3 Add rustfmt formatting policy and reformat codebase
Add rustfmt.toml with stable defaults and apply cargo fmt to all
source files. This establishes a consistent formatting baseline
for CI enforcement.
2026-04-10 08:27:07 +00:00
Johnathan Corgan 58664c7c77 Update dependencies: rand 0.10, rtnetlink 0.20, tun 0.8, and others
Bump rand (0.8→0.10), rtnetlink (0.14→0.20), tun (0.7→0.8),
simple-dns (0.9→0.11), socket2 (0.5→0.6), and criterion (0.5→0.8).

Migrate all rand call sites: thread_rng()→rng(), gen()→random(),
gen_range()→random_range(), RngCore→Rng trait. Work around secp256k1
0.30 requiring rand 0.8 by generating random bytes directly and
constructing SecretKey from slice.

Migrate rtnetlink to builder-based API: LinkSetRequest replaced with
LinkUnspec builder + change(), RouteAddRequest replaced with
RouteMessageBuilder.

Remove bloom benchmark (criterion 0.8 incompatible with old harness
config).
2026-02-24 18:02:06 +00:00
Johnathan Corgan f920526ece Add epoch-based peer restart detection to Noise IK handshake
Each node generates a random 8-byte startup epoch, encrypted inside
both Noise IK handshake messages (msg1 and msg2). When a peer's msg1
arrives with a different epoch than the stored value, the node tears
down the stale session and processes the msg1 as a new connection,
enabling near-instant restart detection instead of the 30-second
dead timeout.

Wire format impact:
- msg1: 82 -> 106 bytes (added 24-byte encrypted epoch after ss DH)
- msg2: 33 -> 57 bytes (added 24-byte encrypted epoch after se DH)
- Wire msg1: 90 -> 114 bytes, wire msg2: 45 -> 69 bytes
2026-02-22 20:50:50 +00:00
Johnathan Corgan 6a10e9228b Link-layer handshake message retry with exponential backoff
Add message-level retry for Noise IK handshake within the 30s timeout
window. Previously, a lost msg1 or msg2 required the full timeout to
expire before cleanup and retry. Under 10% bidirectional loss (~19%
per attempt), this made connection establishment unreliable.

Initiator resends stored msg1 bytes with exponential backoff (1s, 2s,
4s, 8s, 16s — 5 resends). Responder stores msg2 and resends on
duplicate msg1 receipt. Duplicate msg2 at initiator drops silently
via existing pending_outbound cleanup.

Config: handshake_resend_interval_ms (1000), handshake_resend_backoff
(2.0), handshake_max_resends (5) on node.rate_limit.

P(all 6 attempts fail) under 19% loss = 0.19^6 ≈ 0.005%.
2026-02-19 16:11:06 +00:00
Johnathan Corgan 930f139787 Split cache.rs and config.rs into directory modules, create utils/
Module reorganization for three large single-file modules:

cache.rs (792 lines) split into cache/ directory:
- cache/mod.rs: CacheError, CacheStats, re-exports
- cache/entry.rs: CacheEntry with TTL/LRU tracking
- cache/coord_cache.rs: CoordCache (address-to-coordinate mappings)
- cache/route_cache.rs: RouteCache + CachedCoords (discovery routes)
- Added 22 new tests filling coverage gaps across all submodules

config.rs (1318 lines) split into config/ directory:
- config/mod.rs: ConfigError, IdentityConfig, Config struct with file
  loading/merge logic, all 24 integration tests
- config/node.rs: NodeConfig + 9 subsection structs (Limits, RateLimit,
  Retry, Cache, Discovery, Tree, Bloom, Session, Buffers)
- config/transport.rs: TransportInstances<T>, TransportsConfig, UdpConfig
- config/peer.rs: ConnectPolicy, PeerAddress, PeerConfig

DnsConfig and TunConfig moved to upper/config.rs to co-locate with the
upper layer components they configure (TUN interface, DNS responder).

index.rs moved to utils/index.rs as cross-cutting infrastructure that
serves both node and peer layers.
2026-02-15 17:56:32 +00:00
Johnathan Corgan b8a1f322c2 Module reorganization and clippy cleanup
Move single-consumer modules into node/:
- rate_limit.rs, wire.rs, dns.rs — exclusively used by node subsystem
- Reduces top-level lib.rs from 16 to 13 modules

Split large files into focused subdirectories:
- noise.rs (1475 lines) → noise/{mod, handshake, session, replay, tests}.rs
- tree.rs (1479 lines) → tree/{mod, coordinate, declaration, state, tests}.rs
- bloom.rs (849 lines) → bloom/{mod, filter, state, tests}.rs
- All public APIs re-exported from mod.rs, no external import changes

Remove unused rate_limit defaults:
- HANDSHAKE_TIMEOUT_SECS, MAX_PENDING_INBOUND constants
- Default constructor eliminated in favor of with_params() taking config values

Fix all clippy warnings across codebase:
- Remove .clone() on Copy types, collapse nested ifs, replace match-return-None
  with ?, remove/gate unused code, fix loop indexing, remove unnecessary casts
- Box large PeerSlot enum variants to reduce size disparity
- cargo clippy --all-targets now reports zero warnings
2026-02-15 15:07:42 +00:00
Johnathan Corgan 9d36977ec8 Fix pending cross-connection cleanup, add auto-retry with exponential backoff
Fix promote_connection() to detect and clean up pending outbound
handshakes to the same peer, not just already-promoted peers. Previously,
when an inbound handshake completed while an outbound was still pending,
the outbound would linger until the 30s timeout.

Add auto-retry for failed outbound connections to auto-connect peers:
- New RetryState struct and node/retry.rs module
- Exponential backoff (default 5s base, max 5 attempts)
- Config: node.max_retries, node.base_retry_interval_secs
- check_timeouts() schedules retries, rx loop processes them
- promote_connection() clears retry state on success
- Remove unused PeerConnection retry fields (state now at Node level)

Move cleanup_stale_connection() logging to callers for context-appropriate
messages.

289 tests pass, clean build.
2026-02-10 22:25:31 +00:00
Johnathan Corgan 7c8a5bd5ae Implement RX event loop with wire format dispatch
Wire format module (src/wire.rs):
- Discriminator-based packet framing (0x00/0x01/0x02)
- Header parsing: EncryptedHeader, Msg1Header, Msg2Header
- Serialization: build_msg1(), build_msg2(), build_encrypted()
- 11 unit tests for parsing and roundtrip

Session index tracking:
- PeerConnection: our_index, their_index, transport_id, source_addr
- ActivePeer: noise_session, indices, transport_id, current_addr
- Removed Clone from ActivePeer (NoiseSession nonce reuse risk)
- PromotionResult refactored to use NodeId instead of ActivePeer

Node RX event loop:
- run_rx_loop() with packet_rx channel consumption
- process_packet() discriminator dispatch
- handle_encrypted_frame() with O(1) index lookup
- handle_msg1() with rate limiting and inbound handshake
- handle_msg2() completing outbound handshakes
- dispatch_link_message() stub for link protocol

Infrastructure (from Session 56):
- IndexAllocator for random 32-bit session indices
- HandshakeRateLimiter with token bucket
- ReplayWindow for 2048-packet sliding window
- Bloom filter defaults updated to v1 spec

All 265 tests pass.
2026-02-02 17:20:06 +00:00
Johnathan Corgan 9a6fa07e77 Session 46: Noise IK peer authentication implementation
Implement Noise Protocol Framework for peer authentication using the IK
pattern, which allows responders to learn initiator identity from the
encrypted handshake message.

Protocol: Noise_IK_secp256k1_ChaChaPoly_SHA256
- Message 1: 82 bytes (ephemeral + encrypted static)
- Message 2: 33 bytes (ephemeral only)

New noise.rs module (~600 lines):
- HandshakeState: Manages handshake for both initiator/responder roles
- NoiseSession: Post-handshake symmetric encryption
- CipherState: ChaCha20-Poly1305 with nonce counter
- SymmetricState: HKDF-SHA256 key derivation

PeerConnection integration:
- Simplified HandshakeState enum (Initial/SentMsg1/ReceivedMsg1/Complete/Failed)
- start_handshake(): Initiator generates msg1
- receive_handshake_init(): Responder processes msg1, discovers identity
- complete_handshake(): Initiator completes with msg2
- take_session(): Extract NoiseSession for ActivePeer

Identity helpers:
- Identity::keypair() for Noise operations
- PeerIdentity::from_pubkey_full() preserves parity for ECDH
- PeerIdentity::pubkey_full() returns full key or derives with even parity

Node changes:
- initiate_peer_connection() now starts handshake and sends msg1
- Method is async to use transport's async send

All 219 tests pass.
2026-02-01 01:28:01 +00:00
Johnathan Corgan 86c49dfb04 Implement PeerSlot architecture with two-phase peer lifecycle
Refactor peer management into two distinct phases:

PeerConnection (handshake phase):
- Indexed by LinkId (identity unknown for inbound connections)
- HandshakeState: AwaitingHello → SentHello → SentAuth → AwaitingAuthAck → Complete
- Tracks expected identity, retry count, timing

ActivePeer (authenticated phase):
- Indexed by NodeId (verified identity)
- ConnectivityState: Connected → Stale → Reconnecting → Disconnected
- Holds tree state, bloom filter, routing data

Cross-connection handling:
- Deterministic tie-breaker: smaller node_id's OUTBOUND wins
- PromotionResult enum for promotion outcomes
- Both nodes reach same conclusion independently

Node refactoring:
- Split storage: connections (by LinkId) + peers (by NodeId)
- Add addr_to_link reverse lookup for packet dispatch
- promote_connection() handles promotion with cross-connection detection
2026-02-01 00:50:47 +00:00