Skip to content

Commit 6215f1b

Browse files
committed
Track actual bound listening addresses in Node
Fix a TOCTOU race condition in integration tests where randomly allocated ports were released before Node::start() re-bound them, causing sporadic InvalidSocketAddress failures in CI. Node now stores the actual addresses returned by TcpListener::bind() and exposes them via listening_addresses(), which returns None when the node is not running. Tests use port 0 to let the OS assign ports at bind time, eliminating the race entirely. AI tools were used in preparing this commit.
1 parent a555133 commit 6215f1b

File tree

3 files changed

+22
-16
lines changed

3 files changed

+22
-16
lines changed

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,7 @@ fn build_with_store_internal(
19941994
payment_store,
19951995
lnurl_auth,
19961996
is_running,
1997+
actual_listening_addresses: Arc::new(RwLock::new(None)),
19971998
node_metrics,
19981999
om_mailbox,
19992000
async_payments_role,

src/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ pub struct Node {
235235
payment_store: Arc<PaymentStore>,
236236
lnurl_auth: Arc<LnurlAuth>,
237237
is_running: Arc<RwLock<bool>>,
238+
actual_listening_addresses: Arc<RwLock<Option<Vec<SocketAddress>>>>,
238239
node_metrics: Arc<RwLock<NodeMetrics>>,
239240
om_mailbox: Option<Arc<OnionMessageMailbox>>,
240241
async_payments_role: Option<AsyncPaymentsRole>,
@@ -403,6 +404,12 @@ impl Node {
403404
Ok(listeners)
404405
})?;
405406

407+
let actual_addrs: Vec<SocketAddress> = listeners
408+
.iter()
409+
.map(|l| l.local_addr().expect("listener must have local addr").into())
410+
.collect();
411+
*self.actual_listening_addresses.write().unwrap() = Some(actual_addrs);
412+
406413
for listener in listeners {
407414
let logger = Arc::clone(&listening_logger);
408415
let peer_mgr = Arc::clone(&peer_manager_connection_handler);
@@ -478,6 +485,7 @@ impl Node {
478485
let bcast_store = Arc::clone(&self.kv_store);
479486
let bcast_logger = Arc::clone(&self.logger);
480487
let bcast_node_metrics = Arc::clone(&self.node_metrics);
488+
let bcast_actual_listening_addresses = Arc::clone(&self.actual_listening_addresses);
481489
let mut stop_bcast = self.stop_sender.subscribe();
482490
let node_alias = self.config.node_alias.clone();
483491
if may_announce_channel(&self.config).is_ok() {
@@ -525,7 +533,7 @@ impl Node {
525533

526534
let addresses = if let Some(announcement_addresses) = bcast_config.announcement_addresses.clone() {
527535
announcement_addresses
528-
} else if let Some(listening_addresses) = bcast_config.listening_addresses.clone() {
536+
} else if let Some(listening_addresses) = bcast_actual_listening_addresses.read().unwrap().clone() {
529537
listening_addresses
530538
} else {
531539
debug_assert!(false, "We checked whether the node may announce, so listening addresses should always be set");
@@ -740,6 +748,8 @@ impl Node {
740748
#[cfg(tokio_unstable)]
741749
self.runtime.log_metrics();
742750

751+
*self.actual_listening_addresses.write().unwrap() = None;
752+
743753
log_info!(self.logger, "Shutdown complete.");
744754
*is_running_lock = false;
745755
Ok(())
@@ -842,16 +852,17 @@ impl Node {
842852
}
843853

844854
/// Returns our own listening addresses.
855+
///
856+
/// Returns the addresses that the node is actually bound to, which may differ
857+
/// from the configured addresses (e.g., when port 0 was configured to let the
858+
/// OS assign a port). Returns `None` if the node is not running.
845859
pub fn listening_addresses(&self) -> Option<Vec<SocketAddress>> {
846-
self.config.listening_addresses.clone()
860+
self.actual_listening_addresses.read().unwrap().clone()
847861
}
848862

849863
/// Returns the addresses that the node will announce to the network.
850864
pub fn announcement_addresses(&self) -> Option<Vec<SocketAddress>> {
851-
self.config
852-
.announcement_addresses
853-
.clone()
854-
.or_else(|| self.config.listening_addresses.clone())
865+
self.config.announcement_addresses.clone().or_else(|| self.listening_addresses())
855866
}
856867

857868
/// Returns our node alias.

tests/common/mod.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -269,16 +269,10 @@ pub(crate) fn random_storage_path() -> PathBuf {
269269
}
270270

271271
pub(crate) fn random_listening_addresses() -> Vec<SocketAddress> {
272-
let num_addresses = 2;
273-
let mut listening_addresses = HashSet::new();
274-
275-
while listening_addresses.len() < num_addresses {
276-
let socket = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
277-
let address: SocketAddress = socket.local_addr().unwrap().into();
278-
listening_addresses.insert(address);
279-
}
280-
281-
listening_addresses.into_iter().collect()
272+
vec![
273+
SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 0 },
274+
SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 0 },
275+
]
282276
}
283277

284278
pub(crate) fn random_node_alias() -> Option<NodeAlias> {

0 commit comments

Comments
 (0)