Move path MTU state and mesh framing constants out of the upper layer

PathMtuEntry and PathMtuLookup describe per-destination path MTU state
that only the node writes, releases and expires; the TUN threads merely
read it. FIPS_OVERHEAD is the combined FMP and FSP framing cost and is
also used by the native datagram API. MIN_REACTIVE_PATH_MTU is path MTU
policy for the MtuExceeded carrier and belongs beside
MIN_ACTIONABLE_PATH_MTU.

Define the path MTU types in node/path_mtu.rs, FIPS_OVERHEAD in a new
proto/framing.rs, and MIN_REACTIVE_PATH_MTU in proto/mmp/limits.rs.
The upper tun and icmp modules re-export all of them, so every existing
crate and public path keeps resolving and no consumer changes. Values,
types and documentation are unchanged, and none of the moved items has
a log site.
This commit is contained in:
Johnathan Corgan
2026-09-24 13:28:54 +00:00
parent 0f0e1dc2bd
commit a481520191
8 changed files with 123 additions and 88 deletions
+1
View File
@@ -19,6 +19,7 @@ mod lifecycle;
pub(crate) mod metrics;
pub(crate) mod netmon;
pub use netmon::NetmonTrigger;
pub(crate) mod path_mtu;
mod peer_error_budget;
mod peering;
mod rate_limit;
+53
View File
@@ -0,0 +1,53 @@
//! Per-destination path MTU state shared with the TCP MSS clamp.
//!
//! Every writer, the release paths and the expiry pass live in the node;
//! the TUN reader and writer threads only read the map.
use crate::FipsAddress;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
/// One `path_mtu_lookup` entry: the MTU the TCP MSS clamp reads, plus how
/// the entry is released.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PathMtuEntry {
/// Path MTU in bytes.
pub mtu: u16,
/// Unix ms at which a discovery `LookupResponse` supplied this value, or
/// `None` for an entry that some event releases instead of a timer.
///
/// The discovery carrier is the one with no release path: it writes an
/// entry for a destination this node may never open a session with, and
/// all three callers of `path_mtu_lookup_release` fire on session state.
/// A link MTU this node derived from its own transport, and a value
/// learned inside a session, are both released by an event that says the
/// thing they describe is gone, so they carry no deadline.
pub learned_ms: Option<u64>,
}
impl PathMtuEntry {
/// An entry released by an event rather than a timer: a locally derived
/// link MTU, or a value learned inside a session.
pub fn held(mtu: u16) -> Self {
Self {
mtu,
learned_ms: None,
}
}
/// A remote party's claim stored at `at_ms` for a destination with no
/// other release path. Expires.
pub fn learned(mtu: u16, at_ms: u64) -> Self {
Self {
mtu,
learned_ms: Some(at_ms),
}
}
}
/// Read-only handle to the per-destination path MTU map. Populated by
/// the discovery handler on `LookupResponse`; read by the TUN reader
/// (outbound clamp) and writer (inbound clamp) at TCP MSS clamp time.
/// Keyed by [`FipsAddress`] (16 bytes, the IPv6 form of a fips peer
/// address).
pub type PathMtuLookup = Arc<RwLock<HashMap<FipsAddress, PathMtuEntry>>>;
+37
View File
@@ -0,0 +1,37 @@
//! Encapsulation overhead spanning the FMP and FSP layers.
//!
//! The framing cost of a session datagram is the sum of FMP link framing and
//! FSP session framing, so it belongs to neither layer's wire module alone.
/// FIPS base encapsulation overhead for DataPacket (excluding port payload).
///
/// This is the fixed overhead for a SessionDatagram carrying an FSP DataPacket,
/// used by the send path's CP-flag guard to check whether piggybacked coords
/// fit within the transport MTU. For IPv6 effective MTU calculations, use
/// [`FIPS_IPV6_OVERHEAD`] which accounts for port multiplexing and header
/// compression.
///
/// Breakdown (traced through the actual send path):
///
/// ```text
/// FMP outer header (cleartext AAD) 16
/// common prefix (4) + receiver_idx (4) + counter (8)
/// FMP AEAD ciphertext:
/// timestamp (4) + msg_type (1) 5 [FMP inner header]
/// ttl (1) + path_mtu (2) + src (16) + dst (16) 35 [SessionDatagram body]
/// FSP header (4 prefix + 8 counter) 12 [cleartext AAD]
/// FSP AEAD ciphertext:
/// timestamp (4) + msg_type (1) + flags (1) 6 [FSP inner header]
/// <application data>
/// Poly1305 tag 16 [FSP AEAD]
/// FMP Poly1305 tag 16 [FMP AEAD]
/// ────
/// 106
/// ```
///
/// Note: the FMP inner header msg_type byte IS the SessionDatagram msg_type
/// byte (shared, not double-counted). The "35 bytes" is the SessionDatagram
/// body after msg_type is consumed by the dispatch layer.
///
/// [`FIPS_IPV6_OVERHEAD`]: crate::upper::icmp::FIPS_IPV6_OVERHEAD
pub const FIPS_OVERHEAD: u16 = 16 + 16 + 5 + 35 + 12 + 6 + 16; // 106 bytes
+16
View File
@@ -82,3 +82,19 @@ pub const SESSION_COLD_START_INTERVAL_MS: u64 = 1_000;
///
/// [`mss_ceiling`]: crate::upper::icmp::mss_ceiling
pub const MIN_ACTIONABLE_PATH_MTU: u16 = 256;
/// Smallest path MTU this node will act on when the claim arrives on the
/// unauthenticated reactive carrier, `MtuExceeded`.
///
/// Held equal to [`MIN_ACTIONABLE_PATH_MTU`] so no hop legitimately configured
/// with a small transport MTU loses reactive feedback. It is a separate
/// constant because the two carriers differ in what they prove: the
/// authenticated `PathMtuNotification` and the proof-carrying discovery
/// response come from a party this node has verified, whereas this one comes
/// from whoever could route a datagram here. What keeps a legal-but-forged
/// claim from pinning a session is corroboration against what this node has
/// actually sent, not this floor. Raising it (576 is the value the original
/// path-MTU floor design proposed, and derives an inner IPv6 MTU of 499)
/// bounds the outcome of an uncorroborated claim further, at the cost of
/// ignoring an honest report from any hop configured between the two values.
pub const MIN_REACTIVE_PATH_MTU: u16 = MIN_ACTIONABLE_PATH_MTU;
+2 -2
View File
@@ -85,6 +85,6 @@ impl fmt::Display for MmpMode {
pub use limits::{
COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS, DEFAULT_LOG_INTERVAL_SECS,
DEFAULT_OWD_WINDOW_SIZE, EWMA_LONG_ALPHA, EWMA_SHORT_ALPHA, MAX_REPORT_INTERVAL_MS,
MAX_SESSION_REPORT_INTERVAL_MS, MIN_ACTIONABLE_PATH_MTU, MIN_REPORT_INTERVAL_MS,
MIN_SESSION_REPORT_INTERVAL_MS, SESSION_COLD_START_INTERVAL_MS,
MAX_SESSION_REPORT_INTERVAL_MS, MIN_ACTIONABLE_PATH_MTU, MIN_REACTIVE_PATH_MTU,
MIN_REPORT_INTERVAL_MS, MIN_SESSION_REPORT_INTERVAL_MS, SESSION_COLD_START_INTERVAL_MS,
};
+1
View File
@@ -10,6 +10,7 @@ pub(crate) mod bloom;
pub(crate) mod codec;
pub(crate) mod coord;
pub(crate) mod fmp;
pub(crate) mod framing;
pub(crate) mod fsp;
pub(crate) mod link;
pub(crate) mod lookup;
+7 -40
View File
@@ -61,34 +61,9 @@ const MAX_ORIGINAL_PACKET: usize = MIN_IPV6_MTU - IPV6_HEADER_LEN - ICMPV6_HEADE
/// FIPS base encapsulation overhead for DataPacket (excluding port payload).
///
/// This is the fixed overhead for a SessionDatagram carrying an FSP DataPacket,
/// used by the send path's CP-flag guard to check whether piggybacked coords
/// fit within the transport MTU. For IPv6 effective MTU calculations, use
/// [`FIPS_IPV6_OVERHEAD`] which accounts for port multiplexing and header
/// compression.
///
/// Breakdown (traced through the actual send path):
///
/// ```text
/// FMP outer header (cleartext AAD) 16
/// common prefix (4) + receiver_idx (4) + counter (8)
/// FMP AEAD ciphertext:
/// timestamp (4) + msg_type (1) 5 [FMP inner header]
/// ttl (1) + path_mtu (2) + src (16) + dst (16) 35 [SessionDatagram body]
/// FSP header (4 prefix + 8 counter) 12 [cleartext AAD]
/// FSP AEAD ciphertext:
/// timestamp (4) + msg_type (1) + flags (1) 6 [FSP inner header]
/// <application data>
/// Poly1305 tag 16 [FSP AEAD]
/// FMP Poly1305 tag 16 [FMP AEAD]
/// ────
/// 106
/// ```
///
/// Note: the FMP inner header msg_type byte IS the SessionDatagram msg_type
/// byte (shared, not double-counted). The "35 bytes" is the SessionDatagram
/// body after msg_type is consumed by the dispatch layer.
pub const FIPS_OVERHEAD: u16 = 16 + 16 + 5 + 35 + 12 + 6 + 16; // 106 bytes
/// Re-exported: the value is FMP plus FSP framing and is defined with the
/// protocol layers, in [`crate::proto::framing::FIPS_OVERHEAD`].
pub use crate::proto::framing::FIPS_OVERHEAD;
/// FIPS encapsulation overhead for compressed IPv6 shim traffic (port 256).
///
@@ -115,18 +90,10 @@ pub use crate::proto::mmp::MIN_ACTIONABLE_PATH_MTU;
/// Smallest path MTU this node will act on when the claim arrives on the
/// unauthenticated reactive carrier, `MtuExceeded`.
///
/// Held equal to [`MIN_ACTIONABLE_PATH_MTU`] so no hop legitimately configured
/// with a small transport MTU loses reactive feedback. It is a separate
/// constant because the two carriers differ in what they prove: the
/// authenticated `PathMtuNotification` and the proof-carrying discovery
/// response come from a party this node has verified, whereas this one comes
/// from whoever could route a datagram here. What keeps a legal-but-forged
/// claim from pinning a session is corroboration against what this node has
/// actually sent, not this floor. Raising it (576 is the value the original
/// path-MTU floor design proposed, and derives an inner IPv6 MTU of 499)
/// bounds the outcome of an uncorroborated claim further, at the cost of
/// ignoring an honest report from any hop configured between the two values.
pub const MIN_REACTIVE_PATH_MTU: u16 = MIN_ACTIONABLE_PATH_MTU;
/// Re-exported: a protocol policy decision defined beside
/// [`crate::proto::mmp::MIN_ACTIONABLE_PATH_MTU`], in
/// [`crate::proto::mmp::MIN_REACTIVE_PATH_MTU`].
pub use crate::proto::mmp::MIN_REACTIVE_PATH_MTU;
/// Calculate the effective IPv6 MTU for FIPS-encapsulated traffic.
///
+6 -46
View File
@@ -14,7 +14,6 @@
use crate::FipsAddress;
#[cfg(unix)]
use crate::{FipsAddress, TunConfig};
use std::collections::HashMap;
#[cfg(unix)]
use std::fs::File;
#[cfg(unix)]
@@ -25,7 +24,7 @@ use std::io::Write;
use std::net::Ipv6Addr;
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::sync::{Arc, RwLock, mpsc};
use std::sync::{Arc, mpsc};
use thiserror::Error;
#[cfg(unix)]
use tracing::error;
@@ -35,50 +34,9 @@ use tracing::{error, warn};
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
use tun::Layer;
/// One `path_mtu_lookup` entry: the MTU the TCP MSS clamp reads, plus how
/// the entry is released.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PathMtuEntry {
/// Path MTU in bytes.
pub mtu: u16,
/// Unix ms at which a discovery `LookupResponse` supplied this value, or
/// `None` for an entry that some event releases instead of a timer.
///
/// The discovery carrier is the one with no release path: it writes an
/// entry for a destination this node may never open a session with, and
/// all three callers of `path_mtu_lookup_release` fire on session state.
/// A link MTU this node derived from its own transport, and a value
/// learned inside a session, are both released by an event that says the
/// thing they describe is gone, so they carry no deadline.
pub learned_ms: Option<u64>,
}
impl PathMtuEntry {
/// An entry released by an event rather than a timer: a locally derived
/// link MTU, or a value learned inside a session.
pub fn held(mtu: u16) -> Self {
Self {
mtu,
learned_ms: None,
}
}
/// A remote party's claim stored at `at_ms` for a destination with no
/// other release path. Expires.
pub fn learned(mtu: u16, at_ms: u64) -> Self {
Self {
mtu,
learned_ms: Some(at_ms),
}
}
}
/// Read-only handle to the per-destination path MTU map. Populated by
/// the discovery handler on `LookupResponse`; read by the TUN reader
/// (outbound clamp) and writer (inbound clamp) at TCP MSS clamp time.
/// Keyed by [`FipsAddress`] (16 bytes, the IPv6 form of a fips peer
/// address).
pub type PathMtuLookup = Arc<RwLock<HashMap<FipsAddress, PathMtuEntry>>>;
// The path MTU map is node state; re-exported so `crate::upper::tun` paths
// keep resolving.
pub use crate::node::path_mtu::{PathMtuEntry, PathMtuLookup};
/// The node-global TCP MSS ceiling, shared live with the TUN reader and
/// writer threads.
@@ -1653,6 +1611,8 @@ mod platform {
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::RwLock;
#[test]
fn test_tun_state_display() {