Added Onion3 support to multiaddr (#1354)

This commit is contained in:
Stan Bondi
2020-02-04 14:40:57 +02:00
committed by GitHub
parent 57fd677686
commit 955b60796a
4 changed files with 173 additions and 55 deletions

View File

@ -3,6 +3,7 @@
pub use multihash; pub use multihash;
mod protocol; mod protocol;
mod onion_addr;
mod errors; mod errors;
mod from_url; mod from_url;
@ -26,6 +27,7 @@ use std::{
pub use self::errors::{Result, Error}; pub use self::errors::{Result, Error};
pub use self::from_url::{FromUrlErr, from_url, from_url_lossy}; pub use self::from_url::{FromUrlErr, from_url, from_url_lossy};
pub use self::protocol::Protocol; pub use self::protocol::Protocol;
pub use self::onion_addr::Onion3Addr;
static_assertions::const_assert! { static_assertions::const_assert! {
// This check is most certainly overkill right now, but done here // This check is most certainly overkill right now, but done here

View File

@ -0,0 +1,54 @@
use std::borrow::Cow;
use std::fmt::Debug;
use serde::export::Formatter;
use std::fmt;
/// Represents an Onion v3 address
#[derive(Clone)]
pub struct Onion3Addr<'a>(Cow<'a, [u8; 35]>, u16);
impl<'a> Onion3Addr<'a> {
/// Return the hash of the public key as bytes
pub fn hash(&self) -> &[u8; 35] {
self.0.as_ref()
}
/// Return the port
pub fn port(&self) -> u16 {
self.1
}
/// Consume this instance and create an owned version containing the same address
pub fn acquire<'b>(self) -> Onion3Addr<'b> {
Self(Cow::Owned(self.0.into_owned()), self.1)
}
}
impl PartialEq for Onion3Addr<'_> {
fn eq(&self, other: &Self) -> bool {
self.1 == other.1 && self.0[..] == other.0[..]
}
}
impl Eq for Onion3Addr<'_> { }
impl From<([u8; 35], u16)> for Onion3Addr<'_> {
fn from(parts: ([u8; 35], u16)) -> Self {
Self(Cow::Owned(parts.0), parts.1)
}
}
impl<'a> From<(&'a [u8; 35], u16)> for Onion3Addr<'a> {
fn from(parts: (&'a [u8; 35], u16)) -> Self {
Self(Cow::Borrowed(parts.0), parts.1)
}
}
impl Debug for Onion3Addr<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_tuple("Onion3Addr")
.field(&format!("{:02x?}", &self.0[..]))
.field(&self.1)
.finish()
}
}

View File

