COORDS_PRESENT warmup-then-reactive for DataPackets

Include source and destination coordinates on the first N DataPackets
of each session (default 5, configurable via
node.session.coords_warmup_packets). This warms transit node
coord_caches so multi-hop forwarding and error signal routing work
after SessionSetup cache entries expire.

On CoordsRequired receipt, reset the counter to re-enable coordinate
inclusion for the next N packets, handling mid-session cache expiry
and path changes.

Changes:
- SessionConfig: add coords_warmup_packets field (default 5)
- SessionEntry: add coords_warmup_remaining counter, initialized on
  Established transition (both initiator and responder paths)
- send_session_data(): attach coords via DataPacket::with_coords()
  while counter > 0, decrement per send
- handle_coords_required(): reset counter for affected session
- 4 new unit tests for counter lifecycle and config default
This commit is contained in:
Johnathan Corgan
2026-02-16 23:28:24 +00:00
parent f374370e5c
commit 1f60fbd7a2
4 changed files with 135 additions and 2 deletions
+28 -2
View File
@@ -211,6 +211,7 @@ impl Node {
};
entry.set_state(EndToEndState::Established(session));
entry.set_coords_warmup_remaining(self.config.node.session.coords_warmup_packets);
entry.touch(Self::now_ms());
self.sessions.insert(*src_addr, entry);
@@ -278,6 +279,7 @@ impl Node {
}
};
entry.set_state(EndToEndState::Established(noise_session));
entry.set_coords_warmup_remaining(self.config.node.session.coords_warmup_packets);
debug!(src = %src_addr, "Session established (responder, on first data)");
}
@@ -341,6 +343,18 @@ impl Node {
);
self.maybe_initiate_lookup(&msg.dest_addr).await;
// Reset coords warmup counter so the next N packets include
// COORDS_PRESENT, re-warming transit caches along the path.
if let Some(entry) = self.sessions.get_mut(&msg.dest_addr) {
let n = self.config.node.session.coords_warmup_packets;
entry.set_coords_warmup_remaining(n);
debug!(
dest = %msg.dest_addr,
warmup_packets = n,
"Reset coords warmup counter after CoordsRequired"
);
}
}
/// Handle a PathBroken error signal from a transit router.
@@ -449,8 +463,20 @@ impl Node {
reason: format!("session encrypt failed: {}", e),
})?;
// Build DataPacket and wrap in SessionDatagram
let data_packet = DataPacket::new(ciphertext);
// Check warmup counter and decrement (while entry is still borrowed)
let include_coords = entry.coords_warmup_remaining() > 0;
if include_coords {
entry.set_coords_warmup_remaining(entry.coords_warmup_remaining() - 1);
}
// Build DataPacket, conditionally with coordinates
let mut data_packet = DataPacket::new(ciphertext);
if include_coords {
let my_coords = self.tree_state.my_coords().clone();
let dest_coords = self.get_dest_coords(dest_addr);
data_packet = data_packet.with_coords(my_coords, dest_coords);
}
let my_addr = *self.node_addr();
let datagram = SessionDatagram::new(my_addr, *dest_addr, data_packet.encode())
.with_hop_limit(self.config.node.session.default_hop_limit);
+16
View File
@@ -54,6 +54,10 @@ pub(crate) struct SessionEntry {
created_at: u64,
/// Last activity timestamp (Unix milliseconds).
last_activity: u64,
/// Remaining DataPackets that should include COORDS_PRESENT.
/// Initialized from config when session becomes Established;
/// reset on CoordsRequired receipt.
coords_warmup_remaining: u8,
}
impl SessionEntry {
@@ -70,6 +74,7 @@ impl SessionEntry {
state: Some(state),
created_at: now_ms,
last_activity: now_ms,
coords_warmup_remaining: 0,
}
}
@@ -116,4 +121,15 @@ impl SessionEntry {
pub(crate) fn last_activity(&self) -> u64 {
self.last_activity
}
/// Remaining DataPackets that should include COORDS_PRESENT.
pub(crate) fn coords_warmup_remaining(&self) -> u8 {
self.coords_warmup_remaining
}
/// Set the coords warmup counter (used on Established transition
/// and CoordsRequired reset).
pub(crate) fn set_coords_warmup_remaining(&mut self, value: u8) {
self.coords_warmup_remaining = value;
}
}
+84
View File
@@ -1299,6 +1299,90 @@ fn test_purge_idle_sessions_disabled_when_zero() {
assert_eq!(node.session_count(), 1, "Sessions should not be purged when idle timeout is disabled");
}
// ============================================================================
// Unit tests: COORDS_PRESENT warmup counter
// ============================================================================
#[test]
fn test_coords_warmup_counter_default_zero_on_new() {
use crate::noise::HandshakeState;
let identity_a = Identity::generate();
let identity_b = Identity::generate();
let handshake = HandshakeState::new_initiator(
identity_a.keypair(),
identity_b.pubkey_full(),
);
let entry = crate::node::session::SessionEntry::new(
*identity_b.node_addr(),
identity_b.pubkey_full(),
EndToEndState::Initiating(handshake),
1000,
);
assert_eq!(entry.coords_warmup_remaining(), 0,
"Counter should be 0 for non-Established sessions");
}
#[test]
fn test_coords_warmup_counter_set_and_get() {
let node = make_node();
let remote = Identity::generate();
let remote_addr = *remote.node_addr();
let session = make_noise_session(node.identity(), &remote);
let mut entry = crate::node::session::SessionEntry::new(
remote_addr,
remote.pubkey_full(),
EndToEndState::Established(session),
1000,
);
assert_eq!(entry.coords_warmup_remaining(), 0);
entry.set_coords_warmup_remaining(5);
assert_eq!(entry.coords_warmup_remaining(), 5);
entry.set_coords_warmup_remaining(0);
assert_eq!(entry.coords_warmup_remaining(), 0);
}
#[test]
fn test_coords_warmup_counter_decrement() {
let node = make_node();
let remote = Identity::generate();
let remote_addr = *remote.node_addr();
let session = make_noise_session(node.identity(), &remote);
let mut entry = crate::node::session::SessionEntry::new(
remote_addr,
remote.pubkey_full(),
EndToEndState::Established(session),
1000,
);
entry.set_coords_warmup_remaining(3);
// Simulate the decrement pattern used in send_session_data
for expected in (0..3).rev() {
assert!(entry.coords_warmup_remaining() > 0);
entry.set_coords_warmup_remaining(entry.coords_warmup_remaining() - 1);
assert_eq!(entry.coords_warmup_remaining(), expected);
}
assert_eq!(entry.coords_warmup_remaining(), 0,
"Counter should reach 0 after N decrements");
}
#[test]
fn test_coords_warmup_config_default() {
let config = crate::config::Config::new();
assert_eq!(config.node.session.coords_warmup_packets, 5,
"Default coords_warmup_packets should be 5");
}
// ============================================================================
// Unit tests: Identity cache
// ============================================================================