Skip to content

Commit

Permalink
Support /dns protocol in multiaddr (#1575)
Browse files Browse the repository at this point in the history
* Add /dns protocol support to multiaddr

The /dns protocol has been added to the spec and has had a de-facto
meaning for years.
See multiformats/multiaddr#100

This adds address parsing and encoding support for /dns to the multiaddr
format library.

* Cover Dns protocol in multiaddr property tests

* transports/dns: Support the /dns protocol

* Support /dns protocol in address translation

* Translate an FQDN URL into a /dns multiaddr

* transports/websocket: Support /dns multiaddr

* Use the /dns protocol in websocket redirects

The whole thing with back-translating from an redirect URL looks a bit
baroque, but at least now the transport does not completely ignore IPv6
addresses resolved from a hostname in a redirect URL.

* Add CHANGELOG entry

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
  • Loading branch information
2 people authored and mxinden committed May 19, 2021
1 parent 5bc3181 commit f8236d4
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 31 deletions.
14 changes: 7 additions & 7 deletions src/from_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn from_url_inner_http_ws(url: url::Url, lossy: bool) -> std::result::Result<Mul
if let Ok(ip) = hostname.parse::<IpAddr>() {
Protocol::from(ip)
} else {
Protocol::Dns4(hostname.into())
Protocol::Dns(hostname.into())
}
} else {
return Err(FromUrlErr::BadUrl);
Expand Down Expand Up @@ -185,31 +185,31 @@ mod tests {
#[test]
fn dns_addr_ws() {
let addr = from_url("ws://example.com").unwrap();
assert_eq!(addr, "/dns4/example.com/tcp/80/ws".parse().unwrap());
assert_eq!(addr, "/dns/example.com/tcp/80/ws".parse().unwrap());
}

#[test]
fn dns_addr_http() {
let addr = from_url("http://example.com").unwrap();
assert_eq!(addr, "/dns4/example.com/tcp/80/http".parse().unwrap());
assert_eq!(addr, "/dns/example.com/tcp/80/http".parse().unwrap());
}

#[test]
fn dns_addr_wss() {
let addr = from_url("wss://example.com").unwrap();
assert_eq!(addr, "/dns4/example.com/tcp/443/wss".parse().unwrap());
assert_eq!(addr, "/dns/example.com/tcp/443/wss".parse().unwrap());
}

#[test]
fn dns_addr_https() {
let addr = from_url("https://example.com").unwrap();
assert_eq!(addr, "/dns4/example.com/tcp/443/https".parse().unwrap());
assert_eq!(addr, "/dns/example.com/tcp/443/https".parse().unwrap());
}

#[test]
fn bad_hostname() {
let addr = from_url("wss://127.0.0.1x").unwrap();
assert_eq!(addr, "/dns4/127.0.0.1x/tcp/443/wss".parse().unwrap());
assert_eq!(addr, "/dns/127.0.0.1x/tcp/443/wss".parse().unwrap());
}

#[test]
Expand All @@ -223,7 +223,7 @@ mod tests {
#[test]
fn dns_and_port() {
let addr = from_url("http://example.com:1000").unwrap();
assert_eq!(addr, "/dns4/example.com/tcp/1000/http".parse().unwrap());
assert_eq!(addr, "/dns/example.com/tcp/1000/http".parse().unwrap());
}

#[test]
Expand Down
19 changes: 19 additions & 0 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use unsigned_varint::{encode, decode};
use crate::onion_addr::Onion3Addr;

const DCCP: u32 = 33;
const DNS: u32 = 53;
const DNS4: u32 = 54;
const DNS6: u32 = 55;
const DNSADDR: u32 = 56;
Expand Down Expand Up @@ -66,6 +67,7 @@ const PATH_SEGMENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Protocol<'a> {
Dccp(u16),
Dns(Cow<'a, str>),
Dns4(Cow<'a, str>),
Dns6(Cow<'a, str>),
Dnsaddr(Cow<'a, str>),
Expand Down Expand Up @@ -125,6 +127,10 @@ impl<'a> Protocol<'a> {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
Ok(Protocol::Ip6(Ipv6Addr::from_str(s)?))
}
"dns" => {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
Ok(Protocol::Dns(Cow::Borrowed(s)))
}
"dns4" => {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
Ok(Protocol::Dns4(Cow::Borrowed(s)))
Expand Down Expand Up @@ -206,6 +212,11 @@ impl<'a> Protocol<'a> {
let num = rdr.read_u16::<BigEndian>()?;
Ok((Protocol::Dccp(num), rest))
}
DNS => {
let (n, input) = decode::usize(input)?;
let (data, rest) = split_at(n, input)?;
Ok((Protocol::Dns(Cow::Borrowed(str::from_utf8(data)?)), rest))
}
DNS4 => {
let (n, input) = decode::usize(input)?;
let (data, rest) = split_at(n, input)?;
Expand Down Expand Up @@ -345,6 +356,12 @@ impl<'a> Protocol<'a> {
w.write_all(encode::u32(SCTP, &mut buf))?;
w.write_u16::<BigEndian>(*port)?
}
Protocol::Dns(s) => {
w.write_all(encode::u32(DNS, &mut buf))?;
let bytes = s.as_bytes();
w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?;
w.write_all(&bytes)?
}
Protocol::Dns4(s) => {
w.write_all(encode::u32(DNS4, &mut buf))?;
let bytes = s.as_bytes();
Expand Down Expand Up @@ -421,6 +438,7 @@ impl<'a> Protocol<'a> {
use self::Protocol::*;
match self {
Dccp(a) => Dccp(a),
Dns(cow) => Dns(Cow::Owned(cow.into_owned())),
Dns4(cow) => Dns4(Cow::Owned(cow.into_owned())),
Dns6(cow) => Dns6(Cow::Owned(cow.into_owned())),
Dnsaddr(cow) => Dnsaddr(Cow::Owned(cow.into_owned())),
Expand Down Expand Up @@ -454,6 +472,7 @@ impl<'a> fmt::Display for Protocol<'a> {
use self::Protocol::*;
match self {
Dccp(port) => write!(f, "/dccp/{}", port),
Dns(s) => write!(f, "/dns/{}", s),
Dns4(s) => write!(f, "/dns4/{}", s),
Dns6(s) => write!(f, "/dns6/{}", s),
Dnsaddr(s) => write!(f, "/dnsaddr/{}", s),
Expand Down
49 changes: 25 additions & 24 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,36 +76,37 @@ struct Proto(Protocol<'static>);
impl Arbitrary for Proto {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
use Protocol::*;
match g.gen_range(0, 24) { // TODO: Add Protocol::Quic
match g.gen_range(0, 25) { // TODO: Add Protocol::Quic
0 => Proto(Dccp(g.gen())),
1 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))),
2 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))),
3 => Proto(Http),
4 => Proto(Https),
5 => Proto(Ip4(Ipv4Addr::arbitrary(g))),
6 => Proto(Ip6(Ipv6Addr::arbitrary(g))),
7 => Proto(P2pWebRtcDirect),
8 => Proto(P2pWebRtcStar),
9 => Proto(P2pWebSocketStar),
10 => Proto(Memory(g.gen())),
1 => Proto(Dns(Cow::Owned(SubString::arbitrary(g).0))),
2 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))),
3 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))),
4 => Proto(Http),
5 => Proto(Https),
6 => Proto(Ip4(Ipv4Addr::arbitrary(g))),
7 => Proto(Ip6(Ipv6Addr::arbitrary(g))),
8 => Proto(P2pWebRtcDirect),
9 => Proto(P2pWebRtcStar),
10 => Proto(P2pWebSocketStar),
11 => Proto(Memory(g.gen())),
// TODO: impl Arbitrary for Multihash:
11 => Proto(P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))),
12 => Proto(P2pCircuit),
13 => Proto(Quic),
14 => Proto(Sctp(g.gen())),
15 => Proto(Tcp(g.gen())),
16 => Proto(Udp(g.gen())),
17 => Proto(Udt),
18 => Proto(Unix(Cow::Owned(SubString::arbitrary(g).0))),
19 => Proto(Utp),
20 => Proto(Ws("/".into())),
21 => Proto(Wss("/".into())),
22 => {
12 => Proto(P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))),
13 => Proto(P2pCircuit),
14 => Proto(Quic),
15 => Proto(Sctp(g.gen())),
16 => Proto(Tcp(g.gen())),
17 => Proto(Udp(g.gen())),
18 => Proto(Udt),
19 => Proto(Unix(Cow::Owned(SubString::arbitrary(g).0))),
20 => Proto(Utp),
21 => Proto(Ws("/".into())),
22 => Proto(Wss("/".into())),
23 => {
let mut a = [0; 10];
g.fill(&mut a);
Proto(Onion(Cow::Owned(a), g.gen_range(1, std::u16::MAX)))
},
23 => {
24 => {
let mut a = [0; 35];
g.fill_bytes(&mut a);
Proto(Onion3((a, g.gen_range(1, std::u16::MAX)).into()))
Expand Down

0 comments on commit f8236d4

Please sign in to comment.