Skip to content

Commit 526375e

Browse files
committed
Allow binding to port 0 for OS-assigned ports
Add support for configuring listening addresses with port 0, letting the OS pick a free port. After binding, the actual port is resolved via local_addr() and stored in a new bound_listening_addresses field. The listening_addresses() and announcement_addresses() getters now return the resolved bound addresses when the node is running, falling back to the configured addresses when stopped. The gossip broadcast task also prefers resolved addresses. This eliminates the need for the deterministic port picker in tests, which was fragile due to potential port collisions. Tests now use 127.0.0.1:0 and query the actual port after start(). AI tools were used in preparing this commit.
1 parent 3aef2b3 commit 526375e

4 files changed

Lines changed: 56 additions & 25 deletions

File tree

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,7 @@ fn build_with_store_internal(
19881988
peer_store,
19891989
payment_store,
19901990
lnurl_auth,
1991+
bound_listening_addresses: Arc::new(RwLock::new(None)),
19911992
is_running,
19921993
node_metrics,
19931994
om_mailbox,

src/lib.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ pub struct Node {
234234
peer_store: Arc<PeerStore<Arc<Logger>>>,
235235
payment_store: Arc<PaymentStore>,
236236
lnurl_auth: Arc<LnurlAuth>,
237+
bound_listening_addresses: Arc<RwLock<Option<Vec<SocketAddress>>>>,
237238
is_running: Arc<RwLock<bool>>,
238239
node_metrics: Arc<RwLock<NodeMetrics>>,
239240
om_mailbox: Option<Arc<OnionMessageMailbox>>,
@@ -403,6 +404,35 @@ impl Node {
403404
Ok(listeners)
404405
})?;
405406

407+
// Store the actual bound addresses (resolving port 0 to real ports).
408+
let mut bound_addrs = Vec::with_capacity(listeners.len());
409+
for listener in &listeners {
410+
let local_addr = listener.local_addr().map_err(|e| {
411+
log_error!(
412+
self.logger,
413+
"Failed to retrieve local address from listener: {}",
414+
e
415+
);
416+
Error::InvalidSocketAddress
417+
})?;
418+
let socket_address = match local_addr {
419+
std::net::SocketAddr::V4(a) => {
420+
SocketAddress::TcpIpV4 { addr: a.ip().octets(), port: a.port() }
421+
},
422+
std::net::SocketAddr::V6(a) => {
423+
SocketAddress::TcpIpV6 { addr: a.ip().octets(), port: a.port() }
424+
},
425+
};
426+
bound_addrs.push(socket_address);
427+
}
428+
429+
log_info!(
430+
self.logger,
431+
"Listening on {}",
432+
bound_addrs.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(", ")
433+
);
434+
*self.bound_listening_addresses.write().unwrap() = Some(bound_addrs);
435+
406436
for listener in listeners {
407437
let logger = Arc::clone(&listening_logger);
408438
let peer_mgr = Arc::clone(&peer_manager_connection_handler);
@@ -478,6 +508,7 @@ impl Node {
478508
let bcast_store = Arc::clone(&self.kv_store);
479509
let bcast_logger = Arc::clone(&self.logger);
480510
let bcast_node_metrics = Arc::clone(&self.node_metrics);
511+
let bcast_bound_addrs = Arc::clone(&self.bound_listening_addresses);
481512
let mut stop_bcast = self.stop_sender.subscribe();
482513
let node_alias = self.config.node_alias.clone();
483514
if may_announce_channel(&self.config).is_ok() {
@@ -525,6 +556,8 @@ impl Node {
525556

526557
let addresses = if let Some(announcement_addresses) = bcast_config.announcement_addresses.clone() {
527558
announcement_addresses
559+
} else if let Some(bound_addrs) = bcast_bound_addrs.read().unwrap().clone() {
560+
bound_addrs
528561
} else if let Some(listening_addresses) = bcast_config.listening_addresses.clone() {
529562
listening_addresses
530563
} else {
@@ -740,6 +773,7 @@ impl Node {
740773
#[cfg(tokio_unstable)]
741774
self.runtime.log_metrics();
742775

776+
*self.bound_listening_addresses.write().unwrap() = None;
743777
log_info!(self.logger, "Shutdown complete.");
744778
*is_running_lock = false;
745779
Ok(())
@@ -842,16 +876,21 @@ impl Node {
842876
}
843877

844878
/// Returns our own listening addresses.
879+
///
880+
/// When the node is running, this returns the actual bound addresses (which may differ from
881+
/// the configured addresses if port 0 was used). When the node is not running, this returns
882+
/// the configured addresses.
845883
pub fn listening_addresses(&self) -> Option<Vec<SocketAddress>> {
846-
self.config.listening_addresses.clone()
884+
self.bound_listening_addresses
885+
.read()
886+
.unwrap()
887+
.clone()
888+
.or_else(|| self.config.listening_addresses.clone())
847889
}
848890

849891
/// Returns the addresses that the node will announce to the network.
850892
pub fn announcement_addresses(&self) -> Option<Vec<SocketAddress>> {
851-
self.config
852-
.announcement_addresses
853-
.clone()
854-
.or_else(|| self.config.listening_addresses.clone())
893+
self.config.announcement_addresses.clone().or_else(|| self.listening_addresses())
855894
}
856895

857896
/// Returns our node alias.

tests/common/mod.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use std::collections::{HashMap, HashSet};
1414
use std::env;
1515
use std::future::Future;
1616
use std::path::PathBuf;
17-
use std::sync::atomic::{AtomicU16, Ordering};
1817
use std::sync::{Arc, RwLock};
1918
use std::time::Duration;
2019

@@ -269,16 +268,6 @@ pub(crate) fn random_storage_path() -> PathBuf {
269268
temp_path
270269
}
271270

272-
static NEXT_PORT: AtomicU16 = AtomicU16::new(20000);
273-
274-
pub(crate) fn generate_listening_addresses() -> Vec<SocketAddress> {
275-
let port = NEXT_PORT.fetch_add(2, Ordering::Relaxed);
276-
vec![
277-
SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port },
278-
SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: port + 1 },
279-
]
280-
}
281-
282271
pub(crate) fn random_node_alias() -> Option<NodeAlias> {
283272
let mut rng = rng();
284273
let rand_val = rng.random_range(0..1000);
@@ -302,7 +291,7 @@ pub(crate) fn random_config(anchor_channels: bool) -> TestConfig {
302291
println!("Setting random LDK storage dir: {}", rand_dir.display());
303292
node_config.storage_dir_path = rand_dir.to_str().unwrap().to_owned();
304293

305-
let listening_addresses = generate_listening_addresses();
294+
let listening_addresses = vec![SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 0 }];
306295
println!("Setting LDK listening addresses: {:?}", listening_addresses);
307296
node_config.listening_addresses = Some(listening_addresses);
308297

tests/integration_tests_rust.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ use common::{
2121
expect_channel_pending_event, expect_channel_ready_event, expect_channel_ready_events,
2222
expect_event, expect_payment_claimable_event, expect_payment_received_event,
2323
expect_payment_successful_event, expect_splice_pending_event, generate_blocks_and_wait,
24-
generate_listening_addresses, open_channel, open_channel_push_amt, open_channel_with_all,
25-
premine_and_distribute_funds, premine_blocks, prepare_rbf, random_chain_source, random_config,
26-
setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, splice_in_with_all,
27-
wait_for_tx, TestChainSource, TestStoreType, TestSyncStore,
24+
open_channel, open_channel_push_amt, open_channel_with_all, premine_and_distribute_funds,
25+
premine_blocks, prepare_rbf, random_chain_source, random_config, setup_bitcoind_and_electrsd,
26+
setup_builder, setup_node, setup_two_nodes, splice_in_with_all, wait_for_tx, TestChainSource,
27+
TestStoreType, TestSyncStore,
2828
};
2929
use electrsd::corepc_node::Node as BitcoinD;
3030
use electrsd::ElectrsD;
@@ -37,6 +37,7 @@ use ldk_node::payment::{
3737
};
3838
use ldk_node::{Builder, Event, NodeError};
3939
use lightning::ln::channelmanager::PaymentId;
40+
use lightning::ln::msgs::SocketAddress;
4041
use lightning::routing::gossip::{NodeAlias, NodeId};
4142
use lightning::routing::router::RouteParametersConfig;
4243
use lightning_invoice::{Bolt11InvoiceDescription, Description};
@@ -1431,9 +1432,11 @@ async fn test_node_announcement_propagation() {
14311432
node_a_alias_bytes[..node_a_alias_string.as_bytes().len()]
14321433
.copy_from_slice(node_a_alias_string.as_bytes());
14331434
let node_a_node_alias = Some(NodeAlias(node_a_alias_bytes));
1434-
let node_a_announcement_addresses = generate_listening_addresses();
1435+
let node_a_announcement_addresses = vec![
1436+
SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 10001 },
1437+
SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 10002 },
1438+
];
14351439
config_a.node_config.node_alias = node_a_node_alias.clone();
1436-
config_a.node_config.listening_addresses = Some(generate_listening_addresses());
14371440
config_a.node_config.announcement_addresses = Some(node_a_announcement_addresses.clone());
14381441

14391442
// Node B will only use listening addresses
@@ -1443,9 +1446,7 @@ async fn test_node_announcement_propagation() {
14431446
node_b_alias_bytes[..node_b_alias_string.as_bytes().len()]
14441447
.copy_from_slice(node_b_alias_string.as_bytes());
14451448
let node_b_node_alias = Some(NodeAlias(node_b_alias_bytes));
1446-
let node_b_listening_addresses = generate_listening_addresses();
14471449
config_b.node_config.node_alias = node_b_node_alias.clone();
1448-
config_b.node_config.listening_addresses = Some(node_b_listening_addresses.clone());
14491450
config_b.node_config.announcement_addresses = None;
14501451

14511452
let node_a = setup_node(&chain_source, config_a);
@@ -1505,6 +1506,7 @@ async fn test_node_announcement_propagation() {
15051506
#[cfg(feature = "uniffi")]
15061507
assert_eq!(node_b_announcement_info.alias, node_b_alias_string);
15071508

1509+
let node_b_listening_addresses = node_b.listening_addresses().unwrap();
15081510
#[cfg(not(feature = "uniffi"))]
15091511
assert_eq!(node_b_announcement_info.addresses(), &node_b_listening_addresses);
15101512
#[cfg(feature = "uniffi")]

0 commit comments

Comments
 (0)