Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.

Commit d9095d8

Browse files
authored
Merge pull request umgefahren#24 from UnstoppableSwap/feat/listen-hidden-service
feat: Listen on onion-service, dial onion services
2 parents d9fa27a + 0a973c8 commit d9095d8

File tree

4 files changed

+353
-54
lines changed

4 files changed

+353
-54
lines changed

Cargo.toml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,39 @@ repository = "https://github.com/umgefahren/libp2p-tor"
99
authors = ["umgefahren <[email protected]>"]
1010

1111
[dependencies]
12-
arti-client = { version = "0.24", default-features = false, features = ["tokio", "rustls"] }
12+
thiserror = "1.0"
13+
anyhow = "1.0.93"
14+
tokio = "1.41.1"
1315
futures = "0.3"
16+
17+
arti-client = { version = "0.24", default-features = false, features = ["tokio", "rustls", "onion-service-client"] }
1418
libp2p = { version = "^0.53", default-features = false, features = ["tokio", "tcp", "tls"] }
19+
1520
tor-rtcompat = { version = "0.24.0", features = ["tokio", "rustls"] }
16-
tokio = { version = "1.0", features = ["macros"] }
1721
tracing = "0.1.40"
22+
tor-hsservice = { version = "0.24.0", optional = true }
23+
tor-cell = { version = "0.24.0", optional = true }
24+
tor-proto = { version = "0.24.0", optional = true }
25+
data-encoding = { version = "2.6.0", optional = true }
1826

1927
[dev-dependencies]
2028
libp2p = { version = "0.53", default-features = false, features = ["tokio", "noise", "yamux", "ping", "macros", "tcp", "tls"] }
2129
tokio-test = "0.4.4"
30+
tokio = { version = "1.41.1", features = ["macros"] }
31+
tracing-subscriber = "0.2"
32+
33+
[features]
34+
listen-onion-service = [
35+
"arti-client/onion-service-service",
36+
"dep:tor-hsservice",
37+
"dep:tor-cell",
38+
"dep:tor-proto",
39+
"dep:data-encoding"
40+
]
2241

2342
[[example]]
2443
name = "ping-onion"
44+
required-features = ["listen-onion-service"]
2545

2646
[package.metadata.docs.rs]
2747
all-features = true

examples/ping-onion.rs

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,48 +54,67 @@ use libp2p::{
5454
};
5555
use libp2p_community_tor::{AddressConversion, TorTransport};
5656
use std::error::Error;
57+
use tor_hsservice::config::OnionServiceConfigBuilder;
5758

59+
/// Create a transport
60+
/// Returns a tuple of the transport and the onion address we can instruct it to listen on
5861
async fn onion_transport(
5962
keypair: identity::Keypair,
6063
) -> Result<
61-
libp2p::core::transport::Boxed<(PeerId, libp2p::core::muxing::StreamMuxerBox)>,
64+
(
65+
libp2p::core::transport::Boxed<(PeerId, libp2p::core::muxing::StreamMuxerBox)>,
66+
Multiaddr,
67+
),
6268
Box<dyn Error>,
6369
> {
64-
let transport = TorTransport::bootstrapped()
70+
let mut transport = TorTransport::bootstrapped()
6571
.await?
66-
.with_address_conversion(AddressConversion::IpAndDns)
67-
.boxed();
72+
.with_address_conversion(AddressConversion::IpAndDns);
73+
74+
// We derive the nickname for the onion address from the peer id
75+
let svg_cfg = OnionServiceConfigBuilder::default()
76+
.nickname(
77+
keypair
78+
.public()
79+
.to_peer_id()
80+
.to_base58()
81+
.to_ascii_lowercase()
82+
.parse()
83+
.unwrap(),
84+
)
85+
.num_intro_points(3)
86+
.build()
87+
.unwrap();
88+
89+
let onion_listen_address = transport.add_onion_service(svg_cfg, 999).unwrap();
6890

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

7294
let transport = transport
95+
.boxed()
7396
.upgrade(Version::V1)
7497
.authenticate(auth_upgrade)
7598
.multiplex(multiplex_upgrade)
7699
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
77100
.boxed();
78101

79-
Ok(transport)
102+
Ok((transport, onion_listen_address))
80103
}
81104

