Compare commits

...

17 Commits

Author SHA1 Message Date
Daniel Karzel f892efc0ac
generate onion address
3 years ago
Daniel Karzel 7a3054267b
change a 7 to a 6
3 years ago
Thomas Eizinger 56285883e8
Change onion identity
3 years ago
Thomas Eizinger 04d7508f4e
Use plain tcp config
3 years ago
Thomas Eizinger 140ab200fc
Debugging ...
3 years ago
Thomas Eizinger 89b6131252
Replace tor::Client with extension trait
3 years ago
Thomas Eizinger 83c4be1657
Move Tor running check out of client
3 years ago
Thomas Eizinger 085848d646
Formatting and debugging changes
3 years ago
Thomas Eizinger 0a69eb5866
Make sure to validate that we have the key for the given address
3 years ago
Daniel Karzel 6b72108681
Debug for expired addr
3 years ago
Daniel Karzel 0c057e2e5e
change ipaddr to rule that out
3 years ago
Daniel Karzel b51c7bcd36
Fixed identity and check for Tor availability
3 years ago
Daniel Karzel 6fd36dbeb5
same port to rule that out
3 years ago
Daniel Karzel dede7b38f9
mplex
3 years ago
Daniel Karzel 10a5bf05ca
remove things that make problems with compile on raspi
3 years ago
Daniel Karzel 52bdaf2c1d
WIP - figure out what is not working
3 years ago
Thomas Eizinger 44dddcd6bc
WIP: libp2p-tor
3 years ago

@ -1,2 +1,5 @@
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

222
Cargo.lock generated

