Skip to content

Commit 955c7fe

Browse files
committed
Support onion multiaddr
1 parent db23449 commit 955c7fe

File tree

6 files changed

+147
-1
lines changed

6 files changed

+147
-1
lines changed

multiaddr/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ bytes = "1.0"
1515
bs58 = "0.5.0"
1616
sha2 = "0.10.0"
1717
serde = "1"
18+
byteorder = "1.5.0"
19+
data-encoding = "2.6.0"
20+
arrayref = "0.3.9"
1821

1922
[dev-dependencies]
2023
parity-multiaddr = "0.11"

multiaddr/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use unsigned_varint::decode;
55
pub enum Error {
66
DataLessThanLen,
77
InvalidMultiaddr,
8+
InvalidOnion3Addr,
89
InvalidProtocolString,
910
InvalidUvar(decode::Error),
1011
ParsingError(Box<dyn error::Error + Send + Sync>),
@@ -19,6 +20,7 @@ impl fmt::Display for Error {
1920
Error::DataLessThanLen => f.write_str("we have less data than indicated by length"),
2021
Error::InvalidMultiaddr => f.write_str("invalid multiaddr"),
2122
Error::InvalidProtocolString => f.write_str("invalid protocol string"),
23+
Error::InvalidOnion3Addr => f.write_str("invalid onion3 address"),
2224
Error::InvalidUvar(e) => write!(f, "failed to decode unsigned varint: {}", e),
2325
Error::ParsingError(e) => write!(f, "failed to parse: {}", e),
2426
Error::UnknownHash => write!(f, "unknown hash"),

multiaddr/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
///! Mini Implementation of [multiaddr](https://github.com/jbenet/multiaddr) in Rust.
22
mod error;
3+
mod onion_addr;
34
mod protocol;
45

56
pub use self::error::Error;
7+
pub use self::onion_addr::Onion3Addr;
68
pub use self::protocol::Protocol;
79
use bytes::{Bytes, BytesMut};
810
use serde::{

multiaddr/src/onion_addr.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use std::{borrow::Cow, fmt};
2+
3+
/// Represents an Onion v3 address
4+
#[derive(Clone)]
5+
pub struct Onion3Addr<'a>(Cow<'a, [u8; 35]>, u16);
6+
7+
impl<'a> Onion3Addr<'a> {
8+
/// Return the hash of the public key as bytes
9+
pub fn hash(&self) -> &[u8; 35] {
10+
self.0.as_ref()
11+
}
12+
13+
/// Return the port
14+
pub fn port(&self) -> u16 {
15+
self.1
16+
}
17+
18+
/// Consume this instance and create an owned version containing the same address
19+
pub fn acquire<'b>(self) -> Onion3Addr<'b> {
20+
Onion3Addr(Cow::Owned(self.0.into_owned()), self.1)
21+
}
22+
}
23+
24+
impl PartialEq for Onion3Addr<'_> {
25+
fn eq(&self, other: &Self) -> bool {
26+
self.1 == other.1 && self.0[..] == other.0[..]
27+
}
28+
}
29+
30+
impl Eq for Onion3Addr<'_> {}
31+
32+
impl From<([u8; 35], u16)> for Onion3Addr<'_> {
33+
fn from(parts: ([u8; 35], u16)) -> Self {
34+
Self(Cow::Owned(parts.0), parts.1)
35+
}
36+
}
37+
38+
impl<'a> From<(&'a [u8; 35], u16)> for Onion3Addr<'a> {
39+
fn from(parts: (&'a [u8; 35], u16)) -> Self {
40+
Self(Cow::Borrowed(parts.0), parts.1)
41+
}
42+
}
43+
44+
impl fmt::Debug for Onion3Addr<'_> {
45+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
46+
f.debug_tuple("Onion3Addr")
47+
.field(&format!("{:02x?}", &self.0[..]))
48+
.field(&self.1)
49+
.finish()
50+
}
51+
}

