diff --git a/misc/mdns/src/behaviour.rs b/misc/mdns/src/behaviour.rs index 01ae4437c77..d56768cd8cc 100644 --- a/misc/mdns/src/behaviour.rs +++ b/misc/mdns/src/behaviour.rs @@ -60,6 +60,15 @@ impl Mdns { marker: PhantomData, }) } + /// Builds a new `Mdns` behaviour with legacy IPFS support + pub fn new_with_legacy() -> io::Result> { + Ok(Mdns { + service: MdnsService::new_with_legacy()?, + discovered_nodes: SmallVec::new(), + closest_expiration: None, + marker: PhantomData, + }) + } /// Returns true if the given `PeerId` is in the list of nodes discovered through mDNS. pub fn has_node(&self, peer_id: &PeerId) -> bool { @@ -240,7 +249,7 @@ where if let Some(new_addr) = params.nat_traversal(&addr, &observed) { addrs.push(new_addr); } - addrs.push(addr); + addrs.push(addr.clone()); } for addr in addrs { diff --git a/misc/mdns/src/dns.rs b/misc/mdns/src/dns.rs index da4e1e50e17..82d8e974beb 100644 --- a/misc/mdns/src/dns.rs +++ b/misc/mdns/src/dns.rs @@ -21,7 +21,7 @@ //! Contains methods that handle the DNS encoding and decoding capabilities not available in the //! `dns_parser` library. -use crate::{META_QUERY_SERVICE, SERVICE_NAME}; +use crate::{META_QUERY_SERVICE, SERVICE_NAME, LEGACY_SERVICE_NAME_GO, LEGACY_SERVICE_NAME_JS}; use data_encoding; use libp2p_core::{Multiaddr, PeerId}; use rand; @@ -49,7 +49,19 @@ pub fn decode_character_string(mut from: &[u8]) -> Result, ()> { /// Builds the binary representation of a DNS query to send on the network. pub fn build_query() -> Vec { - let mut out = Vec::with_capacity(33); + build_query_inner(SERVICE_NAME) +} + +/// Builds the binary representations of legacy DNS queries to send on the network. +pub fn build_legacy_queries() -> (Vec, Vec) { + return ( + build_query_inner(LEGACY_SERVICE_NAME_JS), + build_query_inner(LEGACY_SERVICE_NAME_GO) + ) +} + +fn build_query_inner(name: &[u8]) -> Vec { + let mut out = vec![]; // Program-generated transaction ID; unused by our implementation. append_u16(&mut out, rand::random()); @@ -67,15 +79,12 @@ pub fn build_query() -> Vec { // Our single question. // The name. - append_qname(&mut out, SERVICE_NAME); + append_qname(&mut out, name); // Flags. append_u16(&mut out, 0x0c); append_u16(&mut out, 0x01); - // Since the output is constant, we reserve the right amount ahead of time. - // If this assert fails, adjust the capacity of `out` in the source code. - debug_assert_eq!(out.capacity(), out.len()); out } @@ -86,7 +95,7 @@ pub fn build_query_response( id: u16, peer_id: PeerId, addresses: impl ExactSizeIterator, - ttl: Duration, + ttl: Duration ) -> Result, MdnsResponseError> { // Convert the TTL into seconds. let ttl = duration_to_secs(ttl); @@ -147,6 +156,7 @@ pub fn build_query_response( Ok(out) } + /// Builds the response to the DNS query. pub fn build_service_discovery_response(id: u16, ttl: Duration) -> Vec { // Convert the TTL into seconds. diff --git a/misc/mdns/src/lib.rs b/misc/mdns/src/lib.rs index 767ef9d026a..03b61b24a4e 100644 --- a/misc/mdns/src/lib.rs +++ b/misc/mdns/src/lib.rs @@ -32,6 +32,10 @@ /// Hardcoded name of the mDNS service. Part of the mDNS libp2p specifications. const SERVICE_NAME: &[u8] = b"_p2p._udp.local"; +/// Hardcoded name of the mDNS service as used by legacy IPFS services in GO +const LEGACY_SERVICE_NAME_GO: &[u8] = b"_ipfs-discovery._udp.local"; +/// Hardcoded name of the mDNS service as used by legacy IPFS services in JS +const LEGACY_SERVICE_NAME_JS: &[u8] = b"ipfs.local"; /// Hardcoded name of the service used for DNS-SD. const META_QUERY_SERVICE: &[u8] = b"_services._dns-sd._udp.local"; diff --git a/misc/mdns/src/service.rs b/misc/mdns/src/service.rs index d668b59d8e6..5487485a9e6 100644 --- a/misc/mdns/src/service.rs +++ b/misc/mdns/src/service.rs @@ -18,8 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{SERVICE_NAME, META_QUERY_SERVICE, dns}; -use dns_parser::{Packet, RData}; +use crate::{SERVICE_NAME, LEGACY_SERVICE_NAME_GO, LEGACY_SERVICE_NAME_JS, META_QUERY_SERVICE, dns}; +use dns_parser::{Packet, RData, ResourceRecord}; use futures::{prelude::*, task}; use libp2p_core::{Multiaddr, PeerId}; use multiaddr::Protocol; @@ -91,16 +91,16 @@ pub use dns::MdnsResponseError; /// }).for_each(|_| Ok(())); /// # } pub struct MdnsService { - /// Main socket for listening. + /// Main socket for listening and sending queries socket: UdpSocket, - /// Socket for sending queries on the network. - query_socket: UdpSocket, /// Interval for sending queries. query_interval: Interval, /// Whether we send queries on the network at all. /// Note that we still need to have an interval for querying, as we need to wake up the socket /// regularly to recover from errors. Otherwise we could simply use an `Option`. silent: bool, + /// Whether this will support legacy IPFS discovery + legacy_support: bool, /// Buffer used for receiving data from the main socket. recv_buffer: [u8; 2048], /// Buffers pending to send on the main socket. @@ -113,17 +113,23 @@ impl MdnsService { /// Starts a new mDNS service. #[inline] pub fn new() -> io::Result { - Self::new_inner(false) + Self::new_inner(false, false) } /// Same as `new`, but we don't send automatically send queries on the network. #[inline] pub fn silent() -> io::Result { - Self::new_inner(true) + Self::new_inner(true, false) + } + + /// Starts a new mDNS service with legacy IPFS discovery support + #[inline] + pub fn new_with_legacy() -> io::Result { + Self::new_inner(false, true) } /// Starts a new mDNS service. - fn new_inner(silent: bool) -> io::Result { + fn new_inner(silent: bool, legacy_support: bool) -> io::Result { let socket = { #[cfg(unix)] fn platform_specific(s: &net2::UdpBuilder) -> io::Result<()> { @@ -146,9 +152,9 @@ impl MdnsService { Ok(MdnsService { socket, - query_socket: UdpSocket::bind(&From::from(([0, 0, 0, 0], 0)))?, query_interval: Interval::new(Instant::now(), Duration::from_secs(20)), silent, + legacy_support, recv_buffer: [0; 2048], send_buffers: Vec::new(), query_send_buffers: Vec::new(), @@ -165,6 +171,11 @@ impl MdnsService { if !self.silent { let query = dns::build_query(); self.query_send_buffers.push(query.to_vec()); + if self.legacy_support { + let (js_query, go_query) = dns::build_legacy_queries(); + self.query_send_buffers.push(js_query.to_vec()); + self.query_send_buffers.push(go_query.to_vec()); + } } } Ok(Async::NotReady) => (), @@ -199,7 +210,7 @@ impl MdnsService { while !self.query_send_buffers.is_empty() { let to_send = self.query_send_buffers.remove(0); match self - .query_socket + .socket .poll_send_to(&to_send, &From::from(([224, 0, 0, 251], 5353))) { Ok(Async::Ready(bytes_written)) => { @@ -239,7 +250,8 @@ impl MdnsService { .iter() .any(|q| q.qname.to_string().as_bytes() == META_QUERY_SERVICE) { - // TODO: what if multiple questions, one with SERVICE_NAME and one with META_QUERY_SERVICE? + // TODO: what if multiple questions, one with SERVICE_NAME + // and one with META_QUERY_SERVICE? return Async::Ready(MdnsPacket::ServiceDiscovery( MdnsServiceDiscovery { from, @@ -326,15 +338,18 @@ impl<'a> MdnsQuery<'a> { self, peer_id: PeerId, addresses: TAddresses, - ttl: Duration, + ttl: Duration ) -> Result<(), MdnsResponseError> where TAddresses: IntoIterator, TAddresses::IntoIter: ExactSizeIterator, { - let response = - dns::build_query_response(self.query_id, peer_id, addresses.into_iter(), ttl)?; - self.send_buffers.push(response); + self.send_buffers.push(dns::build_query_response( + self.query_id, + peer_id, + addresses.into_iter(), + ttl + )?); Ok(()) } @@ -395,48 +410,114 @@ pub struct MdnsResponse<'a> { } impl<'a> MdnsResponse<'a> { - /// Returns the list of peers that have been reported in this packet. - /// - /// > **Note**: Keep in mind that this will also contain the responses we sent ourselves. - pub fn discovered_peers<'b>(&'b self) -> impl Iterator> { + + fn get_mapper<'b>(&'b self) -> Box Option + 'b> { let packet = &self.packet; - self.packet.answers.iter().filter_map(move |record| { - if record.name.to_string().as_bytes() != SERVICE_NAME { - return None; + + let matched = self.packet.answers.iter().filter_map(|record| + match record.name.to_string().as_bytes() { + SERVICE_NAME => Some(false), // we've found a regular packet + LEGACY_SERVICE_NAME_JS | LEGACY_SERVICE_NAME_GO => Some(true), // legacy + _ => None } + ).next(); + + if Some(true) == matched { + // Legacy support + if let Some((peer_id, port, peer_name)) = packet.answers.iter().filter_map(|r| { + let name = r.name.to_string(); + let name_bytes = name.as_bytes(); + if name_bytes.ends_with(LEGACY_SERVICE_NAME_JS) + || name_bytes.ends_with(LEGACY_SERVICE_NAME_GO) + { + if let RData::SRV(srv) = r.data { + if let Some(Ok(peer_id)) = name.splitn(2, |c| c == '.') + .next().map(|n| n.parse::()) + { + return Some((peer_id, srv.port, srv.target.to_string())) + } + } + } + None + }).next() { + return Box::new(move |record: &ResourceRecord| { + if record.name.to_string() != peer_name { + // skip non DNS records + return None + } - let record_value = match record.data { - RData::PTR(record) => record.0.to_string(), - _ => return None, - }; + let addr = match record.data { + RData::A(addr) => addr.0, + _ => return None + }; + + let addr = match format!( + "/ip4/{}/tcp/{}", + addr, + port + ).parse::() { + Ok(m) => m, + _ => return None + }; + println!("Legacy peer detected: {:?}", addr); + return Some(MdnsPeer { + peer_id: peer_id.clone(), + addresses: vec![addr], + ttl: record.ttl, + }) + }); + } + } + + if matched == Some(false) { + // regular match + return Box::new(move |record: &ResourceRecord | { + if record.name.to_string().as_bytes() != SERVICE_NAME { + return None + } + + let record_value = match record.data { + RData::PTR(record) => record.0.to_string(), + _ => return None, + }; - let peer_name = { let mut iter = record_value.splitn(2, |c| c == '.'); - let name = match iter.next() { + let peer_name = match iter.next() { Some(n) => n.to_owned(), None => return None, }; - if iter.next().map(|v| v.as_bytes()) != Some(SERVICE_NAME) { - return None; + + let decoded = match iter.next().map(|v| v.as_bytes()) { + Some(SERVICE_NAME) => data_encoding::BASE32_DNSCURVE.decode( + peer_name.as_bytes()), + _ => return None + }.map(|bytes| PeerId::from_bytes(bytes)); + + // in modern versions, we receive everything in this record + // and can return it. + if let Ok(Ok(peer_id)) = decoded { + Some(MdnsPeer::parse( + peer_id, + record.ttl, + packet, + record_value, + )) + } else { + None } - name - }; - - let peer_id = match data_encoding::BASE32_DNSCURVE.decode(peer_name.as_bytes()) { - Ok(bytes) => match PeerId::from_bytes(bytes) { - Ok(id) => id, - Err(_) => return None, - }, - Err(_) => return None, - }; - - Some(MdnsPeer { - packet, - record_value, - peer_id, - ttl: record.ttl, }) - }) + } + + // fallback + Box::new(|_record: &ResourceRecord| None) + } + /// Returns the list of peers that have been reported in this packet. + /// + /// > **Note**: Keep in mind that this will also contain the responses we sent ourselves. + pub fn discovered_peers<'b>(&'b self) -> impl Iterator + 'b { + let mapper_fn = self.get_mapper(); + + self.packet.answers.iter().filter_map(move |r| mapper_fn(r)) } /// Source address of the packet. @@ -455,24 +536,73 @@ impl<'a> fmt::Debug for MdnsResponse<'a> { } /// A peer discovered by the service. -pub struct MdnsPeer<'a> { - /// The original packet which will be used to determine the addresses. - packet: &'a Packet<'a>, - /// Cached value of `concat(base32(peer_id), service name)`. - record_value: String, +pub struct MdnsPeer { + addresses: Vec, /// Id of the peer. peer_id: PeerId, /// TTL of the record in seconds. ttl: u32, } -impl<'a> MdnsPeer<'a> { +impl MdnsPeer { /// Returns the id of the peer. #[inline] pub fn id(&self) -> &PeerId { &self.peer_id } + /// Create new entry from packet + pub fn parse<'a>( + peer_id: PeerId, + ttl: u32, + packet: &'a Packet<'a>, + record_value: String + ) -> Self { + MdnsPeer { + ttl, + peer_id: peer_id.clone(), + addresses: packet + .additional + .iter() + .filter_map(move |add_record| { + if add_record.name.to_string() != record_value { + return None; + } + + if let RData::TXT(ref txt) = add_record.data { + Some(txt) + } else { + None + } + }) + .flat_map(|txt| txt.iter()) + .filter_map(move |txt| { + // TODO: wrong, txt can be multiple character strings + let addr = match dns::decode_character_string(txt) { + Ok(a) => a, + Err(_) => return None, + }; + if !addr.starts_with(b"dnsaddr=") { + return None; + } + let addr = match str::from_utf8(&addr[8..]) { + Ok(a) => a, + Err(_) => return None, + }; + let mut addr = match addr.parse::() { + Ok(a) => a, + Err(_) => return None, + }; + match addr.pop() { + Some(Protocol::P2p(ref remote_id)) if remote_id == &peer_id => (), + _ => return None, + }; + Some(addr) + }) + .collect() + } + } + /// Returns the requested time-to-live for the record. #[inline] pub fn ttl(&self) -> Duration { @@ -482,51 +612,12 @@ impl<'a> MdnsPeer<'a> { /// Returns the list of addresses the peer says it is listening on. /// /// Filters out invalid addresses. - pub fn addresses<'b>(&'b self) -> impl Iterator + 'b { - let my_peer_id = &self.peer_id; - let record_value = &self.record_value; - self.packet - .additional - .iter() - .filter_map(move |add_record| { - if &add_record.name.to_string() != record_value { - return None; - } - - if let RData::TXT(ref txt) = add_record.data { - Some(txt) - } else { - None - } - }) - .flat_map(|txt| txt.iter()) - .filter_map(move |txt| { - // TODO: wrong, txt can be multiple character strings - let addr = match dns::decode_character_string(txt) { - Ok(a) => a, - Err(_) => return None, - }; - if !addr.starts_with(b"dnsaddr=") { - return None; - } - let addr = match str::from_utf8(&addr[8..]) { - Ok(a) => a, - Err(_) => return None, - }; - let mut addr = match addr.parse::() { - Ok(a) => a, - Err(_) => return None, - }; - match addr.pop() { - Some(Protocol::P2p(ref peer_id)) if peer_id == my_peer_id => (), - _ => return None, - }; - Some(addr) - }) + pub fn addresses<'b>(&'b self) -> impl Iterator + 'b { + self.addresses.iter() } } -impl<'a> fmt::Debug for MdnsPeer<'a> { +impl fmt::Debug for MdnsPeer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MdnsPeer") .field("peer_id", &self.peer_id) @@ -555,7 +646,7 @@ mod tests { match packet { MdnsPacket::Query(query) => { - query.respond(peer_id.clone(), None, Duration::from_secs(120)).unwrap(); + query.respond(peer_id.clone(), None, Duration::from_secs(120), false).unwrap(); } MdnsPacket::Response(response) => { for peer in response.discovered_peers() {