@ -8,6 +8,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aead"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "aead"
version = "0.4.1"
@ -17,6 +26,17 @@ dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "aes"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2bc6d3f370b5666245ff421e231cba4353df936e26986d2918e61a8fd6aef6"
dependencies = [
"aes-soft",
"aesni",
"block-cipher",
]
[[package]]
name = "aes"
version = "0.7.2"
@ -29,20 +49,54 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "aes-gcm"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0301c9e9c443494d970a07885e8cf3e587bae8356a1d5abd0999068413f7205f"
dependencies = [
"aead 0.3.2",
"aes 0.5.0",
"block-cipher",
"ghash 0.3.1",
"subtle 2.4.0",
]
[[package]]
name = "aes-gcm"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee2263805ba4537ccbb19db28525a7b1ebc7284c228eb5634c3124ca63eb03f"
dependencies = [
"aead",
"aes",
"aead 0.4.1",
"aes 0.7.2",
"cipher",
"ctr",
"ghash",
"ghash 0.4.1",
"subtle 2.4.0",
]
[[package]]
name = "aes-soft"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63dd91889c49327ad7ef3b500fd1109dbd3c509a03db0d4a9ce413b79f575cb6"
dependencies = [
"block-cipher",
"byteorder",
"opaque-debug 0.3.0",
]
[[package]]
name = "aesni"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6fe808308bb07d393e2ea47780043ec47683fcf19cf5efc8ca51c50cc8c68a"
dependencies = [
"block-cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "ahash"
version = "0.4.7"
@ -436,6 +490,15 @@ dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "block-cipher"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "block-padding"
version = "0.1.5"
@ -549,6 +612,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "244fbce0d47e97e8ef2f63b81d5e05882cb518c68531eb33194990d7b7e85845"
dependencies = [
"stream-cipher",
"zeroize",
]
[[package]]
name = "chacha20"
version = "0.7.1"
@ -561,16 +634,29 @@ dependencies = [
"zeroize",
]
[[package]]
name = "chacha20poly1305"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bf18d374d66df0c05cdddd528a7db98f78c28e2519b120855c4f84c5027b1f5"
dependencies = [
"aead 0.3.2",
"chacha20 0.5.0",
"poly1305 0.6.2",
"stream-cipher",
"zeroize",
]
[[package]]
name = "chacha20poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5"
dependencies = [
"aead",
"chacha20",
"aead 0.4.1",
"chacha20 0.7.1",
"cipher",
"poly1305",
"poly1305 0.7.0",
"zeroize",
]
@ -701,6 +787,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "cpuid-bool"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -1302,6 +1394,16 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "ghash"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
dependencies = [
"opaque-debug 0.3.0",
"polyval 0.4.5",
]
[[package]]
name = "ghash"
version = "0.4.1"
@ -1309,7 +1411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6fb2a26dd2ebd268a68bc8e9acc9e67e487952f33384055a1cbe697514c64e"
dependencies = [
"opaque-debug 0.3.0",
"polyval",
"polyval 0.5.0",
]
[[package]]
@ -1779,8 +1881,13 @@ dependencies = [
"futures",
"lazy_static",
"libp2p-core",
"libp2p-mplex",
"libp2p-noise 0.30.0",
"libp2p-ping",
"libp2p-swarm",
"libp2p-swarm-derive",
"libp2p-tcp",
"libp2p-yamux",
"parity-multiaddr",
"parking_lot 0.11.1",
"pin-project 1.0.5",
@ -1801,7 +1908,7 @@ dependencies = [
"libp2p-core",
"libp2p-dns",
"libp2p-mplex",
"libp2p-noise",
"libp2p-noise 0.31.0",
"libp2p-ping",
"libp2p-request-response",
"libp2p-swarm",
@ -1890,6 +1997,28 @@ dependencies = [
"unsigned-varint 0.7.0",
]
[[package]]
name = "libp2p-noise"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36db0f0db3b0433f5b9463f1c0cd9eadc0a3734a9170439ce501ff99733a88bd"
dependencies = [
"bytes 1.0.1",
"curve25519-dalek",
"futures",
"lazy_static",
"libp2p-core",
"log 0.4.14",
"prost",
"prost-build",
"rand 0.7.3",
"sha2 0.9.5",
"snow 0.7.2",
"static_assertions",
"x25519-dalek",
"zeroize",
]
[[package]]
name = "libp2p-noise"
version = "0.31.0"
@ -1906,7 +2035,7 @@ dependencies = [
"prost-build",
"rand 0.8.3",
"sha2 0.9.5",
"snow",
"snow 0.8.0",
"static_assertions",
"x25519-dalek",
"zeroize",
@ -1990,6 +2119,26 @@ dependencies = [
"tokio",
]
[[package]]
name = "libp2p-tor"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"data-encoding",
"futures",
"libp2p 0.37.1",
"rand 0.8.3",
"reqwest",
"tempfile",
"testcontainers 0.12.0",
"tokio",
"tokio-socks",
"torut",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "libp2p-websocket"
version = "0.29.0"
@ -2674,6 +2823,16 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "poly1305"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b7456bc1ad2d4cf82b3a016be4c2ac48daf11bf990c1603ebd447fe6f30fca8"
dependencies = [
"cpuid-bool 0.2.0",
"universal-hash",
]
[[package]]
name = "poly1305"
version = "0.7.0"
@ -2685,6 +2844,17 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "polyval"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
dependencies = [
"cpuid-bool 0.2.0",
"opaque-debug 0.3.0",
"universal-hash",
]
[[package]]
name = "polyval"
version = "0.5.0"
@ -3589,7 +3759,7 @@ checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpuid-bool",
"cpuid-bool 0.1.2",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
@ -3703,15 +3873,33 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "snow"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "795dd7aeeee24468e5a32661f6d27f7b5cbed802031b2d7640c7b10f8fb2dd50"
dependencies = [
"aes-gcm 0.7.0",
"blake2",
"chacha20poly1305 0.6.0",
"rand 0.7.3",
"rand_core 0.5.1",
"ring",
"rustc_version 0.2.3",
"sha2 0.9.5",
"subtle 2.4.0",
"x25519-dalek",
]
[[package]]
name = "snow"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7"
dependencies = [
"aes-gcm",
"aes-gcm 0.9.1",
"blake2",
"chacha20poly1305",
"chacha20poly1305 0.8.0",
"rand 0.8.3",
"rand_core 0.6.2",
"ring",
@ -3849,6 +4037,16 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "stream-cipher"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c80e15f898d8d8f25db24c253ea615cc14acf418ff307822995814e7d42cfa89"
dependencies = [
"block-cipher",
"generic-array 0.14.4",
]
[[package]]
name = "strsim"
version = "0.8.0"

@ -1,5 +1,5 @@
[workspace]
members = [ "monero-harness", "monero-rpc", "swap", "monero-wallet" ]
members = [ "monero-harness", "monero-rpc", "swap", "monero-wallet", "libp2p-tor" ]
[patch.crates-io]
monero = { git = "https://github.com/comit-network/monero-rs", rev = "818f38b" }

@ -0,0 +1,28 @@
[package]
name = "libp2p-tor"
version = "0.1.0"
authors = [ "Thomas Eizinger <thomas@eizinger.io>" ]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1" # TODO: Get rid of anyhow dependency
async-trait = "0.1"
data-encoding = "2.3"
futures = "0.3"
libp2p = { version = "0.37", default-features = false, features = [ "tcp-tokio" ] }
rand = { version = "0.8", optional = true }
reqwest = { version = "0.11", features = [ "socks", "rustls-tls" ], default-features = false }
tokio = { version = "1", features = [ "sync" ] }
tokio-socks = "0.5"
torut = { version = "0.1", default-features = false, features = [ "v3", "control" ] }
tracing = "0.1"
[dev-dependencies]
libp2p = { version = "0.37", default-features = false, features = [ "yamux", "noise", "ping", "mplex" ] }
rand = "0.8"
reqwest = { version = "0.11", features = [ "rustls-tls", "stream", "socks" ], default-features = false }
tempfile = "3"
testcontainers = "0.12"
tokio = { version = "1", features = [ "full" ] }
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "chrono", "tracing-log" ] }

@ -0,0 +1,102 @@
use anyhow::{anyhow, bail, Result};
use libp2p::core::muxing::StreamMuxerBox;
use libp2p::core::upgrade::{SelectUpgrade, Version};
use libp2p::mplex::MplexConfig;
use libp2p::ping::{Ping, PingEvent, PingSuccess};
use libp2p::swarm::{SwarmBuilder, SwarmEvent};
use libp2p::{identity, noise, yamux, Multiaddr, Swarm, Transport};
use libp2p_tor::dial_only;
use std::time::Duration;
use tracing_subscriber::util::SubscriberInitExt;
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
let _guard = tracing_subscriber::fmt()
.with_env_filter("debug,libp2p_tor=debug") // add `reqwest::connect::verbose=trace` if you want to logs of the RPC clients
.with_test_writer()
.set_default();
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050")
.map_err(|_| anyhow!("tor proxy should be there"))?;
let client = reqwest::Client::builder().proxy(proxy).build()?;
let res = client.get("https://check.torproject.org").send().await?;
let text = res.text().await?;
if !text.contains("Congratulations. This browser is configured to use Tor.") {
bail!("Tor is currently not running")
}
let addr_to_dial = "/onion3/jpclybnowuibjexya3qggzvzkoeruuav4nyjlxpnkrosldsvykfbn6qd:7654/p2p/12D3KooWHKqGyK4hVtf5BQY8GpbY6fSGKDZ8eBXMQ3H2RsdnKVzC"
.parse::<Multiaddr>()
.unwrap();
let mut swarm = new_swarm();
println!("Peer-ID: {}", swarm.local_peer_id());
println!("Dialing {}", addr_to_dial);
swarm.dial_addr(addr_to_dial).unwrap();
loop {
match swarm.next_event().await {
SwarmEvent::ConnectionEstablished {
peer_id, endpoint, ..
} => {
println!(
"Connected to {} via {}",
peer_id,
endpoint.get_remote_address()
);
}
SwarmEvent::Behaviour(PingEvent { result, peer }) => match result {
Ok(PingSuccess::Pong) => {
println!("Got pong from {}", peer);
}
Ok(PingSuccess::Ping { rtt }) => {
println!("Pinged {} with rtt of {}s", peer, rtt.as_secs());
}
Err(failure) => {
println!("Failed to ping {}: {}", peer, failure)
}
},
event => {
println!("Swarm event: {:?}", event)
}
}
}
}
/// Builds a new swarm that is capable of dialling onion address.
fn new_swarm() -> Swarm<Ping> {
let identity = identity::Keypair::generate_ed25519();
let peer_id = identity.public().into_peer_id();
println!("peer id upon swarm setup: {}", peer_id);
SwarmBuilder::new(
dial_only::TorConfig::new(9050)
.upgrade(Version::V1)
.authenticate(
noise::NoiseConfig::xx(
noise::Keypair::<noise::X25519Spec>::new()
.into_authentic(&identity)
.unwrap(),
)
.into_authenticated(),
)
.multiplex(SelectUpgrade::new(
yamux::YamuxConfig::default(),
MplexConfig::new(),
))
.timeout(Duration::from_secs(20))
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
.boxed(),
Ping::default(),
peer_id,
)
.executor(Box::new(|f| {
tokio::spawn(f);
}))
.build()
}

@ -0,0 +1,146 @@
use libp2p::core::muxing::StreamMuxerBox;
use libp2p::core::upgrade::{SelectUpgrade, Version};
use libp2p::identity::Keypair;
use libp2p::mplex::MplexConfig;
use libp2p::ping::{Ping, PingEvent, PingSuccess};
use libp2p::swarm::{SwarmBuilder, SwarmEvent};
use libp2p::{identity, noise, yamux, Multiaddr, Swarm, Transport};
use libp2p_tor::duplex;
use libp2p_tor::torut_ext::AuthenticatedConnectionExt;
use noise::NoiseConfig;
use std::str::FromStr;
use std::time::Duration;
use torut::control::AuthenticatedConn;
use torut::onion::TorSecretKeyV3;
use tracing_subscriber::util::SubscriberInitExt;
use libp2p_tor::duplex::TorutAsyncEventHandler;
use libp2p::tcp::TokioTcpConfig;
#[tokio::main(flavor = "multi_thread")]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter("trace") // add `reqwest::connect::verbose=trace` if you want to logs of the RPC clients
.init();
let key = fixed_onion_identity();
let onion_address = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
tracing::info!("{}", onion_address);
let onion_port = 7654;
let mut client = AuthenticatedConn::new(9051).await.unwrap();
client
.add_ephemeral_service(&key, onion_port, onion_port)
.await
.unwrap();
let mut swarm = new_swarm(client, key).await;
let peer_id = *swarm.local_peer_id();
tracing::info!("Peer-ID: {}", peer_id);
// TODO: Figure out what to with the port, we could also set it to 0 and then
// imply it from the assigned port swarm.listen_on(Multiaddr::
// from_str(format!("/onion3/{}:{}", onion_address,
// onion_port).as_str()).unwrap()).unwrap();
// swarm
// .listen_on(
// Multiaddr::from_str(format!("/onion3/{}:{}", onion_address, onion_port).as_str()).unwrap(),
// )
// .unwrap();
swarm
.listen_on(
Multiaddr::from_str(format!("/ip4/127.0.0.1/tcp/{}", onion_port).as_str()).unwrap(),
)
.unwrap();
loop {
match swarm.next_event().await {
SwarmEvent::NewListenAddr(addr) => {
tracing::info!("Listening on {}", addr);
tracing::info!("Connection string: {}/p2p/{}", addr, peer_id);
}
SwarmEvent::ConnectionEstablished {
peer_id, endpoint, ..
} => {
tracing::info!(
"Connected to {} via {}",
peer_id,
endpoint.get_remote_address()
);
}
SwarmEvent::Behaviour(PingEvent { result, peer }) => match result {
Ok(PingSuccess::Pong) => {
tracing::info!("Got pong from {}", peer);
}
Ok(PingSuccess::Ping { rtt }) => {
tracing::info!("Pinged {} with rtt of {}s", peer, rtt.as_secs());
}
Err(failure) => {
tracing::info!("Failed to ping {}: {}", peer, failure)
}
},
event => {
tracing::debug!("Swarm event: {:?}", event)
}
}
}
}
/// Builds a new swarm that is capable of listening and dialling on the Tor
/// network.
///
/// In particular, this swarm can create ephemeral hidden services on the
/// configured Tor node.
async fn new_swarm(client: AuthenticatedConn<tokio::net::TcpStream, TorutAsyncEventHandler>, key: TorSecretKeyV3) -> Swarm<Ping> {
let identity = fixed_libp2p_identity();
SwarmBuilder::new(
TokioTcpConfig::new().nodelay(true)
.boxed()
.upgrade(Version::V1)
.authenticate(
NoiseConfig::xx(
noise::Keypair::<noise::X25519Spec>::new()
.into_authentic(&identity)
.unwrap(),
)
.into_authenticated(),
)
.multiplex(SelectUpgrade::new(
yamux::YamuxConfig::default(),
MplexConfig::new(),
))
.timeout(Duration::from_secs(20))
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
.boxed(),
Ping::default(),
identity.public().into_peer_id(),
)
.executor(Box::new(|f| {
tokio::spawn(f);
}))
.build()
}
fn fixed_onion_identity() -> TorSecretKeyV3 {
TorSecretKeyV3::generate()
}
fn fixed_libp2p_identity() -> Keypair {
// randomly venerated bytes, corresponding peer-id:
// 12D3KooWHKqGyK4hVtf5BQY8GpbY6fSGKDZ8eBXMQ3H2RsdnKVzC
let fixed_identity = [
75, 146, 26, 107, 50, 252, 71, 2, 238, 224, 92, 112, 216, 238, 131, 57, 84, 9, 218, 120,
195, 9, 129, 102, 42, 206, 165, 102, 32, 238, 158, 248,
];
let key =
identity::ed25519::SecretKey::from_bytes(fixed_identity).expect("we always pass 32 bytes");
identity::Keypair::Ed25519(key.into())
}

@ -0,0 +1,57 @@
use crate::torut_ext::AuthenticatedConnectionExt;
use crate::{fmt_as_tor_compatible_address, Error};
use anyhow::Result;
use fmt_as_tor_compatible_address::fmt_as_tor_compatible_address;
use futures::future::BoxFuture;
use futures::prelude::*;
use libp2p::core::multiaddr::Multiaddr;
use libp2p::core::transport::{ListenerEvent, TransportError};
use libp2p::core::Transport;
use libp2p::futures::stream::BoxStream;
use libp2p::tcp::tokio::TcpStream;
use torut::control::AuthenticatedConn;
#[derive(Clone)]
pub struct TorConfig {
socks_port: u16,
}
impl TorConfig {
pub fn new(socks_port: u16) -> Self {
Self { socks_port }
}
pub async fn from_control_port(control_port: u16) -> Result<Self, Error> {
let mut client = AuthenticatedConn::new(control_port).await?;
let socks_port = client.get_socks_port().await?;
Ok(Self::new(socks_port))
}
}
impl Transport for TorConfig {
type Output = TcpStream;
type Error = Error;
#[allow(clippy::type_complexity)]
type Listener =
BoxStream<'static, Result<ListenerEvent<Self::ListenerUpgrade, Self::Error>, Self::Error>>;
type ListenerUpgrade = BoxFuture<'static, Result<Self::Output, Self::Error>>;
type Dial = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn listen_on(self, addr: Multiaddr) -> Result<Self::Listener, TransportError<Self::Error>> {
Err(TransportError::MultiaddrNotSupported(addr))
}
fn dial(self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
tracing::debug!("Connecting through Tor proxy to address {}", addr);
let address = fmt_as_tor_compatible_address(addr.clone())
.ok_or(TransportError::MultiaddrNotSupported(addr))?;
Ok(crate::dial_via_tor(address, self.socks_port).boxed())
}
fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option<Multiaddr> {
None // address translation for tor doesn't make any sense :)
}
}

@ -0,0 +1,163 @@
use crate::torut_ext::AuthenticatedConnectionExt;
use crate::{fmt_as_tor_compatible_address, torut_ext, Error};
use fmt_as_tor_compatible_address::fmt_as_tor_compatible_address;
use futures::future::BoxFuture;
use futures::prelude::*;
use libp2p::core::multiaddr::{Multiaddr, Protocol};
use libp2p::core::transport::map_err::MapErr;
use libp2p::core::transport::{ListenerEvent, TransportError};
use libp2p::core::Transport;
use libp2p::futures::stream::BoxStream;
use libp2p::futures::{StreamExt, TryStreamExt};
use libp2p::tcp::{GenTcpConfig, TokioTcpConfig};
use std::sync::Arc;
use tokio::sync::Mutex;
use torut::control::{AsyncEvent, AuthenticatedConn};
use torut::onion::TorSecretKeyV3;
pub type TorutAsyncEventHandler =
fn(
AsyncEvent<'_>,
) -> Box<dyn Future<Output = Result<(), torut::control::ConnError>> + Unpin + Send>;
#[derive(Clone)]
pub struct TorConfig {
inner: MapErr<GenTcpConfig<libp2p::tcp::tokio::Tcp>, fn(std::io::Error) -> Error>, /* TODO: Make generic over async-std / tokio */
tor_client: Arc<Mutex<AuthenticatedConn<tokio::net::TcpStream, TorutAsyncEventHandler>>>,
key: TorSecretKeyV3,
socks_port: u16,
}
impl TorConfig {
pub async fn new(
mut client: AuthenticatedConn<tokio::net::TcpStream, TorutAsyncEventHandler>,
key: TorSecretKeyV3,
) -> Result<Self, Error> {
let socks_port = client.get_socks_port().await?;
Ok(Self {
inner: TokioTcpConfig::new().nodelay(true).map_err(Error::InnerTransport),
tor_client: Arc::new(Mutex::new(client)),
key,
socks_port,
})
}
pub async fn from_control_port(control_port: u16, key: TorSecretKeyV3) -> Result<Self, Error> {
let client = AuthenticatedConn::new(control_port).await?;
Self::new(client, key).await
}
}
impl Transport for TorConfig {
type Output = libp2p::tcp::tokio::TcpStream;
type Error = Error;
#[allow(clippy::type_complexity)]
type Listener =
BoxStream<'static, Result<ListenerEvent<Self::ListenerUpgrade, Self::Error>, Self::Error>>;
type ListenerUpgrade = BoxFuture<'static, Result<Self::Output, Self::Error>>;
type Dial = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn listen_on(self, addr: Multiaddr) -> Result<Self::Listener, TransportError<Self::Error>> {
let mut protocols = addr.iter();
let onion = if let Protocol::Onion3(onion) = protocols
.next()
.ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?
{
onion
} else {
return Err(TransportError::MultiaddrNotSupported(addr));
};
let key: TorSecretKeyV3 = self.key;
let onion_bytes = key.public().get_onion_address().get_raw_bytes();
let onion_port = onion.port();
if onion.hash() != &onion_bytes {
return Err(TransportError::MultiaddrNotSupported(addr));
}
let localhost_tcp_random_port_addr = format!("/ip4/127.0.0.1/tcp/{}", onion_port)
.as_str()
.parse()
.expect("always a valid multiaddr");
let listener = self.inner.listen_on(localhost_tcp_random_port_addr)?;
let tor_client = self.tor_client.clone();
let listener = listener
.and_then({
move |event| {
let tor_client = tor_client.clone();
let key = key.clone();
let onion_multiaddress =
Multiaddr::empty().with(Protocol::Onion3((onion_bytes, onion_port).into()));
async move {
Ok(match event {
ListenerEvent::NewAddress(address) => {
ListenerEvent::NewAddress(onion_multiaddress.clone())
}
ListenerEvent::Upgrade {
upgrade,
local_addr,
remote_addr,
} => ListenerEvent::Upgrade {
upgrade: upgrade.boxed(),
local_addr,
remote_addr,
},
// TODO: why was the constructed multiaddr used here?
ListenerEvent::AddressExpired(adr) => {
// TODO: even if so, why would we ignore it? Far more logical to
// just use it... can ignore address
// because we only ever listened on one and we
// know which one that was
let onion_address_without_dot_onion = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
tracing::debug!(
"Listening expired, removing onion {}",
onion_address_without_dot_onion
);
match tor_client
.lock()
.await
.del_onion(&onion_address_without_dot_onion)
.await
{
Ok(()) => ListenerEvent::AddressExpired(adr),
Err(e) => ListenerEvent::Error(Error::Torut(
torut_ext::Error::Connection(e),
)),
}
}
ListenerEvent::Error(e) => ListenerEvent::Error(e),
})
}
}
})
.boxed();
Ok(listener)
}
fn dial(self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
tracing::debug!("Connecting through Tor proxy to address {}", addr);
let address = fmt_as_tor_compatible_address(addr.clone())
.ok_or(TransportError::MultiaddrNotSupported(addr))?;
Ok(crate::dial_via_tor(address, self.socks_port).boxed())
}
fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option<Multiaddr> {
self.inner.address_translation(listen, observed)
}
}

@ -0,0 +1,60 @@
use data_encoding::BASE32;
use libp2p::multiaddr::Protocol;
use libp2p::Multiaddr;
/// 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.
pub fn fmt_as_tor_compatible_address(multi: Multiaddr) -> Option<String> {
let mut protocols = multi.iter();
let address_string = match protocols.next()? {
// if it is an Onion address, we have all we need and can return
Protocol::Onion3(addr) => {
return Some(format!(
"{}.onion:{}",
BASE32.encode(addr.hash()).to_lowercase(),
addr.port()
));
}
// Deal with non-onion addresses
Protocol::Ip4(addr) => format!("{}", addr),
Protocol::Ip6(addr) => format!("{}", addr),
Protocol::Dns(addr) => format!("{}", addr),
Protocol::Dns4(addr) => format!("{}", addr),
_ => return None,
};
let port = match protocols.next()? {
Protocol::Tcp(port) => port,
Protocol::Udp(port) => port,
_ => return None,
};
Some(format!("{}:{}", address_string, port))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_fmt_as_tor_compatible_address() {
let test_cases = &[
("/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9", Some("oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024")),
("/ip4/127.0.0.1/tcp/7777", Some("127.0.0.1:7777")),
("/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777", Some("2001:db8:85a3:8d3:1319:8a2e:370:7348:7777")),
("/ip4/127.0.0.1/udp/7777", Some("127.0.0.1:7777")),
("/ip4/127.0.0.1/tcp/7777/ws", Some("127.0.0.1:7777")),
("/dns4/randomdomain.com/tcp/7777", Some("randomdomain.com:7777")),
("/dns/randomdomain.com/tcp/7777", Some("randomdomain.com:7777")),
("/dnsaddr/randomdomain.com", None),
];
for (multiaddress, expected_address) in test_cases {
let actual_address =
fmt_as_tor_compatible_address(multiaddress.parse().expect("a valid multi-address"));
assert_eq!(&actual_address.as_deref(), expected_address)
}
}
}

@ -0,0 +1,42 @@
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::{fmt, io};
use libp2p::tcp::tokio::TcpStream;
use tokio_socks::tcp::Socks5Stream;
pub mod dial_only;
pub mod duplex;
mod fmt_as_tor_compatible_address;
pub mod torut_ext;
async fn dial_via_tor(onion_address: String, socks_port: u16) -> anyhow::Result<TcpStream, Error> {
let sock = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, socks_port));
let stream = Socks5Stream::connect(sock, dbg!(onion_address))
.await
.map_err(Error::UnreachableProxy)?;
let stream = TcpStream(stream.into_inner());
Ok(stream)
}
#[derive(Debug)]
pub enum Error {
OnlyWildcardAllowed,
Torut(torut_ext::Error),
UnreachableProxy(tokio_socks::Error),
InnerTransport(io::Error),
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
todo!()
}
}
impl From<torut_ext::Error> for Error {
fn from(e: torut_ext::Error) -> Self {
Error::Torut(e)
}
}

