net: add i2p transport

This commit is contained in:
oars
2025-04-19 10:48:45 +03:00
parent 0aefb38307
commit c73e8607bc
8 changed files with 85 additions and 9 deletions

View File

@@ -363,6 +363,8 @@ async fn spawn_net(name: String, info: &NetInfo, ex: Arc<Executor<'static>>) ->
"tor+tls".to_string(),
"nym".to_string(),
"nym+tls".to_string(),
"i2p".to_string(),
"i2p+tls".to_string(),
],
ban_policy: BanPolicy::Relaxed,
..Default::default()

View File

@@ -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 {

View File

@@ -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"));
}
}

View File

@@ -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

View File

@@ -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<Url>,
/// I2p Socks5 proxy to connect to i2p eepsite (hidden services)
#[structopt(long)]
pub i2p_socks5_proxy: Option<Url>,
/// 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<SettingsOpt> 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

View File

@@ -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<String>) -> io::Result<Self> {
pub async fn new(
endpoint: Url,
datastore: Option<String>,
i2p_socks5_proxy: Option<Url>,
) -> io::Result<Self> {
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))

View File

@@ -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.

View File

@@ -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();