diff --git a/bin/lilith/src/main.rs b/bin/lilith/src/main.rs index 03d53e381..60abd4528 100644 --- a/bin/lilith/src/main.rs +++ b/bin/lilith/src/main.rs @@ -363,6 +363,8 @@ async fn spawn_net(name: String, info: &NetInfo, ex: Arc>) -> "tor+tls".to_string(), "nym".to_string(), "nym+tls".to_string(), + "i2p".to_string(), + "i2p+tls".to_string(), ], ban_policy: BanPolicy::Relaxed, ..Default::default() diff --git a/src/net/connector.rs b/src/net/connector.rs index e65776a77..b4385a56e 100644 --- a/src/net/connector.rs +++ b/src/net/connector.rs @@ -67,6 +67,7 @@ impl Connector { let transport_mixing = settings.transport_mixing; let datastore = settings.p2p_datastore.clone(); let outbound_connect_timeout = settings.outbound_connect_timeout; + let i2p_socks5_proxy = settings.i2p_socks5_proxy.clone(); let socks5_proxy = settings.socks5_proxy.clone(); drop(settings); @@ -111,7 +112,7 @@ impl Connector { } } - let dialer = Dialer::new(endpoint.clone(), datastore).await?; + let dialer = Dialer::new(endpoint.clone(), datastore, Some(i2p_socks5_proxy)).await?; let timeout = Duration::from_secs(outbound_connect_timeout); let stop_fut = async { diff --git a/src/net/hosts.rs b/src/net/hosts.rs index 0318ad393..e82ed0639 100644 --- a/src/net/hosts.rs +++ b/src/net/hosts.rs @@ -40,6 +40,7 @@ use super::{ use crate::{ system::{Publisher, PublisherPtr, Subscription}, util::{ + encoding::base32, file::{load_file, save_file}, most_frequent_or_any, path::expand_path, @@ -1357,6 +1358,16 @@ impl Hosts { ); } + "i2p" | "i2p+tls" => { + if !Self::is_i2p_host(host_str) { + continue + } + trace!( + target: "net::hosts::filter_addresses", + "[I2p] Valid: {}", host_str, + ); + } + _ => continue, } @@ -1501,7 +1512,7 @@ impl Hosts { // with stuff that has host_str(). if addr.host_str().is_some() { // Localhost connections should never enter the blacklist - // This however allows any Tor and Nym connections. + // This however allows any Tor, Nym and I2p connections. if self.is_local_host(addr) { return Ok(()); } @@ -1590,6 +1601,26 @@ impl Hosts { None } + + fn is_i2p_host(host: &str) -> bool { + if !host.ends_with(".i2p") { + return false + } + + // Two kinds of address + // 1. wvbtv6i6njxdtxwsgsr3d4xejdtsy6n7s3d2paqgigjkv3fv5imq.b32.i2p + // 2. node.darkfi.i2p + let name = host.trim_end_matches(".i2p"); + + if name.ends_with(".b32") { + let b32 = name.trim_end_matches(".b32"); + let decoded = base32::decode(b32); + // decoded should be a SHA256 hash + return decoded.is_some() && decoded.unwrap().len() == 32 + } + + name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.') + } } /// We need a convenience method from Rust's unstable feature "ip". @@ -1866,4 +1897,12 @@ mod tests { println!("last entry: {} {}", entry.0, entry.1); }); } + + #[test] + fn test_is_p2p_host() { + assert!(Hosts::is_i2p_host("tm7bz5qfh73id33yjpshxmesrqedoz2ckghd3levktqywcrramwq.b32.i2p")); + assert!(!Hosts::is_i2p_host("randomstring.b32.i2p")); + assert!(Hosts::is_i2p_host("node.dark.fi.i2p")); + assert!(!Hosts::is_i2p_host("node.dark.fi")); + } } diff --git a/src/net/protocol/protocol_address.rs b/src/net/protocol/protocol_address.rs index 503057ca7..12a496f50 100644 --- a/src/net/protocol/protocol_address.rs +++ b/src/net/protocol/protocol_address.rs @@ -73,7 +73,8 @@ const PROTO_NAME: &str = "ProtocolAddress"; /// combinations. Should be updated if and when new transports are /// added. Creates a upper bound on the number of transports a given peer /// can request. -const TRANSPORT_COMBOS: [&str; 7] = ["tor", "tls", "tcp", "nym", "tor+tls", "nym+tls", "tcp+tls"]; +const TRANSPORT_COMBOS: [&str; 9] = + ["tor", "tls", "tcp", "nym", "i2p", "tor+tls", "nym+tls", "tcp+tls", "i2p+tls"]; impl ProtocolAddress { /// Creates a new address protocol. Makes an address, an external address diff --git a/src/net/settings.rs b/src/net/settings.rs index 938271136..8990537e3 100644 --- a/src/net/settings.rs +++ b/src/net/settings.rs @@ -67,6 +67,8 @@ pub struct Settings { /// Socks5 proxy to connect to when socks5 or socks5+tls are added to allowed transports /// and transport mixing is enabled pub socks5_proxy: Url, + /// I2p Socks5 proxy to connect to i2p eepsite (hidden services) + pub i2p_socks5_proxy: Url, /// Outbound connection slots number, this many connections will be /// attempted. (This does not include manual connections) pub outbound_connections: usize, @@ -128,6 +130,7 @@ impl Default for Settings { allowed_transports: vec!["tcp+tls".to_string()], transport_mixing: true, socks5_proxy: Url::parse("socks5://127.0.0.1:9050").unwrap(), + i2p_socks5_proxy: Url::parse("socks5://127.0.0.1:4447").unwrap(), outbound_connections: 8, inbound_connections: 8, outbound_connect_timeout: 15, @@ -234,6 +237,10 @@ pub struct SettingsOpt { #[structopt(long)] pub socks5_proxy: Option, + /// I2p Socks5 proxy to connect to i2p eepsite (hidden services) + #[structopt(long)] + pub i2p_socks5_proxy: Option, + /// If this is true, strictly follow the gold_connect_count and /// white_connect_percent settings. Otherwise, connect to greylist /// entries if we have no white or gold connections. @@ -311,6 +318,7 @@ impl From for Settings { allowed_transports: opt.allowed_transports.unwrap_or(def.allowed_transports), transport_mixing: opt.transport_mixing.unwrap_or(def.transport_mixing), socks5_proxy: opt.socks5_proxy.unwrap_or(def.socks5_proxy), + i2p_socks5_proxy: opt.i2p_socks5_proxy.unwrap_or(def.i2p_socks5_proxy), outbound_connections: opt.outbound_connections.unwrap_or(def.outbound_connections), inbound_connections: opt.inbound_connections.unwrap_or(def.inbound_connections), outbound_connect_timeout: opt diff --git a/src/net/transport/mod.rs b/src/net/transport/mod.rs index 184be48ac..6fecba535 100644 --- a/src/net/transport/mod.rs +++ b/src/net/transport/mod.rs @@ -132,7 +132,11 @@ macro_rules! enforce_abspath { impl Dialer { /// Instantiate a new [`Dialer`] with the given [`Url`] and datastore path. - pub async fn new(endpoint: Url, datastore: Option) -> io::Result { + pub async fn new( + endpoint: Url, + datastore: Option, + i2p_socks5_proxy: Option, + ) -> io::Result { match endpoint.scheme().to_lowercase().as_str() { "tcp" => { // Build a TCP dialer @@ -211,6 +215,27 @@ impl Dialer { Ok(Self { endpoint, variant }) } + "i2p" => { + // Build a Socks5 Dialer for I2p + enforce_hostport!(endpoint); + let mut url = i2p_socks5_proxy.unwrap(); + url.set_path(&format!("{}:{}", endpoint.host().unwrap(), endpoint.port().unwrap())); + let variant = socks5::Socks5Dialer::new(&url).await?; + let variant = DialerVariant::Socks5(variant); + Ok(Self { endpoint, variant }) + } + + "i2p+tls" => { + // Build a SOCKS5 dialer with TLS encapsulation for I2p + enforce_hostport!(endpoint); + let mut url = i2p_socks5_proxy.unwrap(); + url.set_path(&format!("{}:{}", endpoint.host().unwrap(), endpoint.port().unwrap())); + url.set_scheme("socks5+tls").unwrap(); + let variant = socks5::Socks5Dialer::new(&url).await?; + let variant = DialerVariant::Socks5Tls(variant); + Ok(Self { endpoint, variant }) + } + x => { error!("[P2P] Requested unsupported transport: {}", x); Err(io::Error::from_raw_os_error(libc::ENETUNREACH)) diff --git a/src/rpc/client.rs b/src/rpc/client.rs index 0ad537a61..171be2dcf 100644 --- a/src/rpc/client.rs +++ b/src/rpc/client.rs @@ -70,7 +70,7 @@ impl RpcClient { // Instantiate Dialer and dial the server // TODO: Could add a timeout here - let dialer = Dialer::new(dialer_url, None).await?; + let dialer = Dialer::new(dialer_url, None, None).await?; let stream = dialer.dial(None).await?; // Create the StoppableTask running the request-reply loop. @@ -322,7 +322,7 @@ impl RpcChadClient { // Instantiate Dialer and dial the server // TODO: Could add a timeout here - let dialer = Dialer::new(dialer_url, None).await?; + let dialer = Dialer::new(dialer_url, None, None).await?; let stream = dialer.dial(None).await?; // Create the StoppableTask running the request-reply loop. diff --git a/tests/network_transports.rs b/tests/network_transports.rs index e6a2eab3c..5c7128c7f 100644 --- a/tests/network_transports.rs +++ b/tests/network_transports.rs @@ -39,7 +39,7 @@ fn tcp_transport() { let payload = "ohai tcp"; - let dialer = Dialer::new(url, None).await.unwrap(); + let dialer = Dialer::new(url, None, None).await.unwrap(); let mut client = dialer.dial(None).await.unwrap(); payload.encode_async(&mut client).await.unwrap(); @@ -70,7 +70,7 @@ fn tcp_tls_transport() { let payload = "ohai tls"; - let dialer = Dialer::new(url, None).await.unwrap(); + let dialer = Dialer::new(url, None, None).await.unwrap(); let mut client = dialer.dial(None).await.unwrap(); payload.encode_async(&mut client).await.unwrap(); @@ -103,7 +103,7 @@ fn unix_transport() { let payload = "ohai unix"; - let dialer = Dialer::new(url, None).await.unwrap(); + let dialer = Dialer::new(url, None, None).await.unwrap(); let mut client = dialer.dial(None).await.unwrap(); payload.encode_async(&mut client).await.unwrap();