@ -0,0 +1,121 @@
use std::borrow::Cow;
use std::future::Future;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::num::ParseIntError;
use std::{io, iter};
use torut::control::{AsyncEvent, AuthenticatedConn, TorAuthData, UnauthenticatedConn};
use torut::onion::TorSecretKeyV3;
pub type AsyncEventHandler =
fn(
AsyncEvent<'_>,
) -> Box<dyn Future<Output = Result<(), torut::control::ConnError>> + Unpin + Send>;
#[derive(Debug)]
pub enum Error {
FailedToConnect(io::Error),
NoAuthData(Option<io::Error>),
Connection(torut::control::ConnError),
FailedToAddHiddenService(torut::control::ConnError),
FailedToParsePort(ParseIntError),
}
impl From<torut::control::ConnError> for Error {
fn from(e: torut::control::ConnError) -> Self {
Error::Connection(e)
}
}
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Self {
Error::FailedToParsePort(e)
}
}
#[async_trait::async_trait]
pub trait AuthenticatedConnectionExt: Sized {
async fn new(control_port: u16) -> Result<Self, Error>;
async fn with_password(control_port: u16, password: &str) -> Result<Self, Error>;
async fn add_ephemeral_service(
&mut self,
key: &TorSecretKeyV3,
onion_port: u16,
local_port: u16,
) -> Result<(), Error>;
async fn get_socks_port(&mut self) -> Result<u16, Error>;
}
#[async_trait::async_trait]
impl AuthenticatedConnectionExt for AuthenticatedConn<tokio::net::TcpStream, AsyncEventHandler> {
async fn new(control_port: u16) -> Result<Self, Error> {
let stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", control_port))
.await
.map_err(Error::FailedToConnect)?;
let mut uac = UnauthenticatedConn::new(stream);
let tor_info = uac.load_protocol_info().await?;
let tor_auth_data = tor_info
.make_auth_data()
.map_err(|e| Error::NoAuthData(Some(e)))?
.ok_or(Error::NoAuthData(None))?;
uac.authenticate(&tor_auth_data).await?;
Ok(uac.into_authenticated().await)
}
async fn with_password(control_port: u16, password: &str) -> Result<Self, Error> {
let stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", control_port))
.await
.map_err(Error::FailedToConnect)?;
let mut uac = UnauthenticatedConn::new(stream);
uac.authenticate(&TorAuthData::HashedPassword(Cow::Borrowed(password)))
.await?;
Ok(uac.into_authenticated().await)
}
async fn add_ephemeral_service(
&mut self,
key: &TorSecretKeyV3,
onion_port: u16,
local_port: u16,
) -> Result<(), Error> {
tracing::debug!(
"Attempting to add ephemeral service, onion port {}, local port {}",
onion_port, local_port
);
self.add_onion_v3(
&key,
false,
false,
false,
None,
&mut iter::once(&(
onion_port,
SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), local_port),
)),
)
.await
.map_err(Error::FailedToAddHiddenService)
}
async fn get_socks_port(&mut self) -> Result<u16, Error> {
const DEFAULT_SOCKS_PORT: u16 = 9050;
let mut vec = self
.get_conf("SocksPort")
.await
.map_err(Error::Connection)?;
let first_element = vec
.pop()
.expect("exactly one element because we requested one config option");
let port = first_element.map_or(Ok(DEFAULT_SOCKS_PORT), |port| port.parse())?; // if config is empty, we are listing on the default port
Ok(port)
}
}

