diff --git a/src/node/lifecycle.rs b/src/node/lifecycle.rs index e0b654e..5b2435f 100644 --- a/src/node/lifecycle.rs +++ b/src/node/lifecycle.rs @@ -1824,17 +1824,39 @@ impl Node { self.register_identity(peer_node_addr, peer_identity.pubkey_full()); let transport_id = self.allocate_transport_id(); - // Adopted ephemeral UDP transports use UdpConfig::default() when the - // bootstrap runtime doesn't pass an override. Default MTU resolves to - // 1280 (IPv6 minimum), which is the only value guaranteed to survive - // arbitrary NAT-traversal middlebox paths. Inheriting from the named - // [transports.udp] config (Option 3 in ISSUE-2026-0013) would track - // operator config more closely but risks regressions on hostile paths; - // accepted as-is until a concrete use case justifies the change. + // Adopted ephemeral UDP transports inherit MTU + socket-buffer sizing + // (and accept_connections / advertise flags) from the operator's + // configured [transports.udp] when the bootstrap runtime doesn't + // pass an explicit override. Lookup tries `transport_name` first + // (covers the `Named` multi-listener variant) and falls back to the + // unnamed `Single` listener, so single- and named-listener configs + // both inherit cleanly. + // + // Tradeoff: `UdpConfig::default()` sets MTU 1280 (IPv6 minimum), the + // only value guaranteed to survive arbitrary middlebox paths. + // Inheriting a higher operator-chosen MTU means NAT-traversed flows + // initially attempt that MTU and may black-hole on tighter paths + // until reactive `MtuExceeded` recovery kicks in. Operators who + // raise the primary MTU based on known-clean topology accept that + // tradeoff; the silent drop on a too-low default was strictly + // worse for the common case where the primary MTU is reachable. + // + // Bind / external address fields are cleared since the socket is + // already bound. + let inherited_config = traversal.transport_config.clone().unwrap_or_else(|| { + let mut cfg = self + .lookup_udp_config(traversal.transport_name.as_deref()) + .or_else(|| self.lookup_udp_config(None)) + .cloned() + .unwrap_or_default(); + cfg.bind_addr = None; + cfg.external_addr = None; + cfg + }); let mut transport = crate::transport::udp::UdpTransport::new( transport_id, traversal.transport_name.clone(), - traversal.transport_config.clone().unwrap_or_default(), + inherited_config, packet_tx, ); diff --git a/src/node/tests/bootstrap.rs b/src/node/tests/bootstrap.rs index bfb3bd5..04906fa 100644 --- a/src/node/tests/bootstrap.rs +++ b/src/node/tests/bootstrap.rs @@ -2,10 +2,11 @@ use super::*; use crate::EstablishedTraversal; -use crate::config::UdpConfig; +use crate::config::{TransportInstances, UdpConfig}; use crate::node::wire::{PHASE_MSG1, PHASE_MSG2}; use crate::transport::udp::UdpTransport; use crate::utils::index::IndexAllocator; +use std::collections::HashMap; use tokio::time::{Duration, timeout, timeout_at}; #[tokio::test] @@ -243,3 +244,90 @@ async fn test_third_peer_can_handshake_via_adopted_transport_socket() { transport.stop().await.ok(); } } + +#[tokio::test] +async fn test_adopted_udp_inherits_mtu_from_single_primary_config() { + let mut node = make_node(); + node.config.transports.udp = TransportInstances::Single(UdpConfig { + mtu: Some(1500), + ..Default::default() + }); + + let (packet_tx, packet_rx) = packet_channel(64); + node.packet_tx = Some(packet_tx); + node.packet_rx = Some(packet_rx); + node.state = NodeState::Running; + + let peer = make_node(); + let adopted_socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); + let handoff = EstablishedTraversal::new( + "sess-inherit-single", + peer.npub(), + "127.0.0.1:9".parse().unwrap(), + adopted_socket, + ); + + let result = node.adopt_established_traversal(handoff).await.unwrap(); + let adopted = node + .get_transport(&result.transport_id) + .expect("adopted transport present"); + assert_eq!( + adopted.mtu(), + 1500, + "adopted UDP transport should inherit MTU from the primary [transports.udp] config", + ); + + for (_, transport) in node.transports.iter_mut() { + transport.stop().await.ok(); + } +} + +#[tokio::test] +async fn test_adopted_udp_inherits_mtu_from_named_primary_config() { + let mut node = make_node(); + let mut named = HashMap::new(); + named.insert( + "primary".to_string(), + UdpConfig { + mtu: Some(1500), + ..Default::default() + }, + ); + named.insert( + "secondary".to_string(), + UdpConfig { + mtu: Some(1280), + ..Default::default() + }, + ); + node.config.transports.udp = TransportInstances::Named(named); + + let (packet_tx, packet_rx) = packet_channel(64); + node.packet_tx = Some(packet_tx); + node.packet_rx = Some(packet_rx); + node.state = NodeState::Running; + + let peer = make_node(); + let adopted_socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); + let handoff = EstablishedTraversal::new( + "sess-inherit-named", + peer.npub(), + "127.0.0.1:9".parse().unwrap(), + adopted_socket, + ) + .with_transport_name("primary"); + + let result = node.adopt_established_traversal(handoff).await.unwrap(); + let adopted = node + .get_transport(&result.transport_id) + .expect("adopted transport present"); + assert_eq!( + adopted.mtu(), + 1500, + "adopted UDP transport should inherit MTU from the named [transports.udp.] config matching transport_name", + ); + + for (_, transport) in node.transports.iter_mut() { + transport.stop().await.ok(); + } +}