Skip to content

Commit

Permalink
feat: listening on onion services, dialing onion services
Browse files Browse the repository at this point in the history
  • Loading branch information
binarybaron committed Nov 24, 2024
1 parent b7e67ae commit f4e71fa
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 54 deletions.
24 changes: 22 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,39 @@ repository = "https://github.com/umgefahren/libp2p-tor"
authors = ["umgefahren <[email protected]>"]

[dependencies]
arti-client = { version = "0.24", default-features = false, features = ["tokio", "rustls"] }
thiserror = "1.0"
anyhow = "1.0.93"
tokio = "1.41.1"
futures = "0.3"

arti-client = { version = "0.24", default-features = false, features = ["tokio", "rustls", "onion-service-client"] }
libp2p = { version = "^0.53", default-features = false, features = ["tokio", "tcp", "tls"] }

tor-rtcompat = { version = "0.24.0", features = ["tokio", "rustls"] }
tokio = { version = "1.0", features = ["macros"] }
tracing = "0.1.40"
tor-hsservice = { version = "0.24.0", optional = true }
tor-cell = { version = "0.24.0", optional = true }
tor-proto = { version = "0.24.0", optional = true }
data-encoding = { version = "2.6.0", optional = true }

[dev-dependencies]
libp2p = { version = "0.53", default-features = false, features = ["tokio", "noise", "yamux", "ping", "macros", "tcp", "tls"] }
tokio-test = "0.4.4"
tokio = { version = "1.41.1", features = ["macros"] }
tracing-subscriber = "0.2"

[features]
listen-onion-service = [
"arti-client/onion-service-service",
"dep:tor-hsservice",
"dep:tor-cell",
"dep:tor-proto",
"dep:data-encoding"
]

[[example]]
name = "ping-onion"
required-features = ["listen-onion-service"]

[package.metadata.docs.rs]
all-features = true
Expand Down
57 changes: 39 additions & 18 deletions examples/ping-onion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,48 +54,67 @@ use libp2p::{
};
use libp2p_community_tor::{AddressConversion, TorTransport};
use std::error::Error;
use tor_hsservice::config::OnionServiceConfigBuilder;

/// Create a transport
/// Returns a tuple of the transport and the onion address we can instruct it to listen on
async fn onion_transport(
keypair: identity::Keypair,
) -> Result<
libp2p::core::transport::Boxed<(PeerId, libp2p::core::muxing::StreamMuxerBox)>,
(
libp2p::core::transport::Boxed<(PeerId, libp2p::core::muxing::StreamMuxerBox)>,
Multiaddr,
),
Box<dyn Error>,
> {
let transport = TorTransport::bootstrapped()
let mut transport = TorTransport::bootstrapped()
.await?
.with_address_conversion(AddressConversion::IpAndDns)
.boxed();
.with_address_conversion(AddressConversion::IpAndDns);

// We derive the nickname for the onion address from the peer id
let svg_cfg = OnionServiceConfigBuilder::default()
.nickname(
keypair
.public()
.to_peer_id()
.to_base58()
.to_ascii_lowercase()
.parse()
.unwrap(),
)
.num_intro_points(3)
.build()
.unwrap();

let onion_listen_address = transport.add_onion_service(svg_cfg, 999).unwrap();

let auth_upgrade = noise::Config::new(&keypair)?;
let multiplex_upgrade = yamux::Config::default();

let transport = transport
.boxed()
.upgrade(Version::V1)
.authenticate(auth_upgrade)
.multiplex(multiplex_upgrade)
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
.boxed();

Ok(transport)
Ok((transport, onion_listen_address))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init();

let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());

println!("Local peer id: {local_peer_id}");

let transport = onion_transport(local_key).await?;
let (transport, onion_listen_address) = onion_transport(local_key).await?;

let mut swarm = SwarmBuilder::with_new_identity()
.with_tokio()
.with_tcp(
Default::default(),
(libp2p::tls::Config::new, libp2p::noise::Config::new),
libp2p::yamux::Config::default,
)
.unwrap()
.with_other_transport(|_| transport)
.unwrap()
.with_behaviour(|_| Behaviour {
Expand All @@ -104,18 +123,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
.unwrap()
.build();

// Tell the swarm to listen on all interfaces and a random, OS-assigned
// port.
swarm
.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
.unwrap();

// Dial the peer identified by the multi-address given as the second
// command-line argument, if any.
if let Some(addr) = std::env::args().nth(1) {
let remote: Multiaddr = addr.parse()?;
swarm.dial(remote)?;
println!("Dialed {addr}")
} else {
// TODO: We need to do this because otherwise the status of the onion service is gonna be [`Shutdown`]
// when we first poll it and then the swarm will not pull it again (?). I don't know why this is the case.
tokio::time::sleep(std::time::Duration::from_secs(20)).await;

// If we are not dialing, we need to listen
// Tell the swarm to listen on a specific onion address
swarm.listen_on(onion_listen_address).unwrap();
}

loop {
Expand Down
22 changes: 17 additions & 5 deletions src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use arti_client::{DangerouslyIntoTorAddr, IntoTorAddr, TorAddr};
use libp2p::{core::multiaddr::Protocol, Multiaddr};
use libp2p::{core::multiaddr::Protocol, multiaddr::Onion3Addr, Multiaddr};
use std::net::SocketAddr;

/// "Dangerously" extract a Tor address from the provided [`Multiaddr`].
Expand All @@ -45,22 +44,35 @@ pub fn dangerous_extract(multiaddr: &Multiaddr) -> Option<TorAddr> {
pub fn safe_extract(multiaddr: &Multiaddr) -> Option<TorAddr> {
let mut protocols = multiaddr.into_iter();

let tor_addr = try_to_domain_and_port(&protocols.next()?, &protocols.next()?)?
let tor_addr = try_to_domain_and_port(&protocols.next()?, &protocols.next())?
.into_tor_addr()
.ok()?;

Some(tor_addr)
}

fn libp2p_onion_address_to_domain_and_port<'a>(
onion_address: &'a Onion3Addr<'_>,
) -> Option<(&'a str, u16)> {
// Here we convert from Onion3Addr to TorAddr
// We need to leak the string because it's a temporary string that would otherwise be freed
let hash = data_encoding::BASE32.encode(onion_address.hash());
let onion_domain = format!("{}.onion", hash);
let onion_domain = Box::leak(onion_domain.into_boxed_str());

Some((onion_domain, onion_address.port()))
}

fn try_to_domain_and_port<'a>(
maybe_domain: &'a Protocol,
maybe_port: &Protocol,
maybe_port: &Option<Protocol>,
) -> Option<(&'a str, u16)> {
match (maybe_domain, maybe_port) {
(
Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain),
Protocol::Tcp(port),
Some(Protocol::Tcp(port)),
) => Some((domain.as_ref(), *port)),
(Protocol::Onion3(domain), _) => libp2p_onion_address_to_domain_and_port(domain),
_ => None,
}
}
Expand Down
Loading

0 comments on commit f4e71fa

Please sign in to comment.