@ -14,6 +14,7 @@ use std::{
str::{self, FromStr} str::{self, FromStr}
}; };
use unsigned_varint::{encode, decode}; use unsigned_varint::{encode, decode};
use crate::onion_addr::Onion3Addr;
const DCCP: u32 = 33; const DCCP: u32 = 33;
const DNS4: u32 = 54; const DNS4: u32 = 54;
@ -27,6 +28,7 @@ const P2P_WEBRTC_STAR: u32 = 275;
const P2P_WEBSOCKET_STAR: u32 = 479; const P2P_WEBSOCKET_STAR: u32 = 479;
const MEMORY: u32 = 777; const MEMORY: u32 = 777;
const ONION: u32 = 444; const ONION: u32 = 444;
const ONION3: u32 = 445;
const P2P: u32 = 421; const P2P: u32 = 421;
const P2P_CIRCUIT: u32 = 290; const P2P_CIRCUIT: u32 = 290;
const QUIC: u32 = 460; const QUIC: u32 = 460;
@ -75,6 +77,7 @@ pub enum Protocol<'a> {
/// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port". /// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port".
Memory(u64), Memory(u64),
Onion(Cow<'a, [u8; 10]>, u16), Onion(Cow<'a, [u8; 10]>, u16),
Onion3(Onion3Addr<'a>),
P2p(Multihash), P2p(Multihash),
P2pCircuit, P2pCircuit,
Quic, Quic,
@ -150,6 +153,11 @@ impl<'a> Protocol<'a> {
.ok_or(Error::InvalidProtocolString) .ok_or(Error::InvalidProtocolString)
.and_then(|s| read_onion(&s.to_uppercase())) .and_then(|s| read_onion(&s.to_uppercase()))
.map(|(a, p)| Protocol::Onion(Cow::Owned(a), p)), .map(|(a, p)| Protocol::Onion(Cow::Owned(a), p)),
"onion3" =>
iter.next()
.ok_or(Error::InvalidProtocolString)
.and_then(|s| read_onion3(&s.to_uppercase()))
.map(|(a, p)| Protocol::Onion3((a, p).into())),
"quic" => Ok(Protocol::Quic), "quic" => Ok(Protocol::Quic),
"ws" => Ok(Protocol::Ws(Cow::Borrowed("/"))), "ws" => Ok(Protocol::Ws(Cow::Borrowed("/"))),
"wss" => Ok(Protocol::Wss(Cow::Borrowed("/"))), "wss" => Ok(Protocol::Wss(Cow::Borrowed("/"))),
@ -242,6 +250,11 @@ impl<'a> Protocol<'a> {
let port = BigEndian::read_u16(&data[10 ..]); let port = BigEndian::read_u16(&data[10 ..]);
Ok((Protocol::Onion(Cow::Borrowed(array_ref!(data, 0, 10)), port), rest)) Ok((Protocol::Onion(Cow::Borrowed(array_ref!(data, 0, 10)), port), rest))
} }
ONION3 => {
let (data, rest) = split_at(37, input)?;
let port = BigEndian::read_u16(&data[35 ..]);
Ok((Protocol::Onion3((array_ref!(data, 0, 35), port).into()), rest))
}
P2P => { P2P => {
let (n, input) = decode::usize(input)?; let (n, input) = decode::usize(input)?;
let (data, rest) = split_at(n, input)?; let (data, rest) = split_at(n, input)?;
@ -350,6 +363,11 @@ impl<'a> Protocol<'a> {
w.write_all(addr.as_ref())?; w.write_all(addr.as_ref())?;
w.write_u16::<BigEndian>(*port)? w.write_u16::<BigEndian>(*port)?
} }
Protocol::Onion3(addr) => {
w.write_all(encode::u32(ONION3, &mut buf))?;
w.write_all(addr.hash().as_ref())?;
w.write_u16::<BigEndian>(addr.port())?
}
Protocol::Quic => w.write_all(encode::u32(QUIC, &mut buf))?, Protocol::Quic => w.write_all(encode::u32(QUIC, &mut buf))?,
Protocol::Utp => w.write_all(encode::u32(UTP, &mut buf))?, Protocol::Utp => w.write_all(encode::u32(UTP, &mut buf))?,
Protocol::Udt => w.write_all(encode::u32(UDT, &mut buf))?, Protocol::Udt => w.write_all(encode::u32(UDT, &mut buf))?,
@ -397,6 +415,7 @@ impl<'a> Protocol<'a> {
P2pWebSocketStar => P2pWebSocketStar, P2pWebSocketStar => P2pWebSocketStar,
Memory(a) => Memory(a), Memory(a) => Memory(a),
Onion(addr, port) => Onion(Cow::Owned(addr.into_owned()), port), Onion(addr, port) => Onion(Cow::Owned(addr.into_owned()), port),
Onion3(addr) => Onion3(addr.acquire()),
P2p(a) => P2p(a), P2p(a) => P2p(a),
P2pCircuit => P2pCircuit, P2pCircuit => P2pCircuit,
Quic => Quic, Quic => Quic,
@ -431,6 +450,10 @@ impl<'a> fmt::Display for Protocol<'a> {
let s = BASE32.encode(addr.as_ref()); let s = BASE32.encode(addr.as_ref());
write!(f, "/onion/{}:{}", s.to_lowercase(), port) write!(f, "/onion/{}:{}", s.to_lowercase(), port)
} }
Onion3(addr ) => {
let s = BASE32.encode(addr.hash());
write!(f, "/onion3/{}:{}", s.to_lowercase(), addr.port())
}
P2p(c) => write!(f, "/p2p/{}", bs58::encode(c.as_bytes()).into_string()), P2p(c) => write!(f, "/p2p/{}", bs58::encode(c.as_bytes()).into_string()),
P2pCircuit => f.write_str("/p2p-circuit"), P2pCircuit => f.write_str("/p2p-circuit"),
Quic => f.write_str("/quic"), Quic => f.write_str("/quic"),
@ -478,34 +501,49 @@ impl<'a> From<Ipv6Addr> for Protocol<'a> {
} }
} }
macro_rules! read_onion_impl {
($name:ident, $len:expr, $encoded_len:expr) => {
fn $name(s: &str) -> Result<([u8; $len], u16)> {
let mut parts = s.split(':');
// address part (without ".onion")
let b32 = parts.next().ok_or(Error::InvalidMultiaddr)?;
if b32.len() != $encoded_len {
return Err(Error::InvalidMultiaddr)
}
// port number
let port = parts.next()
.ok_or(Error::InvalidMultiaddr)
.and_then(|p| str::parse(p).map_err(From::from))?;
// port == 0 is not valid for onion
if port == 0 {
return Err(Error::InvalidMultiaddr);
}
// nothing else expected
if parts.next().is_some() {
return Err(Error::InvalidMultiaddr)
}
if $len != BASE32.decode_len(b32.len()).map_err(|_| Error::InvalidMultiaddr)? {
return Err(Error::InvalidMultiaddr)
}
let mut buf = [0u8; $len];
BASE32.decode_mut(b32.as_bytes(), &mut buf).map_err(|_| Error::InvalidMultiaddr)?;
Ok((buf, port))
}
}
}
// Parse a version 2 onion address and return its binary representation. // Parse a version 2 onion address and return its binary representation.
// //
// Format: <base-32 address> ":" <port number> // Format: <base-32 address> ":" <port number>
fn read_onion(s: &str) -> Result<([u8; 10], u16)> { read_onion_impl!(read_onion, 10, 16);
let mut parts = s.split(':'); // Parse a version 3 onion address and return its binary representation.
//
// address part (without ".onion") // Format: <base-32 address> ":" <port number>
let b32 = parts.next().ok_or(Error::InvalidMultiaddr)?; read_onion_impl!(read_onion3, 35, 56);
if b32.len() != 16 {
return Err(Error::InvalidMultiaddr)
}
// port number
let port = parts.next()
.ok_or(Error::InvalidMultiaddr)
.and_then(|p| str::parse(p).map_err(From::from))?;
// nothing else expected
if parts.next().is_some() {
return Err(Error::InvalidMultiaddr)
}
if 10 != BASE32.decode_len(b32.len()).map_err(|_| Error::InvalidMultiaddr)? {
return Err(Error::InvalidMultiaddr)
}
let mut buf = [0u8; 10];
BASE32.decode_mut(b32.as_bytes(), &mut buf).map_err(|_| Error::InvalidMultiaddr)?;
Ok((buf, port))
}

