feat(network): Configurable external address for Version network messages (#8488)

* add configurable external address for Version network messages

* apply suggestions from code review

Co-authored-by: Arya <aryasolhi@gmail.com>

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2024-05-02 22:07:52 -03:00 committed by GitHub
parent 8a786fe6ce
commit 239fcc85ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 73 additions and 2 deletions

View File

@ -72,6 +72,14 @@ pub struct Config {
/// their address books.
pub listen_addr: SocketAddr,
/// The external address of this node if any.
///
/// Zebra bind to `listen_addr` but this can be an internal address if the node
/// is behind a firewall, load balancer or NAT. This field can be used to
/// advertise a different address to peers making it possible to receive inbound
/// connections and contribute to the P2P network from behind a firewall, load balancer, or NAT.
pub external_addr: Option<SocketAddr>,
/// The network to connect to.
pub network: Network,
@ -601,6 +609,7 @@ impl Default for Config {
listen_addr: "0.0.0.0:8233"
.parse()
.expect("Hardcoded address should be parseable"),
external_addr: None,
network: Network::Mainnet,
initial_mainnet_peers: mainnet_peers,
initial_testnet_peers: testnet_peers,
@ -635,6 +644,7 @@ impl<'de> Deserialize<'de> for Config {
#[serde(deny_unknown_fields, default)]
struct DConfig {
listen_addr: String,
external_addr: Option<String>,
network: NetworkKind,
testnet_parameters: Option<DTestnetParameters>,
initial_mainnet_peers: IndexSet<String>,
@ -651,6 +661,7 @@ impl<'de> Deserialize<'de> for Config {
let config = Config::default();
Self {
listen_addr: "0.0.0.0".to_string(),
external_addr: None,
network: Default::default(),
testnet_parameters: None,
initial_mainnet_peers: config.initial_mainnet_peers,
@ -665,6 +676,7 @@ impl<'de> Deserialize<'de> for Config {
let DConfig {
listen_addr,
external_addr,
network: network_kind,
testnet_parameters,
initial_mainnet_peers,
@ -737,6 +749,20 @@ impl<'de> Deserialize<'de> for Config {
},
}?;
let external_socket_addr = if let Some(address) = &external_addr {
match address.parse::<SocketAddr>() {
Ok(socket) => Ok(Some(socket)),
Err(_) => match address.parse::<IpAddr>() {
Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
Err(err) => Err(de::Error::custom(format!(
"{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
))),
},
}?
} else {
None
};
let [max_connections_per_ip, peerset_initial_target_size] = [
("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
// If we want Zebra to operate with no network,
@ -756,6 +782,7 @@ impl<'de> Deserialize<'de> for Config {
Ok(Config {
listen_addr: canonical_socket_addr(listen_addr),
external_addr: external_socket_addr,
network,
initial_mainnet_peers,
initial_testnet_peers,

View File

@ -654,7 +654,17 @@ where
let their_addr = connected_addr
.get_transient_addr()
.expect("non-Isolated connections have a remote addr");
(their_addr, our_services, config.listen_addr)
// Include the configured external address in our version message, if any, otherwise, include our listen address.
let advertise_addr = match config.external_addr {
Some(external_addr) => {
info!(?their_addr, ?config.listen_addr, "using external address for Version messages");
external_addr
}
None => config.listen_addr,
};
(their_addr, our_services, advertise_addr)
}
};

View File

@ -184,7 +184,8 @@ use common::{
check::{is_zebrad_version, EphemeralCheck, EphemeralConfig},
config::random_known_rpc_port_config,
config::{
config_file_full_path, configs_dir, default_test_config, persistent_test_config, testdir,
config_file_full_path, configs_dir, default_test_config, external_address_test_config,
persistent_test_config, testdir,
},
launch::{
spawn_zebrad_for_rpc, spawn_zebrad_without_rpc, ZebradTestDirExt, BETWEEN_NODES_DELAY,
@ -3128,6 +3129,33 @@ async fn validate_regtest_genesis_block() {
)
}
/// Test that Version messages are sent with the external address when configured to do so.
#[test]
fn external_address() -> Result<()> {
let _init_guard = zebra_test::init();
let testdir = testdir()?.with_config(&mut external_address_test_config(&Mainnet)?)?;
let mut child = testdir.spawn_child(args!["start"])?;
// Give enough time to start connecting to some peers.
std::thread::sleep(Duration::from_secs(10));
child.kill(false)?;
let output = child.wait_with_output()?;
let output = output.assert_failure()?;
// Zebra started
output.stdout_line_contains("Starting zebrad")?;
// Make sure we are using external address for Version messages.
output.stdout_line_contains("using external address for Version messages")?;
// Make sure the command was killed.
output.assert_was_killed()?;
Ok(())
}
/// Test successful `getblocktemplate` and `submitblock` RPC calls on Regtest on Canopy.
///
/// See [`common::regtest::submit_blocks`] for more information.

View File

@ -119,6 +119,12 @@ pub fn persistent_test_config(network: &Network) -> Result<ZebradConfig> {
Ok(config)
}
pub fn external_address_test_config(network: &Network) -> Result<ZebradConfig> {
let mut config = default_test_config(network)?;
config.network.external_addr = Some("127.0.0.1:0".parse()?);
Ok(config)
}
pub fn testdir() -> Result<TempDir> {
tempfile::Builder::new()
.prefix("zebrad_tests")