Skip to content

Commit

Permalink
add hostname to --connect
Browse files Browse the repository at this point in the history
  • Loading branch information
Davidson-Souza committed Nov 22, 2024
1 parent aa260fe commit 1348a82
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 17 deletions.
4 changes: 1 addition & 3 deletions crates/floresta-wire/src/p2p_wire/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ use std::net::SocketAddr;
use bitcoin::Network;
use floresta_chain::AssumeUtreexoValue;

use self::address_man::LocalAddress;

#[derive(Debug, Clone)]
/// Configuration for the Utreexo node.
pub struct UtreexoNodeConfig {
Expand All @@ -30,7 +28,7 @@ pub struct UtreexoNodeConfig {
///
/// If you want to connect to a specific peer, you can set this to a string with the
/// format `ip:port`. For example, `localhost:8333`.
pub fixed_peer: Option<LocalAddress>,
pub fixed_peer: Option<String>,
/// Maximum ban score. Defaults to 100.
///
/// If a peer misbehaves, we increase its ban score. If the ban score reaches this value,
Expand Down
256 changes: 255 additions & 1 deletion crates/floresta-wire/src/p2p_wire/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
//! A node should not care about peer-specific messages, peers'll handle things like pings.
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::ops::Deref;
use std::ops::DerefMut;
use std::process::exit;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
Expand Down Expand Up @@ -210,6 +214,25 @@ where
) -> Self {
let (node_tx, node_rx) = unbounded_channel();
let socks5 = config.proxy.map(Socks5StreamBuilder::new);

let fixed_peer = config.fixed_peer.as_ref().map(|address| {
let address =
Self::resolve_connect_host(&address, Self::get_port(config.network.into()));
match address {
Ok(address) => {
info!(
"Using {} as fixed peer",
address.get_net_address().to_string()
);
address
}
Err(e) => {
warn!("Invalid fixed peer address: {:?}", e);
exit(1);
}
}
});

UtreexoNode(
NodeCommon {
last_filter: chain.get_block_hash(0).unwrap(),
Expand Down Expand Up @@ -237,13 +260,122 @@ where
datadir: config.datadir.clone(),
socks5,
max_banscore: config.max_banscore,
fixed_peer: config.fixed_peer.clone(),
fixed_peer,
config,
},
T::default(),
)
}

fn get_port(network: Network) -> u16 {
match network {
Network::Bitcoin => 8333,
Network::Signet => 38333,
Network::Testnet => 18333,
Network::Regtest => 18444,
}
}

fn resolve_connect_host(
address: &str,
default_port: u16,
) -> Result<LocalAddress, AddrParseError> {
// ipv6
if address.starts_with('[') {
if !address.contains(']') {
return Err(AddrParseError::InvalidIpv6);
}

let mut split = address.trim_end().split(']');
let hostname = split.next().ok_or(AddrParseError::InvalidIpv6)?;
let port = split
.next()
.filter(|x| !x.is_empty())
.map(|port| {
port.trim_start_matches(':')
.parse()
.map_err(|_e| AddrParseError::InvalidPort)
})
.transpose()?
.unwrap_or(default_port);

let hostname = hostname.trim_start_matches('[');
let ip = hostname.parse().map_err(|_e| AddrParseError::InvalidIpv6)?;
return Ok(LocalAddress::new(
AddrV2::Ipv6(ip),
0,
AddressState::NeverTried,
ServiceFlags::NONE,
port,
rand::random(),
));
}

// ipv4 - it's hard to differentiate between ipv4 and hostname without an actual regex
// simply try to parse it as an ip address and if it fails, assume it's a hostname
let mut split = address.split(':');
let ip = split
.next()
.ok_or(AddrParseError::InvalidIpv4)?
.parse()
.map_err(|_e| AddrParseError::InvalidIpv4);

match ip {
Ok(ip) => {
let port = split
.next()
.map(|port| port.parse().map_err(|_e| AddrParseError::InvalidPort))
.transpose()?
.unwrap_or(default_port);

if split.next().is_some() {
return Err(AddrParseError::Inconclusive);
}

let id = rand::random();
Ok(LocalAddress::new(
AddrV2::Ipv4(ip),
0,
AddressState::NeverTried,
ServiceFlags::NONE,
port,
id,
))
}

Err(_) => {
let mut split = address.split(':');
let hostname = split.next().ok_or(AddrParseError::InvalidHostname)?;
let port = split
.next()
.map(|port| port.parse().map_err(|_e| AddrParseError::InvalidPort))
.transpose()?
.unwrap_or(default_port);

if split.next().is_some() {
return Err(AddrParseError::Inconclusive);
}

let ip = dns_lookup::lookup_host(hostname)
.map_err(|_e| AddrParseError::InvalidHostname)?;
let id = rand::random();
let ip = match ip[0] {
std::net::IpAddr::V4(ip) => AddrV2::Ipv4(ip),
std::net::IpAddr::V6(ip) => AddrV2::Ipv6(ip),
};

Ok(LocalAddress::new(
ip,
0,
AddressState::NeverTried,
ServiceFlags::NONE,
port,
id,
))
}
}
}

pub(crate) fn get_peer_info(&self, peer: &u32) -> Option<PeerInfo> {
let peer = self.peers.get(peer)?;
Some(PeerInfo {
Expand Down Expand Up @@ -898,6 +1030,27 @@ where
}
}

#[derive(Debug, Clone)]
pub enum AddrParseError {
InvalidIpv6,
InvalidIpv4,
InvalidHostname,
InvalidPort,
Inconclusive,
}

impl Display for AddrParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AddrParseError::InvalidIpv6 => write!(f, "Invalid ipv6"),
AddrParseError::InvalidIpv4 => write!(f, "Invalid ipv4"),
AddrParseError::InvalidHostname => write!(f, "Invalid hostname"),
AddrParseError::InvalidPort => write!(f, "Invalid port"),
AddrParseError::Inconclusive => write!(f, "Inconclusive"),
}
}
}

