mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
FLP wire format revision and MMP link-layer measurement protocol
## FLP Wire Format Revision Replace the 1-byte discriminator with a structured wire format: - 4-byte common prefix (ver+phase, flags, payload_len) and 16-byte established frame header with AEAD AAD binding - 5-byte encrypted inner header (4-byte session-relative timestamp + 1-byte message type) on all link messages - Phase-based packet dispatch replacing discriminator-based dispatch - SessionDatagram reassigned from type 0x40 to 0x00; add SenderReport (0x01) and ReceiverReport (0x02) message types for MMP - SessionDatagram: rename hop_limit to ttl, add path_mtu field (u16 LE) with min(datagram.path_mtu, transport.mtu()) at forwarding - Updated handshake packets (msg1: 87->90 bytes, msg2: 42->45 bytes) - FIPS_OVERHEAD updated from 135 to 144 bytes ## MMP Link-Layer Measurement Protocol Add the Metrics Measurement Protocol for link quality measurement between FIPS peers. Measures RTT, loss, jitter, throughput, OWD trend, and ETX from periodic sender/receiver reports exchanged over established links. Module layout: - mmp/algorithms.rs: JitterEstimator, SrttEstimator, DualEwma, OwdTrend, SpinBit, ETX computation - mmp/report.rs: SenderReport (48B) and ReceiverReport (68B) wire format - mmp/sender.rs: per-peer TX counters and interval tracking - mmp/receiver.rs: per-peer RX counters, jitter, loss, gap tracking - mmp/metrics.rs: derived metrics from report processing (SRTT, goodput_bps) - mmp/mod.rs: MmpMode (Full/Lightweight/Minimal), MmpConfig, MmpPeerState - node/handlers/mmp.rs: report dispatch, timer-driven generation, operator logging (periodic + teardown) Integration: per-frame TX/RX hooks in encrypted message handling, report dispatch from link message router, timer-driven generation from tick handler, and periodic operator logging with throughput formatting. Three operating modes: Full (sender + receiver reports, spin bit, CE echo), Lightweight (receiver reports only), Minimal (spin bit + CE echo only). ## Design Documentation Updated FLP sections across all design documents to match the implemented wire format, including revised overhead calculations and numeric values. 568 tests pass, clippy clean.
This commit is contained in:
+28
-4
@@ -30,7 +30,7 @@ use crate::transport::udp::UdpTransport;
|
||||
use crate::tree::TreeState;
|
||||
use crate::upper::icmp_rate_limit::IcmpRateLimiter;
|
||||
use crate::upper::tun::{TunError, TunOutboundRx, TunState, TunTx};
|
||||
use self::wire::build_encrypted;
|
||||
use self::wire::{build_encrypted, build_established_header, prepend_inner_header, FLAG_SP};
|
||||
use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt;
|
||||
@@ -1026,6 +1026,10 @@ impl Node {
|
||||
/// The plaintext should include the message type byte followed by the
|
||||
/// message-specific payload (e.g., `[0x50, reason]` for Disconnect).
|
||||
///
|
||||
/// The send path prepends a 4-byte session-relative timestamp (inner
|
||||
/// header) before encryption. The full 16-byte outer header is used
|
||||
/// as AAD for the AEAD construction.
|
||||
///
|
||||
/// This is the standard path for sending any link-layer control message
|
||||
/// to a peer over their encrypted Noise session.
|
||||
pub(super) async fn send_encrypted_link_message(
|
||||
@@ -1049,19 +1053,35 @@ impl Node {
|
||||
reason: "no current_addr".into(),
|
||||
})?;
|
||||
|
||||
// Prepend 4-byte session-relative timestamp (inner header)
|
||||
let timestamp_ms = peer.session_elapsed_ms();
|
||||
|
||||
// MMP: read spin bit value before entering session borrow
|
||||
let sp_flag = peer.mmp()
|
||||
.map(|mmp| mmp.spin_bit.tx_bit())
|
||||
.unwrap_or(false);
|
||||
let flags = if sp_flag { FLAG_SP } else { 0 };
|
||||
|
||||
let session = peer.noise_session_mut().ok_or_else(|| NodeError::SendFailed {
|
||||
node_addr: *node_addr,
|
||||
reason: "no noise session".into(),
|
||||
})?;
|
||||
|
||||
// Get counter before encrypt (encrypt increments it)
|
||||
// Inner plaintext: [timestamp:4 LE][msg_type][payload...]
|
||||
let inner_plaintext = prepend_inner_header(timestamp_ms, plaintext);
|
||||
|
||||
// Build 16-byte outer header (used as AAD for AEAD)
|
||||
let counter = session.current_send_counter();
|
||||
let ciphertext = session.encrypt(plaintext).map_err(|e| NodeError::SendFailed {
|
||||
let payload_len = inner_plaintext.len() as u16;
|
||||
let header = build_established_header(their_index, counter, flags, payload_len);
|
||||
|
||||
// Encrypt with AAD binding to the outer header
|
||||
let ciphertext = session.encrypt_with_aad(&inner_plaintext, &header).map_err(|e| NodeError::SendFailed {
|
||||
node_addr: *node_addr,
|
||||
reason: format!("encryption failed: {}", e),
|
||||
})?;
|
||||
|
||||
let wire_packet = build_encrypted(their_index, counter, &ciphertext);
|
||||
let wire_packet = build_encrypted(&header, &ciphertext);
|
||||
|
||||
// Re-borrow peer for stats update after sending
|
||||
let transport = self.transports.get(&transport_id)
|
||||
@@ -1076,6 +1096,10 @@ impl Node {
|
||||
// Update send statistics
|
||||
if let Some(peer) = self.peers.get_mut(node_addr) {
|
||||
peer.link_stats_mut().record_sent(bytes_sent);
|
||||
// MMP: record sent frame for sender report generation
|
||||
if let Some(mmp) = peer.mmp_mut() {
|
||||
mmp.sender.record_sent(counter, timestamp_ms, bytes_sent);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user