82105
#[tokio::main]
83106
async fn main() -> Result<(), Box<dyn Error>> {
107+
tracing_subscriber::fmt::init();
108+
84109
let local_key = identity::Keypair::generate_ed25519();
85110
let local_peer_id = PeerId::from(local_key.public());
86111

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

89-
let transport = onion_transport(local_key).await?;
114+
let (transport, onion_listen_address) = onion_transport(local_key).await?;
90115

91116
let mut swarm = SwarmBuilder::with_new_identity()
92117
.with_tokio()
93-
.with_tcp(
94-
Default::default(),
95-
(libp2p::tls::Config::new, libp2p::noise::Config::new),
96-
libp2p::yamux::Config::default,
97-
)
98-
.unwrap()
99118
.with_other_transport(|_| transport)
100119
.unwrap()
101120
.with_behaviour(|_| Behaviour {
@@ -104,18 +123,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
104123
.unwrap()
105124
.build();
106125

107-
// Tell the swarm to listen on all interfaces and a random, OS-assigned
108-
// port.
109-
swarm
110-
.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
111-
.unwrap();
112-
113126
// Dial the peer identified by the multi-address given as the second
114127
// command-line argument, if any.
115128
if let Some(addr) = std::env::args().nth(1) {
116129
let remote: Multiaddr = addr.parse()?;
117130
swarm.dial(remote)?;
118131
println!("Dialed {addr}")
132+
} else {
133+
// TODO: We need to do this because otherwise the status of the onion service is gonna be [`Shutdown`]
134+
// when we first poll it and then the swarm will not pull it again (?). I don't know why this is the case.
135+
tokio::time::sleep(std::time::Duration::from_secs(20)).await;
136+
137+
// If we are not dialing, we need to listen
138+
// Tell the swarm to listen on a specific onion address
139+
swarm.listen_on(onion_listen_address).unwrap();
119140
}
120141

121142
loop {

src/address.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1818
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1919
// DEALINGS IN THE SOFTWARE.
20-
2120
use arti_client::{DangerouslyIntoTorAddr, IntoTorAddr, TorAddr};
22-
use libp2p::{core::multiaddr::Protocol, Multiaddr};
21+
use libp2p::{core::multiaddr::Protocol, multiaddr::Onion3Addr, Multiaddr};
2322
use std::net::SocketAddr;
2423

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

48-
let tor_addr = try_to_domain_and_port(&protocols.next()?, &protocols.next()?)?
47+
let tor_addr = try_to_domain_and_port(&protocols.next()?, &protocols.next())?
4948
.into_tor_addr()
5049
.ok()?;
5150

5251
Some(tor_addr)
5352
}
5453

54+
fn libp2p_onion_address_to_domain_and_port<'a>(
55+
onion_address: &'a Onion3Addr<'_>,
56+
) -> Option<(&'a str, u16)> {
57+
// Here we convert from Onion3Addr to TorAddr
58+
// We need to leak the string because it's a temporary string that would otherwise be freed
59+
let hash = data_encoding::BASE32.encode(onion_address.hash());
60+
let onion_domain = format!("{}.onion", hash);
61+
let onion_domain = Box::leak(onion_domain.into_boxed_str());
62+
63+
Some((onion_domain, onion_address.port()))
64+
}
65+
5566
fn try_to_domain_and_port<'a>(
5667
maybe_domain: &'a Protocol,
57-
maybe_port: &Protocol,
68+
maybe_port: &Option<Protocol>,
5869
) -> Option<(&'a str, u16)> {
5970
match (maybe_domain, maybe_port) {
6071
(
6172
Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain),
62-
Protocol::Tcp(port),
73+
Some(Protocol::Tcp(port)),
6374
) => Some((domain.as_ref(), *port)),
75+
(Protocol::Onion3(domain), _) => libp2p_onion_address_to_domain_and_port(domain),
6476
_ => None,
6577
}
6678
}

0 commit comments

Comments
 (0)