/// Run a task and log any errors that might occur.
macro_rules! try_and_log {
($what:expr) => {
Expand All @@ -908,6 +1061,7 @@ macro_rules! try_and_log {
}
};
}

macro_rules! periodic_job {
($what:expr, $timer:expr, $interval:ident, $context:ty) => {
if $timer.elapsed() > Duration::from_secs(<$context>::$interval) {
Expand All @@ -922,5 +1076,105 @@ macro_rules! periodic_job {
}
};
}

pub(crate) use periodic_job;
pub(crate) use try_and_log;

#[cfg(test)]
mod tests {
use floresta_chain::pruned_utreexo::partial_chain::PartialChainState;

use crate::node::UtreexoNode;
use crate::running_node::RunningNode;

#[test]
fn test_parse_address() {
let ipv6_valid = "[::1]";
let ipv6_invalid = "[::1";
let ipv6_with_port = "[::1]:8333";
let ipv6_with_invalid_port = "[::1]:8333:8333";

let ipv4_valid = "127.0.0.1";
let ipv4_invalid = "321.321.321.321";
let ipv4_with_port = "127.0.0.1:8333";
let ipv4_with_invalid_port = "127.0.0.1:8333:8333";

let hostname_valid = "example.com";
let hostname_invalid = "example";
let hostname_with_port = "example.com:8333";
let hostname_with_invalid_port = "example.com:8333:8333";

UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(ipv6_valid, 8333)
.unwrap();
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(ipv6_invalid, 8333)
.is_err()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
ipv6_with_port,
8333
)
.is_ok()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
ipv6_with_invalid_port,
8333
)
.is_err()
);

assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(ipv4_valid, 8333)
.is_ok()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(ipv4_invalid, 8333)
.is_err()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
ipv4_with_port,
8333
)
.is_ok()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
ipv4_with_invalid_port,
8333
)
.is_err()
);

assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
hostname_valid,
8333
)
.is_ok()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
hostname_invalid,
8333
)
.is_err()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
hostname_with_port,
8333
)
.is_ok()
);
assert!(
UtreexoNode::<RunningNode, PartialChainState>::resolve_connect_host(
hostname_with_invalid_port,
8333
)
.is_err()
);
}
}
18 changes: 5 additions & 13 deletions florestad/src/florestad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ use floresta_electrum::electrum_protocol::ElectrumServer;
use floresta_watch_only::kv_database::KvDatabase;
use floresta_watch_only::AddressCache;
use floresta_watch_only::AddressCacheDatabase;
use floresta_wire::address_man::LocalAddress;
use floresta_wire::mempool::Mempool;
use floresta_wire::node::UtreexoNode;
use floresta_wire::UtreexoNodeConfig;
Expand Down Expand Up @@ -245,6 +244,7 @@ impl Florestad {
.next()
.map(|x| x.parse().unwrap_or(default_port))
.unwrap_or(default_port);

SocketAddr::new(ips[0], port)
}
}
Expand Down Expand Up @@ -349,13 +349,6 @@ impl Florestad {
#[cfg(not(feature = "compact-filters"))]
let cfilters = None;

// Handle the `-connect` cli option
let connect = self
.config
.clone()
.connect
.map(|host| host.parse::<LocalAddress>().unwrap());

// For now we only have compatible bridges on signet
let pow_fraud_proofs = match self.config.network {
crate::Network::Bitcoin => false,
Expand All @@ -381,7 +374,7 @@ impl Florestad {
.as_ref()
.map(|host| Self::get_ip_address(host, 9050)),
datadir: data_dir.clone(),
fixed_peer: connect,
fixed_peer: self.config.connect.clone(),
max_banscore: 50,
compact_filters: self.config.cfilters,
max_outbound: 10,
Expand Down Expand Up @@ -499,11 +492,10 @@ impl Florestad {

// TLS Electrum accept loop
if let Some(tls_acceptor) = tls_acceptor {
let tls_listener = Arc::new(
block_on(TcpListener::bind(ssl_e_addr)).unwrap_or_else(|e| {
let tls_listener =
Arc::new(block_on(TcpListener::bind(ssl_e_addr)).unwrap_or_else(|e| {
panic!("Cannot bind to ssl electrum address {}: {}", ssl_e_addr, e)
}),
);
}));
task::spawn(client_accept_loop(
tls_listener,
electrum_server.message_transmitter.clone(),
Expand Down

0 comments on commit 1348a82

Please sign in to comment.