multiaddr/src/protocol.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use arrayref::array_ref;
2+
use byteorder::{BigEndian, ByteOrder};
13
use bytes::{Buf, BufMut};
4+
use data_encoding::BASE32;
25
use std::{
36
borrow::Cow,
47
fmt,
@@ -7,7 +10,7 @@ use std::{
710
str::{self, FromStr},
811
};
912

10-
use crate::error::Error;
13+
use crate::{error::Error, Onion3Addr};
1114

1215
const DNS4: u32 = 0x36;
1316
const DNS6: u32 = 0x37;
@@ -19,6 +22,7 @@ const TLS: u32 = 0x01c0;
1922
const WS: u32 = 0x01dd;
2023
const WSS: u32 = 0x01de;
2124
const MEMORY: u32 = 0x0309;
25+
const ONION3: u32 = 0x01bd;
2226

2327
const SHA256_CODE: u64 = 0x12;
2428
const SHA256_SIZE: u8 = 32;
@@ -37,6 +41,7 @@ pub enum Protocol<'a> {
3741
Wss,
3842
/// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port".
3943
Memory(u64),
44+
Onion3(Onion3Addr<'a>),
4045
}
4146

4247
impl<'a> Protocol<'a> {
@@ -87,6 +92,11 @@ impl<'a> Protocol<'a> {
8792
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
8893
Ok(Protocol::Memory(s.parse()?))
8994
}
95+
"onion3" => iter
96+
.next()
97+
.ok_or(Error::InvalidProtocolString)
98+
.and_then(|s| read_onion3(&s.to_uppercase()))
99+
.map(|(a, p)| Protocol::Onion3((a, p).into())),
90100
_ => Err(Error::UnknownProtocolString),
91101
}
92102
}
@@ -101,6 +111,14 @@ impl<'a> Protocol<'a> {
101111
}
102112
Ok(input.split_at(n))
103113
}
114+
115+
fn split_at(n: usize, input: &[u8]) -> Result<(&[u8], &[u8]), Error> {
116+
if input.len() < n {
117+
return Err(Error::DataLessThanLen);
118+
}
119+
Ok(input.split_at(n))
120+
}
121+
104122
let (id, input) = decode::u32(input)?;
105123
match id {
106124
DNS4 => {
@@ -160,6 +178,14 @@ impl<'a> Protocol<'a> {
160178
let num = rdr.get_u64();
161179
Ok((Protocol::Memory(num), rest))
162180
}
181+
ONION3 => {
182+
let (data, rest) = split_at(37, input)?;
183+
let port = BigEndian::read_u16(&data[35..]);
184+
Ok((
185+
Protocol::Onion3((array_ref!(data, 0, 35), port).into()),
186+
rest,
187+
))
188+
}
163189
_ => Err(Error::UnknownProtocolId(id)),
164190
}
165191
}
@@ -213,6 +239,11 @@ impl<'a> Protocol<'a> {
213239
w.put(encode::u32(MEMORY, &mut buf));
214240
w.put_u64(*port)
215241
}
242+
Protocol::Onion3(addr) => {
243+
w.put(encode::u32(ONION3, &mut buf));
244+
w.put(addr.hash().as_ref());
245+
w.put_u16(addr.port());
246+
}
216247
}
217248
}
218249

@@ -229,6 +260,7 @@ impl<'a> Protocol<'a> {
229260
Protocol::Ws => Protocol::Ws,
230261
Protocol::Wss => Protocol::Wss,
231262
Protocol::Memory(a) => Protocol::Memory(a),
263+
Protocol::Onion3(addr) => Protocol::Onion3(addr.acquire()),
232264
}
233265
}
234266
}
@@ -247,6 +279,10 @@ impl<'a> fmt::Display for Protocol<'a> {
247279
Ws => write!(f, "/ws"),
248280
Wss => write!(f, "/wss"),
249281
Memory(port) => write!(f, "/memory/{}", port),
282+
Onion3(addr) => {
283+
let s = BASE32.encode(addr.hash());
284+
write!(f, "/onion3/{}:{}", s.to_lowercase(), addr.port())
285+
}
250286
}
251287
}
252288
}
@@ -291,3 +327,53 @@ fn check_p2p(data: &[u8]) -> Result<(), Error> {
291327
}
292328
Ok(())
293329
}
330+
331+
macro_rules! read_onion_impl {
332+
($name:ident, $len:expr, $encoded_len:expr) => {
333+
fn $name(s: &str) -> Result<([u8; $len], u16), Error> {
334+
let mut parts = s.split(':');
335+
336+
// address part (without ".onion")
337+
let b32 = parts.next().ok_or(Error::InvalidMultiaddr)?;
338+
if b32.len() != $encoded_len {
339+
return Err(Error::InvalidMultiaddr);
340+
}
341+
342+
// port number
343+
let port = parts
344+
.next()
345+
.ok_or(Error::InvalidMultiaddr)
346+
.and_then(|p| str::parse(p).map_err(From::from))?;
347+
348+
// port == 0 is not valid for onion
349+
if port == 0 {
350+
return Err(Error::InvalidMultiaddr);
351+
}
352+
353+
// nothing else expected
354+
if parts.next().is_some() {
355+
return Err(Error::InvalidMultiaddr);
356+
}
357+
358+
if $len
359+
!= BASE32
360+
.decode_len(b32.len())
361+
.map_err(|_| Error::InvalidMultiaddr)?
362+
{
363+
return Err(Error::InvalidMultiaddr);
364+
}
365+
366+
let mut buf = [0u8; $len];
367+
BASE32
368+
.decode_mut(b32.as_bytes(), &mut buf)
369+
.map_err(|_| Error::InvalidMultiaddr)?;
370+
371+
Ok((buf, port))
372+
}
373+
};
374+
}
375+
376+
// Parse a version 3 onion address and return its binary representation.
377+
//
378+
// Format: <base-32 address> ":" <port number>
379+
read_onion_impl!(read_onion3, 35, 56);

tentacle/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ igd = { version = "0.15", optional = true, package = "igd-next" }
4444

4545
#tls
4646
tokio-rustls = { version = "0.26.0", optional = true }
47+
url = "2.5.4"
4748

4849
[target.'cfg(not(target_family = "wasm"))'.dependencies]
4950
# rand 0.8 not support wasm32
5051
rand = "0.8"
5152
socket2 = { version = "0.5.0", features = ["all"] }
53+
shadowsocks = { version = "1.21.0", default-features = false }
5254

5355
[target.'cfg(target_family = "wasm")'.dependencies]
5456
js-sys = "0.3"

0 commit comments

Comments
 (0)