From 82156deac531b764f5cb5bf9936a973aa1b34322 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Mon, 18 May 2020 11:21:55 +0300 Subject: [PATCH] Support /dns protocol in multiaddr (#1575) * 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 https://github.com/multiformats/multiaddr/pull/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 --- CHANGELOG.md | 5 +++ core/src/translation.rs | 7 ++++- misc/multiaddr/src/from_url.rs | 14 ++++----- misc/multiaddr/src/protocol.rs | 19 ++++++++++++ misc/multiaddr/tests/lib.rs | 49 +++++++++++++++--------------- transports/dns/src/lib.rs | 12 ++++++-- transports/websocket/src/framed.rs | 4 ++- 7 files changed, 74 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a926161b0dc..551ab1c3d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ - `libp2p-core`: Make the number of events buffered to/from tasks configurable. [PR 1574](https://github.com/libp2p/rust-libp2p/pull/1574) +- `libp2p-dns`, `parity-multiaddr`: Added support for the `/dns` multiaddr + protocol. Additionally, the `multiaddr::from_url` function will now use + `/dns` instead of `/dns4`. + [PR 1575](https://github.com/libp2p/rust-libp2p/pull/1575) + - `libp2p-noise`: Added the `X25519Spec` protocol suite which uses libp2p-noise-spec compliant signatures on static keys as well as the `/noise` protocol upgrade, hence providing a libp2p-noise-spec compliant diff --git a/core/src/translation.rs b/core/src/translation.rs index 5dab82a03d4..70700ca6ae8 100644 --- a/core/src/translation.rs +++ b/core/src/translation.rs @@ -37,9 +37,14 @@ use multiaddr::{Multiaddr, Protocol}; /// If the first [`Protocol`]s are not IP addresses, `None` is returned instead. pub fn address_translation(original: &Multiaddr, observed: &Multiaddr) -> Option { original.replace(0, move |proto| match proto { - Protocol::Ip4(_) | Protocol::Ip6(_) | Protocol::Dns4(_) | Protocol::Dns6(_) => match observed.iter().next() { + Protocol::Ip4(_) + | Protocol::Ip6(_) + | Protocol::Dns(_) + | Protocol::Dns4(_) + | Protocol::Dns6(_) => match observed.iter().next() { x @ Some(Protocol::Ip4(_)) => x, x @ Some(Protocol::Ip6(_)) => x, + x @ Some(Protocol::Dns(_)) => x, x @ Some(Protocol::Dns4(_)) => x, x @ Some(Protocol::Dns6(_)) => x, _ => None, diff --git a/misc/multiaddr/src/from_url.rs b/misc/multiaddr/src/from_url.rs index 454672da7db..738ed20b560 100644 --- a/misc/multiaddr/src/from_url.rs +++ b/misc/multiaddr/src/from_url.rs @@ -70,7 +70,7 @@ fn from_url_inner_http_ws(url: url::Url, lossy: bool) -> std::result::Result() { Protocol::from(ip) } else { - Protocol::Dns4(hostname.into()) + Protocol::Dns(hostname.into()) } } else { return Err(FromUrlErr::BadUrl); @@ -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] @@ -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] diff --git a/misc/multiaddr/src/protocol.rs b/misc/multiaddr/src/protocol.rs index 6491d0d8599..1ff0a8a637b 100644 --- a/misc/multiaddr/src/protocol.rs +++ b/misc/multiaddr/src/protocol.rs @@ -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; @@ -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>), @@ -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))) @@ -206,6 +212,11 @@ impl<'a> Protocol<'a> { let num = rdr.read_u16::()?; 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)?; @@ -345,6 +356,12 @@ impl<'a> Protocol<'a> { w.write_all(encode::u32(SCTP, &mut buf))?; w.write_u16::(*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(); @@ -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())), @@ -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), diff --git a/misc/multiaddr/tests/lib.rs b/misc/multiaddr/tests/lib.rs index dafeb21520a..d0928022303 100644 --- a/misc/multiaddr/tests/lib.rs +++ b/misc/multiaddr/tests/lib.rs @@ -76,36 +76,37 @@ struct Proto(Protocol<'static>); impl Arbitrary for Proto { fn arbitrary(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())) diff --git a/transports/dns/src/lib.rs b/transports/dns/src/lib.rs index 7c0a372c33e..82e5415119b 100644 --- a/transports/dns/src/lib.rs +++ b/transports/dns/src/lib.rs @@ -124,6 +124,7 @@ where // As an optimization, we immediately pass through if no component of the address contain // a DNS protocol. let contains_dns = addr.iter().any(|cmp| match cmp { + Protocol::Dns(_) => true, Protocol::Dns4(_) => true, Protocol::Dns6(_) => true, _ => false, @@ -139,7 +140,7 @@ where trace!("Dialing address with DNS: {}", addr); let resolve_futs = addr.iter() .map(|cmp| match cmp { - Protocol::Dns4(ref name) | Protocol::Dns6(ref name) => { + Protocol::Dns(ref name) | Protocol::Dns4(ref name) | Protocol::Dns6(ref name) => { let name = name.to_string(); let to_resolve = format!("{}:0", name); let (tx, rx) = oneshot::channel(); @@ -151,7 +152,12 @@ where }); }); - let is_dns4 = if let Protocol::Dns4(_) = cmp { true } else { false }; + let (dns4, dns6) = match cmp { + Protocol::Dns(_) => (true, true), + Protocol::Dns4(_) => (true, false), + Protocol::Dns6(_) => (false, true), + _ => unreachable!(), + }; async move { let list = rx.await @@ -166,7 +172,7 @@ where list.into_iter() .filter_map(|addr| { - if (is_dns4 && addr.is_ipv4()) || (!is_dns4 && addr.is_ipv6()) { + if (dns4 && addr.is_ipv4()) || (dns6 && addr.is_ipv6()) { Some(Protocol::from(addr)) } else { None diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index 191636a044f..182b81ab68b 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -353,6 +353,8 @@ fn host_and_dnsname(addr: &Multiaddr) -> Result<(String, Option Ok((format!("{}:{}", ip, port), None)), + (Some(Protocol::Dns(h)), Some(Protocol::Tcp(port))) => + Ok((format!("{}:{}", &h, port), Some(tls::dns_name_ref(&h)?.to_owned()))), (Some(Protocol::Dns4(h)), Some(Protocol::Tcp(port))) => Ok((format!("{}:{}", &h, port), Some(tls::dns_name_ref(&h)?.to_owned()))), (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) => @@ -371,7 +373,7 @@ fn location_to_multiaddr(location: &str) -> Result> { let mut a = Multiaddr::empty(); match url.host() { Some(url::Host::Domain(h)) => { - a.push(Protocol::Dns4(h.into())) + a.push(Protocol::Dns(h.into())) } Some(url::Host::Ipv4(ip)) => { a.push(Protocol::Ip4(ip))