mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
FSP wire format revision and session-layer MMP implementation
FSP wire format revision (TASK-2026-0007): Introduce the FIPS Session Protocol (FSP) wire format with a 4-byte common prefix [ver_phase:1][flags:1][payload_len:2 LE] replacing the old 1-byte msg_type dispatch. All session messages share this prefix with phase-based dispatch (Established, Setup, Ack, Unencrypted). - New session_wire.rs: FSP constants, header types, parse/build helpers - SessionMessageType enum: DataPacket (0x10), SenderReport (0x11), ReceiverReport (0x12), PathMtuNotification (0x13) - FspFlags (CP/K/U) and FspInnerFlags (SP) for flag management - SessionSenderReport, SessionReceiverReport, PathMtuNotification message structs with encode/decode - FSP send pipeline: 12-byte header as AAD, 6-byte inner header (timestamp + msg_type + inner_flags), encrypt_with_aad() - FSP receive pipeline: parse header, extract cleartext coords (CP), AEAD decrypt with AAD, strip inner header, msg_type dispatch - Forwarding: transit nodes parse cleartext coords without decryption - Removed DataPacket struct and associated types - SessionEntry: session_start_ms, mark_established(), session_timestamp() - FIPS_OVERHEAD: 144 → 150 bytes (+6 for FSP inner header) - Design docs updated for new wire format Session-layer MMP implementation (TASK-2026-0008): Implement complete session-layer MMP reusing the link-layer algorithm modules (SenderState, ReceiverState, MmpMetrics, SpinBitState) with independent configuration and higher report interval clamps. - SessionMmpConfig: separate config section (node.session_mmp.*) - MmpSessionState: session-specific wrapper with PathMtuState tracking - Session-layer constants (500ms-10s report intervals, 1s cold start) - Parameterized interval methods (new_with_cold_start, update_report_interval_with_bounds) on SenderState/ReceiverState - Bidirectional From conversions between link/session report types - SessionEntry: mmp and is_initiator fields, initialized on Established - send_session_msg() for reports/notifications - Per-message RX recording with spin bit state tracking - Handlers for SenderReport, ReceiverReport, PathMtuNotification - path_mtu threaded from SessionDatagram envelope through to handlers - check_session_mmp_reports() tick handler with collect-then-send pattern - Periodic and teardown operator logging for session metrics - PathMtuState: destination observes incoming MTU on all session messages, source seeded from outbound transport MTU, decrease-immediate / increase-requires-3-consecutive rules Link-layer MMP fix: - Stop feeding spin bit RTT samples into SRTT estimator; inter-frame timing in the mesh is irregular, inflating spin-bit RTT by variable processing delays; timestamp-echo provides accurate RTT 29 files changed, 602 tests pass, 0 clippy warnings.
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
//! multi-hop forwarding through live node topologies.
|
||||
|
||||
use super::*;
|
||||
use crate::protocol::{DataPacket, SessionAck, SessionDatagram, SessionSetup};
|
||||
use crate::node::session_wire::{build_fsp_header, FSP_FLAG_CP};
|
||||
use crate::protocol::{SessionAck, SessionDatagram, SessionSetup, encode_coords};
|
||||
use crate::tree::TreeCoordinate;
|
||||
use spanning_tree::{
|
||||
cleanup_nodes, process_available_packets, run_tree_test, verify_tree_convergence,
|
||||
@@ -184,7 +185,7 @@ async fn test_coord_cache_warming_session_ack() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_coord_cache_warming_data_packet_with_coords() {
|
||||
async fn test_coord_cache_warming_encrypted_msg_with_coords() {
|
||||
let mut node = make_node();
|
||||
let from = make_node_addr(0xAA);
|
||||
let src_addr = make_node_addr(0x01);
|
||||
@@ -194,9 +195,13 @@ async fn test_coord_cache_warming_data_packet_with_coords() {
|
||||
let src_coords = TreeCoordinate::from_addrs(vec![src_addr, root_addr]).unwrap();
|
||||
let dest_coords = TreeCoordinate::from_addrs(vec![dest_addr, root_addr]).unwrap();
|
||||
|
||||
let data = DataPacket::new(0, vec![1, 2, 3, 4])
|
||||
.with_coords(src_coords.clone(), dest_coords.clone());
|
||||
let data_payload = data.encode();
|
||||
// Build FSP encrypted message with CP flag: header(12) + coords + fake_ciphertext
|
||||
let header = build_fsp_header(0, FSP_FLAG_CP, 20);
|
||||
let mut data_payload = Vec::new();
|
||||
data_payload.extend_from_slice(&header);
|
||||
encode_coords(&src_coords, &mut data_payload);
|
||||
encode_coords(&dest_coords, &mut data_payload);
|
||||
data_payload.extend_from_slice(&[0xCC; 36]); // fake ciphertext (20 payload + 16 tag)
|
||||
|
||||
let dg = SessionDatagram::new(src_addr, dest_addr, data_payload);
|
||||
let encoded = dg.encode();
|
||||
@@ -213,24 +218,26 @@ async fn test_coord_cache_warming_data_packet_with_coords() {
|
||||
|
||||
assert!(
|
||||
node.coord_cache().get(&src_addr, now_ms).is_some(),
|
||||
"src coords not cached from DataPacket"
|
||||
"src coords not cached from encrypted message"
|
||||
);
|
||||
assert!(
|
||||
node.coord_cache().get(&dest_addr, now_ms).is_some(),
|
||||
"dest coords not cached from DataPacket"
|
||||
"dest coords not cached from encrypted message"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_coord_cache_warming_opaque_data_packet() {
|
||||
async fn test_coord_cache_warming_encrypted_msg_no_coords() {
|
||||
let mut node = make_node();
|
||||
let from = make_node_addr(0xAA);
|
||||
let src_addr = make_node_addr(0x01);
|
||||
let dest_addr = make_node_addr(0x02);
|
||||
|
||||
// DataPacket without COORDS_PRESENT — no coords to cache
|
||||
let data = DataPacket::new(0, vec![1, 2, 3, 4]);
|
||||
let data_payload = data.encode();
|
||||
// Build FSP encrypted message without CP flag: header(12) + fake_ciphertext
|
||||
let header = build_fsp_header(0, 0, 20);
|
||||
let mut data_payload = Vec::new();
|
||||
data_payload.extend_from_slice(&header);
|
||||
data_payload.extend_from_slice(&[0xCC; 36]); // fake ciphertext (20 payload + 16 tag)
|
||||
|
||||
let dg = SessionDatagram::new(src_addr, dest_addr, data_payload);
|
||||
let encoded = dg.encode();
|
||||
@@ -244,11 +251,11 @@ async fn test_coord_cache_warming_opaque_data_packet() {
|
||||
|
||||
assert!(
|
||||
node.coord_cache().get(&src_addr, now_ms).is_none(),
|
||||
"Should not cache coords from opaque DataPacket"
|
||||
"Should not cache coords from message without CP flag"
|
||||
);
|
||||
assert!(
|
||||
node.coord_cache().get(&dest_addr, now_ms).is_none(),
|
||||
"Should not cache coords from opaque DataPacket"
|
||||
"Should not cache coords from message without CP flag"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ fn test_session_entry_new_initiating() {
|
||||
identity_b.pubkey_full(),
|
||||
EndToEndState::Initiating(handshake),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
assert!(entry.state().is_initiating());
|
||||
@@ -86,6 +87,7 @@ fn test_session_entry_touch() {
|
||||
identity_b.pubkey_full(),
|
||||
EndToEndState::Initiating(handshake),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
entry.touch(2000);
|
||||
@@ -111,6 +113,7 @@ fn test_session_table_operations() {
|
||||
identity_b.pubkey_full(),
|
||||
EndToEndState::Initiating(handshake),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
node.sessions.insert(dest_addr, entry);
|
||||
@@ -223,10 +226,10 @@ async fn test_session_direct_peer_data_transfer() {
|
||||
.await
|
||||
.expect("send_session_data failed");
|
||||
|
||||
// Process packets: DataPacket arrives at Node 1
|
||||
// Process packets: encrypted data arrives at Node 1
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
let count = process_available_packets(&mut nodes).await;
|
||||
assert!(count > 0, "Expected DataPacket to arrive");
|
||||
assert!(count > 0, "Expected encrypted data to arrive");
|
||||
|
||||
// Node 1's session should now be Established (was Responding, transitions on first data)
|
||||
assert!(nodes[1]
|
||||
@@ -958,7 +961,7 @@ async fn test_tun_outbound_established_session() {
|
||||
|
||||
nodes[0].node.handle_tun_outbound(ipv6_packet.clone()).await;
|
||||
|
||||
// Process packets: encrypted DataPacket → Node 1
|
||||
// Process packets: encrypted data → Node 1
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
process_available_packets(&mut nodes).await;
|
||||
|
||||
@@ -1176,6 +1179,7 @@ fn test_purge_idle_sessions_removes_expired() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Established(session),
|
||||
1000, // created at t=1000ms
|
||||
true,
|
||||
);
|
||||
|
||||
node.sessions.insert(remote_addr, entry);
|
||||
@@ -1201,6 +1205,7 @@ fn test_purge_idle_sessions_keeps_active() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Established(session),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
// Touch at t=80s — recent activity
|
||||
@@ -1232,6 +1237,7 @@ fn test_purge_idle_sessions_ignores_initiating() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Initiating(handshake),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
node.sessions.insert(remote_addr, entry);
|
||||
@@ -1255,6 +1261,7 @@ fn test_purge_idle_sessions_cleans_pending_packets() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Established(session),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
node.sessions.insert(remote_addr, entry);
|
||||
@@ -1288,6 +1295,7 @@ fn test_purge_idle_sessions_disabled_when_zero() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Established(session),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
node.sessions.insert(remote_addr, entry);
|
||||
@@ -1320,6 +1328,7 @@ fn test_coords_warmup_counter_default_zero_on_new() {
|
||||
identity_b.pubkey_full(),
|
||||
EndToEndState::Initiating(handshake),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(entry.coords_warmup_remaining(), 0,
|
||||
@@ -1338,6 +1347,7 @@ fn test_coords_warmup_counter_set_and_get() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Established(session),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
assert_eq!(entry.coords_warmup_remaining(), 0);
|
||||
@@ -1361,6 +1371,7 @@ fn test_coords_warmup_counter_decrement() {
|
||||
remote.pubkey_full(),
|
||||
EndToEndState::Established(session),
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
entry.set_coords_warmup_remaining(3);
|
||||
|
||||
Reference in New Issue
Block a user