@ -0,0 +1,13 @@
# set alpine as the base image of the Dockerfile
FROM alpine:latest
# update the package repository and install Tor
RUN apk update && apk add tor
# Set `tor` as the default user during the container runtime
USER tor
EXPOSE 9050
EXPOSE 9051
# Set `tor` as the entrypoint for the image
ENTRYPOINT ["tor"]

@ -14,11 +14,9 @@
use anyhow::{bail, Context, Result};
use libp2p::core::multiaddr::Protocol;
use libp2p::core::Multiaddr;
use libp2p::Swarm;
use prettytable::{row, Table};
use std::env;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use structopt::clap;
use structopt::clap::ErrorKind;
@ -33,8 +31,9 @@ use swap::protocol::alice;
use swap::protocol::alice::event_loop::KrakenRate;
use swap::protocol::alice::{redeem, run, EventLoop};
use swap::seed::Seed;
use swap::tor::AuthenticatedClient;
use swap::torut_ext::AuthenticatedConnectionExt;
use swap::{asb, bitcoin, kraken, monero, tor};
use torut::control::AuthenticatedConn;
use tracing::{debug, info, warn};
use tracing_subscriber::filter::LevelFilter;
@ -127,18 +126,38 @@ async fn main() -> Result<()> {
let kraken_price_updates = kraken::connect()?;
let local_listening_tcp_port = config.network.listen.iter().find_map(|address| {
address.iter().find_map(|protocol| match protocol {
Protocol::Tcp(port) => Some(port),
_ => None,
})
});
// setup Tor hidden services
let tor_client =
tor::Client::new(config.tor.socks5_port).with_control_port(config.tor.control_port);
let _ac = match tor_client.assert_tor_running().await {
Ok(_) => {
let _ac = match (
tor::is_daemon_running_on_port(config.tor.socks5_port).await,
local_listening_tcp_port,
) {
(Ok(_), Some(port)) => {
tracing::info!("Tor found. Setting up hidden service");
let ac =
register_tor_services(config.network.clone().listen, tor_client, &seed)
.await?;
Some(ac)
let mut connection = AuthenticatedConn::new(config.tor.control_port).await?;
let tor_secret_key = seed.derive_torv3_key();
connection
.add_ephemeral_service(&tor_secret_key, port, port)
.await?;
let onion_address = tor_secret_key.public().get_onion_address();
tracing::info!(%onion_address);
Some(connection)
}
(Ok(_), None) => {
tracing::warn!("Tor running but no local listening addresses available, unable to set up hidden service");
None
}
Err(_) => {
(Err(_), _) => {
tracing::warn!("Tor not found. Running on clear net");
None
}
@ -333,46 +352,3 @@ async fn init_monero_wallet(
Ok(wallet)
}
/// Registers a hidden service for each network.
/// Note: Once ac goes out of scope, the services will be de-registered.
async fn register_tor_services(
networks: Vec<Multiaddr>,
tor_client: tor::Client,
seed: &Seed,
) -> Result<AuthenticatedClient> {
let mut ac = tor_client.into_authenticated_client().await?;
let hidden_services_details = networks
.iter()
.flat_map(|network| {
network.iter().map(|protocol| match protocol {
Protocol::Tcp(port) => Some((
port,
SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), port),
)),
_ => {
// We only care for Tcp for now.
None
}
})
})
.flatten()
.collect::<Vec<_>>();
let key = seed.derive_torv3_key();
ac.add_services(&hidden_services_details, &key).await?;
let onion_address = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
hidden_services_details.iter().for_each(|(port, _)| {
let onion_address = format!("/onion3/{}:{}", onion_address, port);
tracing::info!(%onion_address);
});
Ok(ac)
}

@ -28,5 +28,6 @@ pub mod network;
pub mod protocol;
pub mod seed;
pub mod tor;
pub mod torut_ext;
mod monero_ext;

@ -49,7 +49,7 @@ pub async fn cli(
alice: PeerId,
tor_socks5_port: u16,
) -> Result<Swarm<bob::Behaviour>> {
let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await {
let maybe_tor_socks5_port = match tor::is_daemon_running_on_port(tor_socks5_port).await {
Ok(()) => Some(tor_socks5_port),
Err(_) => None,
};

@ -1,128 +1,21 @@
use anyhow::{anyhow, bail, Context, Result};
use std::future::Future;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use tokio::net::TcpStream;
use torut::control::{AsyncEvent, AuthenticatedConn, ConnError, UnauthenticatedConn};
use torut::onion::TorSecretKeyV3;
use anyhow::{anyhow, bail, Result};
pub const DEFAULT_SOCKS5_PORT: u16 = 9050;
pub const DEFAULT_CONTROL_PORT: u16 = 9051;
#[derive(Debug, Clone, Copy)]
pub struct Client {
socks5_address: SocketAddrV4,
control_port_address: SocketAddr,
}
/// Check if Tor daemon is running on the given port.
pub async fn is_daemon_running_on_port(port: u16) -> Result<()> {
// Make sure you are running tor and this is your socks port
let proxy = reqwest::Proxy::all(format!("socks5h://127.0.0.1:{}", port).as_str())
.map_err(|_| anyhow!("tor proxy should be there"))?;
let client = reqwest::Client::builder().proxy(proxy).build()?;
impl Default for Client {
fn default() -> Self {
Self {
socks5_address: SocketAddrV4::new(Ipv4Addr::LOCALHOST, DEFAULT_SOCKS5_PORT),
control_port_address: SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::LOCALHOST,
DEFAULT_CONTROL_PORT,
)),
}
}
}
let res = client.get("https://check.torproject.org").send().await?;
let text = res.text().await?;
impl Client {
pub fn new(socks5_port: u16) -> Self {
Self {
socks5_address: SocketAddrV4::new(Ipv4Addr::LOCALHOST, socks5_port),
control_port_address: SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::LOCALHOST,
DEFAULT_CONTROL_PORT,
)),
}
}
pub fn with_control_port(self, control_port: u16) -> Self {
Self {
control_port_address: SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::LOCALHOST,
control_port,
)),
..self
}
if !text.contains("Congratulations. This browser is configured to use Tor.") {
bail!("Tor is currently not running")
}
/// checks if tor is running
pub async fn assert_tor_running(&self) -> Result<()> {
// Make sure you are running tor and this is your socks port
let proxy = reqwest::Proxy::all(format!("socks5h://{}", self.socks5_address).as_str())
.map_err(|_| anyhow!("tor proxy should be there"))?;
let client = reqwest::Client::builder().proxy(proxy).build()?;
let res = client.get("https://check.torproject.org").send().await?;
let text = res.text().await?;
if !text.contains("Congratulations. This browser is configured to use Tor.") {
bail!("Tor is currently not running")
}
Ok(())
}
async fn init_unauthenticated_connection(&self) -> Result<UnauthenticatedConn<TcpStream>> {
// Connect to local tor service via control port
let sock = TcpStream::connect(self.control_port_address).await?;
let uc = UnauthenticatedConn::new(sock);
Ok(uc)
}
/// Create a new authenticated connection to your local Tor service
pub async fn into_authenticated_client(self) -> Result<AuthenticatedClient> {
self.assert_tor_running().await?;
let mut uc = self
.init_unauthenticated_connection()
.await
.map_err(|_| anyhow!("Could not connect to Tor. Tor might not be running or the control port is incorrect."))?;
let tor_info = uc
.load_protocol_info()
.await
.map_err(|_| anyhow!("Failed to load protocol info from Tor."))?;
let tor_auth_data = tor_info
.make_auth_data()?
.context("Failed to make Tor auth data.")?;
// Get an authenticated connection to the Tor via the Tor Controller protocol.
uc.authenticate(&tor_auth_data)
.await
.map_err(|_| anyhow!("Failed to authenticate with Tor"))?;
Ok(AuthenticatedClient {
inner: uc.into_authenticated().await,
})
}
pub fn tor_proxy_port(&self) -> u16 {
self.socks5_address.port()
}
}
type Handler = fn(AsyncEvent<'_>) -> Box<dyn Future<Output = Result<(), ConnError>> + Unpin>;
#[allow(missing_debug_implementations)]
pub struct AuthenticatedClient {
inner: AuthenticatedConn<TcpStream, Handler>,
}
impl AuthenticatedClient {
/// Add an ephemeral tor service on localhost with the provided key
/// `service_port` and `onion_port` can be different but don't have to as
/// they are on different networks.
pub async fn add_services(
&mut self,
services: &[(u16, SocketAddr)],
tor_key: &TorSecretKeyV3,
) -> Result<()> {
let mut listeners = services.iter();
self.inner
.add_onion_v3(tor_key, false, false, false, None, &mut listeners)
.await
.map_err(|e| anyhow!("Could not add onion service.: {:#?}", e))
}
Ok(())
}

@ -0,0 +1,121 @@
use std::borrow::Cow;
use std::future::Future;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::num::ParseIntError;
use std::{io, iter};
use torut::control::{AsyncEvent, AuthenticatedConn, TorAuthData, UnauthenticatedConn};
use torut::onion::TorSecretKeyV3;
pub type AsyncEventHandler =
fn(
AsyncEvent<'_>,
) -> Box<dyn Future<Output = Result<(), torut::control::ConnError>> + Unpin + Send>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to connect to Tor control port")]
FailedToConnect(#[source] io::Error),
#[error("Failed to read Tor auth-cookie filed")]
FailedToReadCookieFile(#[source] io::Error),
#[error("No authentication information could be found")]
NoAuthData,
#[error("Failed to communicate with Tor control port")]
Connection(torut::control::ConnError),
#[error("Failed to add hidden service")]
FailedToAddHiddenService(torut::control::ConnError),
#[error("Failed to parse port")]
FailedToParsePort(#[from] ParseIntError),
}
// TODO: Use #[from] once available: https://github.com/teawithsand/torut/issues/12
impl From<torut::control::ConnError> for Error {
fn from(e: torut::control::ConnError) -> Self {
Error::Connection(e)
}
}
#[async_trait::async_trait]
pub trait AuthenticatedConnectionExt: Sized {
async fn new(control_port: u16) -> Result<Self, Error>;
async fn with_password(control_port: u16, password: &str) -> Result<Self, Error>;
async fn add_ephemeral_service(
&mut self,
key: &TorSecretKeyV3,
onion_port: u16,
local_port: u16,
) -> Result<(), Error>;
async fn get_socks_port(&mut self) -> Result<u16, Error>;
}
#[async_trait::async_trait]
impl AuthenticatedConnectionExt for AuthenticatedConn<tokio::net::TcpStream, AsyncEventHandler> {
async fn new(control_port: u16) -> Result<Self, Error> {
let stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", control_port))
.await
.map_err(Error::FailedToConnect)?;
let mut uac = UnauthenticatedConn::new(stream);
let tor_info = uac.load_protocol_info().await?;
let tor_auth_data = tor_info
.make_auth_data()
.map_err(Error::FailedToReadCookieFile)?
.ok_or(Error::NoAuthData)?;
uac.authenticate(&tor_auth_data).await?;
Ok(uac.into_authenticated().await)
}
async fn with_password(control_port: u16, password: &str) -> Result<Self, Error> {
let stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", control_port))
.await
.map_err(Error::FailedToConnect)?;
let mut uac = UnauthenticatedConn::new(stream);
uac.authenticate(&TorAuthData::HashedPassword(Cow::Borrowed(password)))
.await?;
Ok(uac.into_authenticated().await)
}
async fn add_ephemeral_service(
&mut self,
key: &TorSecretKeyV3,
onion_port: u16,
local_port: u16,
) -> Result<(), Error> {
tracing::debug!(
"Adding ephemeral service, onion port {}, local port {}",
onion_port,
local_port
);
self.add_onion_v3(
&key,
false,
false,
false,
None,
&mut iter::once(&(
onion_port,
SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), local_port),
)),
)
.await
.map_err(Error::FailedToAddHiddenService)
}
async fn get_socks_port(&mut self) -> Result<u16, Error> {
const DEFAULT_SOCKS_PORT: u16 = 9050;
let mut vec = self.get_conf("SocksPort").await?;
let first_element = vec
.pop()
.expect("exactly one element because we requested one config option");
let port = first_element.map_or(Ok(DEFAULT_SOCKS_PORT), |port| port.parse())?; // if config is empty, we are listing on the default port
Ok(port)
}
}
Loading…
Cancel
Save