View File

@ -76,7 +76,7 @@ struct Proto(Protocol<'static>);
impl Arbitrary for Proto { impl Arbitrary for Proto {
fn arbitrary<G: Gen>(g: &mut G) -> Self { fn arbitrary<G: Gen>(g: &mut G) -> Self {
use Protocol::*; use Protocol::*;
match g.gen_range(0, 23) { // TODO: Add Protocol::Quic match g.gen_range(0, 24) { // TODO: Add Protocol::Quic
0 => Proto(Dccp(g.gen())), 0 => Proto(Dccp(g.gen())),
1 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))), 1 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))),
2 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))), 2 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))),
@ -104,7 +104,12 @@ impl Arbitrary for Proto {
let mut a = [0; 10]; let mut a = [0; 10];
g.fill(&mut a); g.fill(&mut a);
Proto(Onion(Cow::Owned(a), g.gen())) Proto(Onion(Cow::Owned(a), g.gen()))
} },
23 => {
let mut a = [0; 35];
g.fill_bytes(&mut a);
Proto(Onion3((a, g.gen()).into()))
},
_ => panic!("outside range") _ => panic!("outside range")
} }
} }
@ -208,35 +213,54 @@ fn construct_success() {
ma_valid("/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", ma_valid("/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
"047F000001062382A202A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", "047F000001062382A202A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B",
vec![Ip4(local.clone()), Tcp(9090), P2pCircuit, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]); vec![Ip4(local.clone()), Tcp(9090), P2pCircuit, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]);
ma_valid(
"/onion/aaimaq4ygg2iegci:80",
"BC030010C0439831B48218480050",
vec![Onion(Cow::Owned([0, 16, 192, 67, 152, 49, 180, 130, 24, 72]), 80)],
);
ma_valid(
"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234",
"BD03ADADEC040BE047F9658668B11A504F3155001F231A37F54C4476C07FB4CC139ED7E30304D2",
vec![Onion3(([173, 173, 236, 4, 11, 224, 71, 249, 101, 134, 104, 177, 26, 80, 79, 49, 85, 0, 31, 35, 26, 55, 245, 76, 68, 118, 192, 127, 180, 204, 19, 158, 215, 227, 3], 1234).into())],
);
} }
#[test] #[test]
fn construct_fail() { fn construct_fail() {
let addresses = ["/ip4", let addresses = [
"/ip4/::1", "/ip4",
"/ip4/fdpsofodsajfdoisa", "/ip4/::1",
"/ip6", "/ip4/fdpsofodsajfdoisa",
"/udp", "/ip6",
"/tcp", "/udp",
"/sctp", "/tcp",
"/udp/65536", "/sctp",
"/tcp/65536", "/udp/65536",
// "/onion/9imaq4ygg2iegci7:80", "/tcp/65536",
// "/onion/aaimaq4ygg2iegci7:80", "/onion/9imaq4ygg2iegci7:80",
// "/onion/timaq4ygg2iegci7:0", "/onion/aaimaq4ygg2iegci7:80",
// "/onion/timaq4ygg2iegci7:-1", "/onion/timaq4ygg2iegci7:0",
// "/onion/timaq4ygg2iegci7", "/onion/timaq4ygg2iegci7:-1",
// "/onion/timaq4ygg2iegci@:666", "/onion/timaq4ygg2iegci7",
"/udp/1234/sctp", "/onion/timaq4ygg2iegci@:666",
"/udp/1234/udt/1234", "/onion3/9ww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80",
"/udp/1234/utp/1234", "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd7:80",
"/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:0",
"/ip4/127.0.0.1/udp", "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:-1",
"/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd",
"/ip4/127.0.0.1/tcp", "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyy@:666",
"/ip4/127.0.0.1/p2p", "/udp/1234/sctp",
"/ip4/127.0.0.1/p2p/tcp", "/udp/1234/udt/1234",
"/p2p-circuit/50"]; "/udp/1234/utp/1234",
"/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa",
"/ip4/127.0.0.1/udp",
"/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa",
"/ip4/127.0.0.1/tcp",
"/ip4/127.0.0.1/p2p",
"/ip4/127.0.0.1/p2p/tcp",
"/p2p-circuit/50"
];
for address in &addresses { for address in &addresses {
assert!(address.parse::<Multiaddr>().is_err(), address.to_string()); assert!(address.parse::<Multiaddr>().is_err(), address.to_string());