mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-13 08:45:53 -04:00
WIP: libp2p-tor
This commit is contained in:
parent
ada5acb2b5
commit
44dddcd6bc
14 changed files with 1109 additions and 13 deletions
|
@ -1,2 +1,5 @@
|
|||
[target.armv7-unknown-linux-gnueabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
|
|
221
Cargo.lock
generated
221
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,12 @@ dependencies = [
|
|||
"futures",
|
||||
"lazy_static",
|
||||
"libp2p-core",
|
||||
"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 +1907,7 @@ dependencies = [
|
|||
"libp2p-core",
|
||||
"libp2p-dns",
|
||||
"libp2p-mplex",
|
||||
"libp2p-noise",
|
||||
"libp2p-noise 0.31.0",
|
||||
"libp2p-ping",
|
||||
"libp2p-request-response",
|
||||
"libp2p-swarm",
|
||||
|
@ -1890,6 +1996,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 +2034,7 @@ dependencies = [
|
|||
"prost-build",
|
||||
"rand 0.8.3",
|
||||
"sha2 0.9.5",
|
||||
"snow",
|
||||
"snow 0.8.0",
|
||||
"static_assertions",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
|
@ -1990,6 +2118,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 +2822,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 +2843,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 +3758,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 +3872,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 +4036,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" }
|
||||
|
|
28
libp2p-tor/Cargo.toml
Normal file
28
libp2p-tor/Cargo.toml
Normal file
|
@ -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
|
||||
torut = { version = "0.1", default-features = false, features = ["v3", "control"] }
|
||||
tokio-socks = "0.5"
|
||||
libp2p = { version = "0.37", default-features = false, features = ["tcp-tokio"] }
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
futures = "0.3"
|
||||
tracing = "0.1"
|
||||
data-encoding = "2.3"
|
||||
reqwest = { version = "0.11", features = ["socks", "rustls-tls"], default-features = false }
|
||||
async-trait = "0.1"
|
||||
rand = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
rand = "0.8"
|
||||
libp2p = { version = "0.37", default-features = false, features = ["yamux", "noise", "ping"] }
|
||||
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
testcontainers = "0.12"
|
17
libp2p-tor/build.rs
Normal file
17
libp2p-tor/build.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let status = Command::new("docker")
|
||||
.arg("build")
|
||||
.arg("-f")
|
||||
.arg("./tor.Dockerfile")
|
||||
.arg(".")
|
||||
.arg("-t")
|
||||
.arg("testcontainers-tor:latest")
|
||||
.status()
|
||||
.unwrap();
|
||||
|
||||
assert!(status.success());
|
||||
|
||||
println!("cargo:rerun-if-changed=./tor.Dockerfile");
|
||||
}
|
75
libp2p-tor/examples/dialer.rs
Normal file
75
libp2p-tor/examples/dialer.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::upgrade::Version;
|
||||
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;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let addr_to_dial = std::env::args()
|
||||
.next()
|
||||
.unwrap()
|
||||
.parse::<Multiaddr>()
|
||||
.unwrap();
|
||||
|
||||
let mut swarm = new_swarm();
|
||||
|
||||
println!("Peer-ID: {}", swarm.local_peer_id());
|
||||
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)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new swarm that is capable of dialling onion address.
|
||||
fn new_swarm() -> Swarm<Ping> {
|
||||
let identity = identity::Keypair::generate_ed25519();
|
||||
|
||||
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(yamux::YamuxConfig::default())
|
||||
.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()
|
||||
}
|
98
libp2p-tor/examples/listener.rs
Normal file
98
libp2p-tor/examples/listener.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::upgrade::Version;
|
||||
use libp2p::ping::{Ping, PingEvent, PingSuccess};
|
||||
use libp2p::swarm::{SwarmBuilder, SwarmEvent};
|
||||
use libp2p::{identity, noise, yamux, Swarm, Transport};
|
||||
use libp2p_tor::duplex;
|
||||
use libp2p_tor::torut_ext::AuthenticatedConnectionExt;
|
||||
use noise::NoiseConfig;
|
||||
use rand::Rng;
|
||||
use std::time::Duration;
|
||||
use torut::control::AuthenticatedConn;
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let wildcard_multiaddr =
|
||||
"/onion3/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW:8080"
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let mut swarm = new_swarm().await;
|
||||
|
||||
println!("Peer-ID: {}", swarm.local_peer_id());
|
||||
swarm.listen_on(wildcard_multiaddr).unwrap();
|
||||
|
||||
loop {
|
||||
match swarm.next_event().await {
|
||||
SwarmEvent::NewListenAddr(addr) => {
|
||||
println!("Listening on {}", addr);
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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() -> Swarm<Ping> {
|
||||
let identity = identity::Keypair::generate_ed25519();
|
||||
|
||||
SwarmBuilder::new(
|
||||
duplex::TorConfig::new(
|
||||
AuthenticatedConn::new(9051).await.unwrap(),
|
||||
random_onion_identity,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.upgrade(Version::V1)
|
||||
.authenticate(
|
||||
NoiseConfig::xx(
|
||||
noise::Keypair::<noise::X25519Spec>::new()
|
||||
.into_authentic(&identity)
|
||||
.unwrap(),
|
||||
)
|
||||
.into_authenticated(),
|
||||
)
|
||||
.multiplex(yamux::YamuxConfig::default())
|
||||
.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 random_onion_identity() -> TorSecretKeyV3 {
|
||||
let mut onion_key_bytes = [0u8; 64];
|
||||
rand::thread_rng().fill(&mut onion_key_bytes);
|
||||
|
||||
onion_key_bytes.into()
|
||||
}
|
57
libp2p-tor/src/dial_only.rs
Normal file
57
libp2p-tor/src/dial_only.rs
Normal file
|
@ -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 :)
|
||||
}
|
||||
}
|
187
libp2p-tor/src/duplex.rs
Normal file
187
libp2p-tor/src/duplex.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
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;
|
||||
|
||||
/// This is the hash of
|
||||
/// `/onion3/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW`.
|
||||
const WILDCARD_ONION_ADDR_HASH: [u8; 35] = [
|
||||
181, 173, 107, 90, 214, 181, 173, 107, 90, 214, 181, 173, 107, 90, 214, 181, 173, 107, 90, 214,
|
||||
181, 173, 107, 90, 214, 181, 173, 107, 90, 214, 181, 173, 107, 90, 214,
|
||||
];
|
||||
|
||||
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>>>,
|
||||
onion_key_generator: Arc<dyn (Fn() -> TorSecretKeyV3) + Send + Sync>,
|
||||
socks_port: u16,
|
||||
}
|
||||
|
||||
impl TorConfig {
|
||||
pub async fn new(
|
||||
mut client: AuthenticatedConn<tokio::net::TcpStream, TorutAsyncEventHandler>,
|
||||
onion_key_generator: impl (Fn() -> TorSecretKeyV3) + Send + Sync + 'static,
|
||||
) -> Result<Self, Error> {
|
||||
let socks_port = client.get_socks_port().await?;
|
||||
|
||||
Ok(Self {
|
||||
inner: TokioTcpConfig::new().map_err(Error::InnerTransprot),
|
||||
tor_client: Arc::new(Mutex::new(client)),
|
||||
onion_key_generator: Arc::new(onion_key_generator),
|
||||
socks_port,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn from_control_port(
|
||||
control_port: u16,
|
||||
key_generator: impl (Fn() -> TorSecretKeyV3) + Send + Sync + 'static,
|
||||
) -> Result<Self, Error> {
|
||||
let client = AuthenticatedConn::new(control_port).await?;
|
||||
|
||||
Self::new(client, key_generator).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));
|
||||
};
|
||||
|
||||
if onion.hash() != &WILDCARD_ONION_ADDR_HASH {
|
||||
return Err(TransportError::Other(Error::OnlyWildcardAllowed));
|
||||
}
|
||||
|
||||
let localhost_tcp_random_port_addr = "/ip4/127.0.0.1/tcp/0"
|
||||
.parse()
|
||||
.expect("always a valid multiaddr");
|
||||
|
||||
let listener = self.inner.listen_on(localhost_tcp_random_port_addr)?;
|
||||
|
||||
let key: TorSecretKeyV3 = (self.onion_key_generator)();
|
||||
let onion_bytes = key.public().get_onion_address().get_raw_bytes();
|
||||
let onion_port = onion.port();
|
||||
|
||||
let tor_client = self.tor_client;
|
||||
|
||||
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) => {
|
||||
let local_port = address
|
||||
.iter()
|
||||
.find_map(|p| match p {
|
||||
Protocol::Tcp(port) => Some(port),
|
||||
_ => None,
|
||||
})
|
||||
.expect("TODO: Error handling");
|
||||
|
||||
tracing::debug!(
|
||||
"Setting up hidden service at {} to forward to {}",
|
||||
onion_multiaddress,
|
||||
address
|
||||
);
|
||||
|
||||
match tor_client
|
||||
.clone()
|
||||
.lock()
|
||||
.await
|
||||
.add_ephemeral_service(&key, onion_port, local_port)
|
||||
.await
|
||||
{
|
||||
Ok(()) => ListenerEvent::NewAddress(onion_multiaddress.clone()),
|
||||
Err(e) => ListenerEvent::Error(Error::Torut(e)),
|
||||
}
|
||||
}
|
||||
ListenerEvent::Upgrade {
|
||||
upgrade,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
} => ListenerEvent::Upgrade {
|
||||
upgrade: upgrade.boxed(),
|
||||
local_addr,
|
||||
remote_addr,
|
||||
},
|
||||
ListenerEvent::AddressExpired(_) => {
|
||||
// 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();
|
||||
|
||||
match tor_client
|
||||
.lock()
|
||||
.await
|
||||
.del_onion(&onion_address_without_dot_onion)
|
||||
.await
|
||||
{
|
||||
Ok(()) => ListenerEvent::AddressExpired(onion_multiaddress),
|
||||
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, _: &Multiaddr, _: &Multiaddr) -> Option<Multiaddr> {
|
||||
None // address translation for tor doesn't make any sense :)
|
||||
}
|
||||
}
|
60
libp2p-tor/src/fmt_as_tor_compatible_address.rs
Normal file
60
libp2p-tor/src/fmt_as_tor_compatible_address.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
42
libp2p-tor/src/lib.rs
Normal file
42
libp2p-tor/src/lib.rs
Normal file
|
@ -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, 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),
|
||||
InnerTransprot(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)
|
||||
}
|
||||
}
|
116
libp2p-tor/src/torut_ext.rs
Normal file
116
libp2p-tor/src/torut_ext.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use std::borrow::Cow;
|
||||
use std::future::Future;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
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> {
|
||||
self.add_onion_v3(
|
||||
&key,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
&mut iter::once(&(
|
||||
onion_port,
|
||||
SocketAddr::V4(SocketAddrV4::new(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)
|
||||
}
|
||||
}
|
203
libp2p-tor/tests/integration_test.rs
Normal file
203
libp2p-tor/tests/integration_test.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::transport;
|
||||
use libp2p::core::upgrade::Version;
|
||||
use libp2p::ping::{Ping, PingEvent};
|
||||
use libp2p::swarm::{SwarmBuilder, SwarmEvent};
|
||||
use libp2p::tcp::tokio::TcpStream;
|
||||
use libp2p::{noise, yamux, Swarm, Transport};
|
||||
use libp2p_tor::torut_ext::AuthenticatedConnectionExt;
|
||||
use libp2p_tor::{dial_only, duplex};
|
||||
use rand::Rng;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
use testcontainers::{Container, Docker, Image, WaitForMessage};
|
||||
use torut::control::AuthenticatedConn;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn create_ephemeral_service() {
|
||||
tracing_subscriber::fmt().with_env_filter("debug").init();
|
||||
let wildcard_multiaddr =
|
||||
"/onion3/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW:8080"
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// let docker = Cli::default();
|
||||
//
|
||||
// let tor1 = docker.run(TorImage::default().with_args(TorArgs {
|
||||
// control_port: Some(9051),
|
||||
// socks_port: None
|
||||
// }));
|
||||
// let tor2 = docker.run(TorImage::default());
|
||||
//
|
||||
// let tor1_control_port = tor1.get_host_port(9051).unwrap();
|
||||
// let tor2_socks_port = tor2.get_host_port(9050).unwrap();
|
||||
|
||||
let mut listen_swarm = make_swarm(async move {
|
||||
let mut onion_key_bytes = [0u8; 64];
|
||||
rand::thread_rng().fill(&mut onion_key_bytes);
|
||||
|
||||
duplex::TorConfig::new(
|
||||
AuthenticatedConn::with_password(9051, "supersecret")
|
||||
.await
|
||||
.unwrap(),
|
||||
move || onion_key_bytes.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.boxed()
|
||||
})
|
||||
.await;
|
||||
let mut dial_swarm = make_swarm(async { dial_only::TorConfig::new(9050).boxed() }).await;
|
||||
|
||||
listen_swarm.listen_on(wildcard_multiaddr).unwrap();
|
||||
|
||||
let onion_listen_addr = loop {
|
||||
let event = listen_swarm.next_event().await;
|
||||
|
||||
tracing::info!("{:?}", event);
|
||||
|
||||
if let SwarmEvent::NewListenAddr(addr) = event {
|
||||
break addr;
|
||||
}
|
||||
};
|
||||
|
||||
dial_swarm.dial_addr(onion_listen_addr).unwrap();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = listen_swarm.next_event() => {
|
||||
tracing::info!("{:?}", event);
|
||||
},
|
||||
event = dial_swarm.next_event() => {
|
||||
tracing::info!("{:?}", event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_swarm(
|
||||
transport_future: impl Future<Output = transport::Boxed<TcpStream>>,
|
||||
) -> Swarm<Behaviour> {
|
||||
let identity = libp2p::identity::Keypair::generate_ed25519();
|
||||
|
||||
let dh_keys = noise::Keypair::<noise::X25519Spec>::new()
|
||||
.into_authentic(&identity)
|
||||
.unwrap();
|
||||
let noise = noise::NoiseConfig::xx(dh_keys).into_authenticated();
|
||||
|
||||
let transport = transport_future
|
||||
.await
|
||||
.upgrade(Version::V1)
|
||||
.authenticate(noise)
|
||||
.multiplex(yamux::YamuxConfig::default())
|
||||
.timeout(Duration::from_secs(20))
|
||||
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
|
||||
.boxed();
|
||||
|
||||
SwarmBuilder::new(
|
||||
transport,
|
||||
Behaviour::default(),
|
||||
identity.public().into_peer_id(),
|
||||
)
|
||||
.executor(Box::new(|f| {
|
||||
tokio::spawn(f);
|
||||
}))
|
||||
.build()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum OutEvent {
|
||||
Ping(PingEvent),
|
||||
}
|
||||
|
||||
impl From<PingEvent> for OutEvent {
|
||||
fn from(e: PingEvent) -> Self {
|
||||
OutEvent::Ping(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(libp2p::NetworkBehaviour, Default)]
|
||||
#[behaviour(event_process = false, out_event = "OutEvent")]
|
||||
struct Behaviour {
|
||||
ping: Ping,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TorImage {
|
||||
args: TorArgs,
|
||||
}
|
||||
|
||||
impl TorImage {
|
||||
// fn control_port_password(&self) -> String {
|
||||
// "supersecret".to_owned()
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone)]
|
||||
struct TorArgs {
|
||||
control_port: Option<u16>,
|
||||
socks_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl IntoIterator for TorArgs {
|
||||
type Item = String;
|
||||
type IntoIter = std::vec::IntoIter<String>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let mut args = Vec::new();
|
||||
|
||||
if let Some(port) = self.socks_port {
|
||||
args.push(format!("SocksPort"));
|
||||
args.push(format!("0.0.0.0:{}", port));
|
||||
}
|
||||
|
||||
if let Some(port) = self.control_port {
|
||||
args.push(format!("ControlPort"));
|
||||
args.push(format!("0.0.0.0:{}", port));
|
||||
args.push(format!("HashedControlPassword"));
|
||||
args.push(format!(
|
||||
"16:436B425404AA332A60B4F341C2023146C4B3A80548D757F0BB10DE81B4"
|
||||
))
|
||||
}
|
||||
|
||||
args.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Image for TorImage {
|
||||
type Args = TorArgs;
|
||||
type EnvVars = HashMap<String, String>;
|
||||
type Volumes = HashMap<String, String>;
|
||||
type EntryPoint = Infallible;
|
||||
|
||||
fn descriptor(&self) -> String {
|
||||
"testcontainers-tor:latest".to_owned() // this is build locally using
|
||||
// the buildscript
|
||||
}
|
||||
|
||||
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
|
||||
container
|
||||
.logs()
|
||||
.stdout
|
||||
.wait_for_message("Bootstrapped 100% (done): Done")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn args(&self) -> Self::Args {
|
||||
self.args.clone()
|
||||
}
|
||||
|
||||
fn env_vars(&self) -> Self::EnvVars {
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
fn volumes(&self) -> Self::Volumes {
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
fn with_args(self, args: Self::Args) -> Self {
|
||||
Self { args }
|
||||
}
|
||||
}
|
13
libp2p-tor/tor.Dockerfile
Normal file
13
libp2p-tor/tor.Dockerfile
Normal file
|
@ -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"]
|
Loading…
Add table
Add a link
Reference in a new issue