You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
8.1 KiB
Rust
239 lines
8.1 KiB
Rust
use data_encoding::BASE32;
|
|
use futures::future::Ready;
|
|
use futures::prelude::*;
|
|
use libp2p::core::multiaddr::{Multiaddr, Protocol};
|
|
use libp2p::core::transport::TransportError;
|
|
use libp2p::core::Transport;
|
|
use libp2p::tcp::tokio::{Tcp, TcpStream};
|
|
use libp2p::tcp::{GenTcpConfig, TcpListenStream, TokioTcpConfig};
|
|
use std::cmp::Ordering;
|
|
use std::io;
|
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
|
use std::pin::Pin;
|
|
use tokio_socks::tcp::Socks5Stream;
|
|
use tokio_socks::IntoTargetAddr;
|
|
|
|
/// Represents the configuration for a TCP/IP transport capability for libp2p.
|
|
#[derive(Clone)]
|
|
pub struct TorTcpConfig {
|
|
inner: GenTcpConfig<Tcp>,
|
|
/// Tor SOCKS5 proxy port number.
|
|
socks_port: u16,
|
|
}
|
|
|
|
impl TorTcpConfig {
|
|
pub fn new(tcp: TokioTcpConfig, socks_port: u16) -> Self {
|
|
Self {
|
|
inner: tcp,
|
|
socks_port,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Transport for TorTcpConfig {
|
|
type Output = TcpStream;
|
|
type Error = io::Error;
|
|
type Listener = TcpListenStream<Tcp>;
|
|
type ListenerUpgrade = Ready<Result<Self::Output, Self::Error>>;
|
|
#[allow(clippy::type_complexity)]
|
|
type Dial = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
|
|
|
|
fn listen_on(self, addr: Multiaddr) -> Result<Self::Listener, TransportError<Self::Error>> {
|
|
self.inner.listen_on(addr)
|
|
}
|
|
|
|
// dials via Tor's socks5 proxy if configured and if the provided address is an
|
|
// onion address. or it falls back to Tcp dialling
|
|
fn dial(self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
|
|
async fn do_tor_dial(socks_port: u16, dest: String) -> Result<TcpStream, io::Error> {
|
|
tracing::trace!("Connecting through Tor proxy to address: {}", dest);
|
|
let stream = connect_to_socks_proxy(dest, socks_port)
|
|
.await
|
|
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?;
|
|
tracing::trace!("Connection through Tor established");
|
|
Ok(stream)
|
|
}
|
|
|
|
match to_address_string(addr.clone()) {
|
|
Some(tor_address_string) => {
|
|
Ok(Box::pin(do_tor_dial(self.socks_port, tor_address_string)))
|
|
}
|
|
_ => {
|
|
tracing::warn!(
|
|
"Address {} could not be formatted. Dialling via clear net",
|
|
addr
|
|
);
|
|
self.inner.dial(addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option<Multiaddr> {
|
|
self.inner.address_translation(listen, observed)
|
|
}
|
|
}
|
|
|
|
/// Tor expects an address format of ADDR:PORT.
|
|
/// This helper function tries to convert the provided multi-address into this
|
|
/// format. None is returned if an unsupported protocol was provided.
|
|
fn to_address_string(multi: Multiaddr) -> Option<String> {
|
|
let components = multi.iter();
|
|
for protocol in components {
|
|
match protocol {
|
|
Protocol::Onion(addr, port) => {
|
|
tracing::warn!("Onion service v2 is being deprecated, consider upgrading to v3");
|
|
return Some(format!(
|
|
"{}.onion:{}",
|
|
BASE32.encode(addr.as_ref()).to_lowercase(),
|
|
port
|
|
));
|
|
}
|
|
Protocol::Onion3(addr) => {
|
|
return Some(format!(
|
|
"{}.onion:{}",
|
|
BASE32.encode(addr.hash()).to_lowercase(),
|
|
addr.port()
|
|
));
|
|
}
|
|
_ => {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deal with non-onion addresses
|
|
let protocols = multi.iter().collect::<Vec<_>>();
|
|
let address_string = protocols
|
|
.iter()
|
|
.filter_map(|protocol| match protocol {
|
|
Protocol::Ip4(addr) => Some(format!("{}", addr)),
|
|
Protocol::Ip6(addr) => Some(format!("{}", addr)),
|
|
Protocol::Dns(addr) => Some(format!("{}", addr)),
|
|
Protocol::Dns4(addr) => Some(format!("{}", addr)),
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
if address_string.is_empty() {
|
|
tracing::warn!(
|
|
"Could not format address {}. Please consider reporting this issue. ",
|
|
multi
|
|
);
|
|
return None;
|
|
}
|
|
|
|
let address_string = address_string
|
|
.get(0)
|
|
.expect("Valid multiaddr consist out of max 1 address")
|
|
.clone();
|
|
|
|
// check for port
|
|
let port = protocols
|
|
.iter()
|
|
.filter_map(|protocol| match protocol {
|
|
Protocol::Tcp(port) => Some(format!("{}", port)),
|
|
Protocol::Udp(port) => Some(format!("{}", port)),
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
match port.len().cmp(&1) {
|
|
Ordering::Greater => {
|
|
tracing::warn!(
|
|
"Did not expect more than 1 port in address {}. Please consider reporting this issue.",
|
|
multi
|
|
);
|
|
Some(address_string)
|
|
}
|
|
Ordering::Less => Some(address_string),
|
|
Ordering::Equal => Some(format!(
|
|
"{}:{}",
|
|
address_string,
|
|
port.get(0)
|
|
.expect("Already verified the length of the vec.")
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Connect to the SOCKS5 proxy socket.
|
|
async fn connect_to_socks_proxy<'a>(
|
|
dest: impl IntoTargetAddr<'a>,
|
|
port: u16,
|
|
) -> Result<TcpStream, tokio_socks::Error> {
|
|
let sock = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port));
|
|
let stream = Socks5Stream::connect(sock, dest).await?;
|
|
Ok(TcpStream(stream.into_inner()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test {
|
|
use crate::network::tor_transport::to_address_string;
|
|
|
|
#[test]
|
|
fn test_tor_address_string() {
|
|
let address =
|
|
"/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9"
|
|
;
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a multi formatted address.");
|
|
assert_eq!(
|
|
address_string,
|
|
"oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn tcp_to_address_string_should_be_some() {
|
|
let address = "/ip4/127.0.0.1/tcp/7777";
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. ");
|
|
assert_eq!(address_string, "127.0.0.1:7777");
|
|
}
|
|
|
|
#[test]
|
|
fn ip6_to_address_string_should_be_some() {
|
|
let address = "/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777";
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. ");
|
|
assert_eq!(address_string, "2001:db8:85a3:8d3:1319:8a2e:370:7348:7777");
|
|
}
|
|
|
|
#[test]
|
|
fn udp_to_address_string_should_be_some() {
|
|
let address = "/ip4/127.0.0.1/udp/7777";
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. ");
|
|
assert_eq!(address_string, "127.0.0.1:7777");
|
|
}
|
|
|
|
#[test]
|
|
fn ws_to_address_string_should_be_some() {
|
|
let address = "/ip4/127.0.0.1/tcp/7777/ws";
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. ");
|
|
assert_eq!(address_string, "127.0.0.1:7777");
|
|
}
|
|
|
|
#[test]
|
|
fn dns4_to_address_string_should_be_some() {
|
|
let address = "/dns4/randomdomain.com/tcp/7777";
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. ");
|
|
assert_eq!(address_string, "randomdomain.com:7777");
|
|
}
|
|
|
|
#[test]
|
|
fn dns_to_address_string_should_be_some() {
|
|
let address = "/dns/randomdomain.com/tcp/7777";
|
|
let address_string =
|
|
to_address_string(address.parse().unwrap()).expect("To be a formatted multi address. ");
|
|
assert_eq!(address_string, "randomdomain.com:7777");
|
|
}
|
|
|
|
#[test]
|
|
fn dnsaddr_to_address_string_should_be_none() {
|
|
let address = "/dnsaddr/randomdomain.com";
|
|
let address_string = to_address_string(address.parse().unwrap());
|
|
assert_eq!(address_string, None);
|
|
}
|
|
}
|