diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 64cf9263f8aa..950019877cc7 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -10,9 +10,10 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] +anyhow = "1.0.42" async-io = "1.3.1" +byteorder = "1.4.3" data-encoding = "2.3.2" -dns-parser = "0.8.0" futures = "0.3.13" if-watch = "0.2.0" lazy_static = "1.4.0" diff --git a/protocols/mdns/src/behaviour.rs b/protocols/mdns/src/behaviour.rs index c819607523f4..804c4e461024 100644 --- a/protocols/mdns/src/behaviour.rs +++ b/protocols/mdns/src/behaviour.rs @@ -18,16 +18,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::packet::MdnsPacket; use crate::IPV4_MDNS_MULTICAST_ADDRESS; -use crate::dns::{build_query, build_query_response, build_service_discovery_response}; -use crate::query::MdnsPacket; use async_io::{Async, Timer}; use futures::prelude::*; use if_watch::{IfEvent, IfWatcher}; use libp2p_core::connection::ListenerId; -use libp2p_core::{ - address_translation, multiaddr::Protocol, Multiaddr, PeerId, -}; +use libp2p_core::{address_translation, multiaddr::Protocol, Multiaddr, PeerId}; use libp2p_swarm::{ protocols_handler::DummyProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, ProtocolsHandler, @@ -197,55 +194,65 @@ impl Mdns { .set_interval_at(Instant::now(), self.query_interval); } - fn inject_mdns_packet(&mut self, packet: MdnsPacket, params: &impl PollParameters) { + fn send_packet(&mut self, packet: MdnsPacket) { + for packet in packet.to_encoded_packets() { + self.send_buffer.push_back(packet); + } + } + + fn inject_mdns_packet( + &mut self, + packet: MdnsPacket, + from: SocketAddr, + params: &impl PollParameters, + ) { match packet { - MdnsPacket::Query(query) => { + MdnsPacket::PeerRequest { query_id } => { self.reset_timer(); log::trace!("sending response"); - for packet in build_query_response( - query.query_id(), - *params.local_peer_id(), - params.listened_addresses(), - self.ttl, - ) { - self.send_buffer.push_back(packet); - } + let response = MdnsPacket::PeerResponse { + query_id, + peer_id: *params.local_peer_id(), + addresses: params.listened_addresses().collect(), + ttl: self.ttl, + }; + self.send_packet(response); } - MdnsPacket::Response(response) => { + MdnsPacket::PeerResponse { + query_id: _, + peer_id, + addresses, + ttl, + } => { // We replace the IP address with the address we observe the // remote as and the address they listen on. - let obs_ip = Protocol::from(response.remote_addr().ip()); - let obs_port = Protocol::Udp(response.remote_addr().port()); + let obs_ip = Protocol::from(from.ip()); + let obs_port = Protocol::Udp(from.port()); let observed: Multiaddr = iter::once(obs_ip).chain(iter::once(obs_port)).collect(); let mut discovered: SmallVec<[_; 4]> = SmallVec::new(); - for peer in response.discovered_peers() { - if peer.id() == params.local_peer_id() { - continue; - } - - let new_expiration = Instant::now() + peer.ttl(); - - let mut addrs: Vec = Vec::new(); - for addr in peer.addresses() { - if let Some(new_addr) = address_translation(&addr, &observed) { - addrs.push(new_addr.clone()) - } - addrs.push(addr.clone()) + if peer_id == *params.local_peer_id() { + return; + } + let new_expiration = Instant::now() + ttl; + let mut addrs: Vec = Vec::new(); + for addr in addresses { + if let Some(new_addr) = address_translation(&addr, &observed) { + addrs.push(new_addr.clone()) } - - for addr in addrs { - if let Some((_, _, cur_expires)) = self - .discovered_nodes - .iter_mut() - .find(|(p, a, _)| p == peer.id() && *a == addr) - { - *cur_expires = cmp::max(*cur_expires, new_expiration); - } else { - self.discovered_nodes - .push((*peer.id(), addr.clone(), new_expiration)); - discovered.push((*peer.id(), addr)); - } + addrs.push(addr.clone()) + } + for addr in addrs { + if let Some((_, _, cur_expires)) = self + .discovered_nodes + .iter_mut() + .find(|(p, a, _)| p == &peer_id && *a == addr) + { + *cur_expires = cmp::max(*cur_expires, new_expiration); + } else { + self.discovered_nodes + .push((peer_id, addr.clone(), new_expiration)); + discovered.push((peer_id, addr)); } } @@ -262,10 +269,14 @@ impl Mdns { inner: discovered.into_iter(), })); } - MdnsPacket::ServiceDiscovery(disc) => { - let resp = build_service_discovery_response(disc.query_id(), self.ttl); - self.send_buffer.push_back(resp); + MdnsPacket::ServiceRequest { query_id } => { + let response = MdnsPacket::ServiceResponse { + query_id, + ttl: self.ttl, + }; + self.send_packet(response); } + MdnsPacket::ServiceResponse { .. } => {} } } } @@ -373,12 +384,10 @@ impl NetworkBehaviour for Mdns { .recv_from(&mut self.recv_buffer) .now_or_never() { - Some(Ok((len, from))) => { - if let Some(packet) = MdnsPacket::new_from_bytes(&self.recv_buffer[..len], from) - { - self.inject_mdns_packet(packet, params); - } - } + Some(Ok((len, from))) => match MdnsPacket::from_bytes(&self.recv_buffer[..len]) { + Ok(packet) => self.inject_mdns_packet(packet, from, params), + Err(err) => log::error!("failed to parse mdns packet from {}: {}", from, err), + }, Some(Err(err)) => log::error!("Failed reading datagram: {}", err), _ => {} } @@ -397,7 +406,9 @@ impl NetworkBehaviour for Mdns { } } else if Pin::new(&mut self.timeout).poll_next(cx).is_ready() { log::trace!("sending query"); - self.send_buffer.push_back(build_query()); + self.send_packet(MdnsPacket::PeerRequest { + query_id: rand::random(), + }); } else { break; } diff --git a/protocols/mdns/src/dns.rs b/protocols/mdns/src/dns.rs deleted file mode 100644 index 92fd980c89d2..000000000000 --- a/protocols/mdns/src/dns.rs +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! (M)DNS encoding and decoding on top of the `dns_parser` library. - -use crate::{META_QUERY_SERVICE, SERVICE_NAME}; -use libp2p_core::{Multiaddr, PeerId}; -use std::{borrow::Cow, cmp, error, fmt, str, time::Duration}; - -/// Maximum size of a DNS label as per RFC1035. -const MAX_LABEL_LENGTH: usize = 63; - -/// DNS TXT records can have up to 255 characters as a single string value. -/// -/// Current values are usually around 170-190 bytes long, varying primarily -/// with the length of the contained `Multiaddr`. -const MAX_TXT_VALUE_LENGTH: usize = 255; - -/// A conservative maximum size (in bytes) of a complete TXT record, -/// as encoded by [`append_txt_record`]. -const MAX_TXT_RECORD_SIZE: usize = MAX_TXT_VALUE_LENGTH + 45; - -/// The maximum DNS packet size is 9000 bytes less the maximum -/// sizes of the IP (60) and UDP (8) headers. -const MAX_PACKET_SIZE: usize = 9000 - 68; - -/// A conservative maximum number of records that can be packed into -/// a single DNS UDP packet, allowing up to 100 bytes of MDNS packet -/// header data to be added by [`query_response_packet()`]. -const MAX_RECORDS_PER_PACKET: usize = (MAX_PACKET_SIZE - 100) / MAX_TXT_RECORD_SIZE; - -/// An encoded MDNS packet. -pub type MdnsPacket = Vec; - -/// Decodes a `` (as defined by RFC1035) into a `Vec` of ASCII characters. -// TODO: better error type? -pub fn decode_character_string(mut from: &[u8]) -> Result, ()> { - if from.is_empty() { - return Ok(Cow::Owned(Vec::new())); - } - - // Remove the initial and trailing " if any. - if from[0] == b'"' { - if from.len() == 1 || from.last() != Some(&b'"') { - return Err(()); - } - let len = from.len(); - from = &from[1..len - 1]; - } - - // TODO: remove the backslashes if any - Ok(Cow::Borrowed(from)) -} - -/// Builds the binary representation of a DNS query to send on the network. -pub fn build_query() -> MdnsPacket { - let mut out = Vec::with_capacity(33); - - // Program-generated transaction ID; unused by our implementation. - append_u16(&mut out, rand::random()); - - // 0x0 flag for a regular query. - append_u16(&mut out, 0x0); - - // Number of questions. - append_u16(&mut out, 0x1); - - // Number of answers, authorities, and additionals. - append_u16(&mut out, 0x0); - append_u16(&mut out, 0x0); - append_u16(&mut out, 0x0); - - // Our single question. - // The name. - append_qname(&mut out, SERVICE_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 -} - -/// Builds the response to an address discovery DNS query. -/// -/// If there are more than 2^16-1 addresses, ignores the rest. -pub fn build_query_response( - id: u16, - peer_id: PeerId, - addresses: impl ExactSizeIterator, - ttl: Duration, -) -> Vec { - // Convert the TTL into seconds. - let ttl = duration_to_secs(ttl); - - // Add a limit to 2^16-1 addresses, as the protocol limits to this number. - let addresses = addresses.take(65535); - - let peer_id_bytes = encode_peer_id(&peer_id); - debug_assert!(peer_id_bytes.len() <= 0xffff); - - // The accumulated response packets. - let mut packets = Vec::new(); - - // The records accumulated per response packet. - let mut records = Vec::with_capacity(addresses.len() * MAX_TXT_RECORD_SIZE); - - // Encode the addresses as TXT records, and multiple TXT records into a - // response packet. - for addr in addresses { - let txt_to_send = format!("dnsaddr={}/p2p/{}", addr.to_string(), peer_id.to_base58()); - let mut txt_record = Vec::with_capacity(txt_to_send.len()); - match append_txt_record(&mut txt_record, &peer_id_bytes, ttl, &txt_to_send) { - Ok(()) => { - records.push(txt_record); - } - Err(e) => { - log::warn!("Excluding address {} from response: {:?}", addr, e); - } - } - - if records.len() == MAX_RECORDS_PER_PACKET { - packets.push(query_response_packet(id, &peer_id_bytes, &records, ttl)); - records.clear(); - } - } - - // If there are still unpacked records, i.e. if the number of records is not - // a multiple of `MAX_RECORDS_PER_PACKET`, create a final packet. - if !records.is_empty() { - packets.push(query_response_packet(id, &peer_id_bytes, &records, ttl)); - } - - // If no packets have been built at all, because `addresses` is empty, - // construct an empty response packet. - if packets.is_empty() { - packets.push(query_response_packet(id, &peer_id_bytes, &Vec::new(), ttl)); - } - - packets -} - -/// Builds the response to a service discovery DNS query. -pub fn build_service_discovery_response(id: u16, ttl: Duration) -> MdnsPacket { - // Convert the TTL into seconds. - let ttl = duration_to_secs(ttl); - - // This capacity was determined empirically. - let mut out = Vec::with_capacity(69); - - append_u16(&mut out, id); - // 0x84 flag for an answer. - append_u16(&mut out, 0x8400); - // Number of questions, answers, authorities, additionals. - append_u16(&mut out, 0x0); - append_u16(&mut out, 0x1); - append_u16(&mut out, 0x0); - append_u16(&mut out, 0x0); - - // Our single answer. - // The name. - append_qname(&mut out, META_QUERY_SERVICE); - - // Flags. - append_u16(&mut out, 0x000c); - append_u16(&mut out, 0x8001); - - // TTL for the answer - append_u32(&mut out, ttl); - - // Service name. - { - let mut name = Vec::with_capacity(SERVICE_NAME.len() + 2); - append_qname(&mut name, SERVICE_NAME); - append_u16(&mut out, name.len() as u16); - out.extend_from_slice(&name); - } - - // Since the output size 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 -} - -/// Constructs an MDNS query response packet for an address lookup. -fn query_response_packet(id: u16, peer_id: &[u8], records: &[Vec], ttl: u32) -> MdnsPacket { - let mut out = Vec::with_capacity(records.len() * MAX_TXT_RECORD_SIZE); - - append_u16(&mut out, id); - // 0x84 flag for an answer. - append_u16(&mut out, 0x8400); - // Number of questions, answers, authorities, additionals. - append_u16(&mut out, 0x0); - append_u16(&mut out, 0x1); - append_u16(&mut out, 0x0); - append_u16(&mut out, records.len() as u16); - - // Our single answer. - // The name. - append_qname(&mut out, SERVICE_NAME); - - // Flags. - append_u16(&mut out, 0x000c); - append_u16(&mut out, 0x0001); - - // TTL for the answer - append_u32(&mut out, ttl); - - // Peer Id. - append_u16(&mut out, peer_id.len() as u16); - out.extend_from_slice(&peer_id); - - // The TXT records. - for record in records { - out.extend_from_slice(&record); - } - - out -} - -/// Returns the number of secs of a duration. -fn duration_to_secs(duration: Duration) -> u32 { - let secs = duration - .as_secs() - .saturating_add(if duration.subsec_nanos() > 0 { 1 } else { 0 }); - cmp::min(secs, From::from(u32::max_value())) as u32 -} - -/// Appends a big-endian u32 to `out`. -fn append_u32(out: &mut Vec, value: u32) { - out.push(((value >> 24) & 0xff) as u8); - out.push(((value >> 16) & 0xff) as u8); - out.push(((value >> 8) & 0xff) as u8); - out.push((value & 0xff) as u8); -} - -/// Appends a big-endian u16 to `out`. -fn append_u16(out: &mut Vec, value: u16) { - out.push(((value >> 8) & 0xff) as u8); - out.push((value & 0xff) as u8); -} - -/// If a peer ID is longer than 63 characters, split it into segments to -/// be compatible with RFC 1035. -fn segment_peer_id(peer_id: String) -> String { - // Guard for the most common case - if peer_id.len() <= MAX_LABEL_LENGTH { - return peer_id; - } - - // This will only perform one allocation except in extreme circumstances. - let mut out = String::with_capacity(peer_id.len() + 8); - - for (idx, chr) in peer_id.chars().enumerate() { - if idx > 0 && idx % MAX_LABEL_LENGTH == 0 { - out.push('.'); - } - out.push(chr); - } - out -} - -/// Combines and encodes a `PeerId` and service name for a DNS query. -fn encode_peer_id(peer_id: &PeerId) -> Vec { - // DNS-safe encoding for the Peer ID - let raw_peer_id = data_encoding::BASE32_DNSCURVE.encode(&peer_id.to_bytes()); - // ensure we don't have any labels over 63 bytes long - let encoded_peer_id = segment_peer_id(raw_peer_id); - let service_name = str::from_utf8(SERVICE_NAME).expect("SERVICE_NAME is always ASCII"); - let peer_name = [&encoded_peer_id, service_name].join("."); - - // allocate with a little extra padding for QNAME encoding - let mut peer_id_bytes = Vec::with_capacity(peer_name.len() + 32); - append_qname(&mut peer_id_bytes, peer_name.as_bytes()); - - peer_id_bytes -} - -/// Appends a `QNAME` (as defined by RFC1035) to the `Vec`. -/// -/// # Panic -/// -/// Panics if `name` has a zero-length component or a component that is too long. -/// This is fine considering that this function is not public and is only called in a controlled -/// environment. -/// -fn append_qname(out: &mut Vec, name: &[u8]) { - debug_assert!(name.is_ascii()); - - for element in name.split(|&c| c == b'.') { - assert!(element.len() < 64, "Service name has a label too long"); - assert_ne!(element.len(), 0, "Service name contains zero length label"); - out.push(element.len() as u8); - for chr in element.iter() { - out.push(*chr); - } - } - - out.push(0); -} - -/// Appends a `` (as defined by RFC1035) to the `Vec`. -fn append_character_string(out: &mut Vec, ascii_str: &str) -> Result<(), MdnsResponseError> { - if !ascii_str.is_ascii() { - return Err(MdnsResponseError::NonAsciiMultiaddr); - } - - if !ascii_str.bytes().any(|c| c == b' ') { - out.extend_from_slice(ascii_str.as_bytes()); - return Ok(()); - } - - out.push(b'"'); - - for &chr in ascii_str.as_bytes() { - if chr == b'\\' { - out.push(b'\\'); - out.push(b'\\'); - } else if chr == b'"' { - out.push(b'\\'); - out.push(b'"'); - } else { - out.push(chr); - } - } - - out.push(b'"'); - Ok(()) -} - -/// Appends a TXT record to `out`. -fn append_txt_record( - out: &mut Vec, - name: &[u8], - ttl_secs: u32, - value: &str, -) -> Result<(), MdnsResponseError> { - // The name. - out.extend_from_slice(name); - - // Flags. - out.push(0x00); - out.push(0x10); // TXT record. - out.push(0x80); - out.push(0x01); - - // TTL for the answer - append_u32(out, ttl_secs); - - // Add the strings. - if value.len() > MAX_TXT_VALUE_LENGTH { - return Err(MdnsResponseError::TxtRecordTooLong); - } - let mut buffer = vec![value.len() as u8]; - append_character_string(&mut buffer, value)?; - - append_u16(out, buffer.len() as u16); - out.extend_from_slice(&buffer); - Ok(()) -} - -/// Errors that can occur on encoding an MDNS response. -#[derive(Debug)] -enum MdnsResponseError { - TxtRecordTooLong, - NonAsciiMultiaddr, -} - -impl fmt::Display for MdnsResponseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - MdnsResponseError::TxtRecordTooLong => { - write!(f, "TXT record invalid because it is too long") - } - MdnsResponseError::NonAsciiMultiaddr => write!( - f, - "A multiaddr contains non-ASCII characters when serialized" - ), - } - } -} - -impl error::Error for MdnsResponseError {} - -#[cfg(test)] -mod tests { - use super::*; - use dns_parser::Packet; - use libp2p_core::identity; - use std::time::Duration; - - #[test] - fn build_query_correct() { - let query = build_query(); - assert!(Packet::parse(&query).is_ok()); - } - - #[test] - fn build_query_response_correct() { - let my_peer_id = identity::Keypair::generate_ed25519().public().to_peer_id(); - let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap(); - let addr2 = "/ip6/::1/udp/10000".parse().unwrap(); - let packets = build_query_response( - 0xf8f8, - my_peer_id, - vec![addr1, addr2].into_iter(), - Duration::from_secs(60), - ); - for packet in packets { - assert!(Packet::parse(&packet).is_ok()); - } - } - - #[test] - fn build_service_discovery_response_correct() { - let query = build_service_discovery_response(0x1234, Duration::from_secs(120)); - assert!(Packet::parse(&query).is_ok()); - } - - #[test] - fn test_segment_peer_id() { - let str_32 = String::from_utf8(vec![b'x'; 32]).unwrap(); - let str_63 = String::from_utf8(vec![b'x'; 63]).unwrap(); - let str_64 = String::from_utf8(vec![b'x'; 64]).unwrap(); - let str_126 = String::from_utf8(vec![b'x'; 126]).unwrap(); - let str_127 = String::from_utf8(vec![b'x'; 127]).unwrap(); - - assert_eq!(segment_peer_id(str_32.clone()), str_32); - assert_eq!(segment_peer_id(str_63.clone()), str_63); - - assert_eq!(segment_peer_id(str_64), [&str_63, "x"].join(".")); - assert_eq!( - segment_peer_id(str_126), - [&str_63, str_63.as_str()].join(".") - ); - assert_eq!(segment_peer_id(str_127), [&str_63, &str_63, "x"].join(".")); - } - - // TODO: test limits and errors -} diff --git a/protocols/mdns/src/lib.rs b/protocols/mdns/src/lib.rs index d8e51bdd0a7b..8c5d3c21871b 100644 --- a/protocols/mdns/src/lib.rs +++ b/protocols/mdns/src/lib.rs @@ -46,5 +46,4 @@ lazy_static! { pub use crate::behaviour::{Mdns, MdnsConfig, MdnsEvent}; mod behaviour; -mod dns; -mod query; +mod packet; diff --git a/protocols/mdns/src/packet.rs b/protocols/mdns/src/packet.rs new file mode 100644 index 000000000000..43a93023bf21 --- /dev/null +++ b/protocols/mdns/src/packet.rs @@ -0,0 +1,425 @@ +use crate::{META_QUERY_SERVICE, SERVICE_NAME}; +use anyhow::Result; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use libp2p_core::PeerId; +use std::convert::TryFrom; +use std::io::{Cursor, Read, Write}; +use std::str::FromStr; +use std::time::Duration; + +/// Maximum size of a DNS label as per RFC1035. +const MAX_LABEL_LENGTH: usize = 63; +/// DNS TXT records can have up to 255 characters as a single string value. +/// +/// Current values are usually around 170-190 bytes long, varying primarily +/// with the length of the contained `Multiaddr`. +const MAX_TXT_VALUE_LENGTH: usize = 255; +/// A conservative maximum size (in bytes) of a complete TXT record. +const MAX_TXT_RECORD_SIZE: usize = MAX_TXT_VALUE_LENGTH + 45; +/// The maximum DNS packet size is 9000 bytes less the maximum +/// sizes of the IP (60) and UDP (8) headers. +const MAX_PACKET_SIZE: usize = 9000 - 68; +/// A conservative maximum number of records that can be packed into +/// a single DNS UDP packet, allowing up to 100 bytes of MDNS packet +/// header data to be added. +const MAX_RECORDS_PER_PACKET: usize = (MAX_PACKET_SIZE - 100) / MAX_TXT_RECORD_SIZE; + +#[derive(Debug, PartialEq)] +pub enum MdnsPacket { + PeerRequest { + query_id: u16, + }, + PeerResponse { + query_id: u16, + peer_id: PeerId, + addresses: Vec, + ttl: Duration, + }, + ServiceRequest { + query_id: u16, + }, + ServiceResponse { + query_id: u16, + ttl: Duration, + }, +} + +impl MdnsPacket { + pub fn to_encoded_packets(&self) -> Vec> { + match self { + Self::PeerRequest { query_id } => { + let mut packet = Vec::with_capacity(33); + // query identifier + packet.write_u16::(*query_id).unwrap(); + // 0x0000 indicates that it is a query + packet.write_u16::(0x0000).unwrap(); + // number of questions + packet.write_u16::(1).unwrap(); + // number of answers + packet.write_u16::(0).unwrap(); + // number of authorities + packet.write_u16::(0).unwrap(); + // number of additionals + packet.write_u16::(0).unwrap(); + // question + // name + append_qname(&mut packet, SERVICE_NAME); + // record type ptr + packet.write_u16::(0x000c).unwrap(); + // address class ip + packet.write_u16::(0x0001).unwrap(); + debug_assert_eq!(packet.len(), 33); + vec![packet] + } + Self::PeerResponse { + query_id, + peer_id, + addresses, + ttl, + } => { + let ttl = to_ttl(ttl); + let peer_id_encoded = encode_peer_id(&peer_id); + debug_assert!(peer_id_encoded.len() + 2 <= 0xffff); + let mut packets = Vec::with_capacity(addresses.len() % MAX_RECORDS_PER_PACKET); + for addresses in addresses.chunks(MAX_RECORDS_PER_PACKET) { + let mut packet = Vec::with_capacity(addresses.len() * MAX_TXT_RECORD_SIZE); + // query identifier + packet.write_u16::(*query_id).unwrap(); + // 0x8400 indicates that it is a response and it + // is an authoritative answer + packet.write_u16::(0x8400).unwrap(); + // number of questions + packet.write_u16::(0).unwrap(); + // number of answers + packet.write_u16::(1).unwrap(); + // number of authorities + packet.write_u16::(0).unwrap(); + // number of additionals + packet + .write_u16::(addresses.len() as u16) + .unwrap(); + // answer + // name + append_qname(&mut packet, SERVICE_NAME); + // record type ptr + packet.write_u16::(0x000c).unwrap(); + // address class ip + packet.write_u16::(0x0001).unwrap(); + // ttl + packet.write_u32::(ttl).unwrap(); + // peer id + packet + .write_u16::(peer_id_encoded.len() as u16 + 2) + .unwrap(); + append_qname(&mut packet, &peer_id_encoded); + // additionals + for address in addresses { + let txt = format!("dnsaddr={}/p2p/{}", address, peer_id); + assert!(txt.len() <= MAX_TXT_VALUE_LENGTH); + // valid multiaddrs don't contain spaces + assert!(!txt.bytes().any(|c| c == b' ')); + append_qname(&mut packet, &peer_id_encoded); + // record type txt + packet.write_u16::(0x0010).unwrap(); + // address class ip with cache-flush bit set + packet.write_u16::(0x8001).unwrap(); + // ttl + packet.write_u32::(ttl).unwrap(); + // txt + let len = txt.as_bytes().len() as u8; + packet.write_u16::(len as u16 + 1).unwrap(); + packet.write_all(&[len]).unwrap(); + packet.extend_from_slice(txt.as_bytes()); + } + packets.push(packet); + } + packets + } + Self::ServiceRequest { .. } => { + // Not used by libp2p + vec![] + } + Self::ServiceResponse { query_id, ttl } => { + let mut packet = Vec::with_capacity(69); + // query identifier + packet.write_u16::(*query_id).unwrap(); + // 0x8400 indicates that it is a response and it + // is an authoritative answer + packet.write_u16::(0x8400).unwrap(); + // number of questions + packet.write_u16::(0).unwrap(); + // number of answers + packet.write_u16::(1).unwrap(); + // number of authorities + packet.write_u16::(0).unwrap(); + // number of additionals + packet.write_u16::(0).unwrap(); + // answer + // name + append_qname(&mut packet, META_QUERY_SERVICE); + // record type ptr + packet.write_u16::(0x000c).unwrap(); + // address class ip with cache-flush bit set + packet.write_u16::(0x8001).unwrap(); + // ttl + packet.write_u32::(to_ttl(ttl)).unwrap(); + { + let mut name = Vec::with_capacity(SERVICE_NAME.len() + 2); + append_qname(&mut name, SERVICE_NAME); + packet.write_u16::(name.len() as u16).unwrap(); + packet.extend_from_slice(&name); + } + debug_assert_eq!(packet.len(), 69); + vec![packet] + } + } + } + + pub fn from_bytes(packet: &[u8]) -> Result { + let mut packet = Cursor::new(packet); + // query identifier + let query_id = packet.read_u16::()?; + // flag + let flag = packet.read_u16::()?; + // number of questions + let questions = packet.read_u16::()? as usize; + // number of answers + let answers = packet.read_u16::()? as usize; + // number of authorities + let _authorities = packet.read_u16::()?; + // number of additionals + let additionals = packet.read_u16::()? as usize; + // qname + let qname = read_qname(&mut packet)?; + match (flag, &qname[..], questions, answers) { + (0x0000, SERVICE_NAME, 1, 0) => Ok(Self::PeerRequest { query_id }), + (0x8400, SERVICE_NAME, 0, 1) => { + let ptr = packet.read_u16::()?; + let class = packet.read_u16::()?; + if ptr != 0x000c || class != 0x0001 || additionals < 1 { + return Err(anyhow::anyhow!("invalid answer")); + } + let ttl = Duration::from_secs(packet.read_u32::()? as u64); + let _peer_id_len = packet.read_u16::()? as usize; + let peer_id_encoded = read_qname(&mut packet)?; + let peer_id = decode_peer_id(&peer_id_encoded)?; + let mut addresses = Vec::with_capacity(additionals); + for _ in 0..additionals { + let qname = read_qname(&mut packet)?; + let txt = packet.read_u16::()?; + let class = packet.read_u16::()?; + let _ttl = packet.read_u32::()?; + let txt_len = packet.read_u16::()? as usize; + let mut txt_bytes = vec![0; txt_len]; + packet.read_exact(&mut txt_bytes)?; + if txt != 0x0010 + || class != 0x8001 + || qname != peer_id_encoded + || txt_len < 2 + || txt_bytes[0] as usize != txt_len - 1 + || !txt_bytes[1..].starts_with(b"dnsaddr=") + { + continue; + } + let addr = if let Ok(addr) = std::str::from_utf8(&txt_bytes[9..]) { + addr + } else { + continue; + }; + let mut addr = if let Ok(addr) = Multiaddr::from_str(addr) { + addr + } else { + continue; + }; + let peer_id2 = match addr.pop() { + Some(Protocol::P2p(peer_id2)) => PeerId::try_from(peer_id2).ok(), + _ => None, + }; + if Some(peer_id) != peer_id2 { + continue; + } + addresses.push(addr); + } + Ok(Self::PeerResponse { + query_id, + peer_id, + addresses, + ttl, + }) + } + (0x0000, META_QUERY_SERVICE, 1, 0) => Ok(Self::ServiceRequest { query_id }), + (_, SERVICE_NAME, _, _) | (_, META_QUERY_SERVICE, _, _) => { + Err(anyhow::anyhow!("invalid mdns packet")) + } + (_, qname, _, _) => Err(anyhow::anyhow!("unknown qname {:?}", qname)), + } + } +} + +/// Appends a `QNAME` (as defined by RFC1035) to the `Vec`. +/// +/// # Panic +/// +/// Panics if `name` has a zero-length component or a component that is too long. +/// This is fine considering that this function is not public and is only called in a controlled +/// environment. +/// +fn append_qname(out: &mut Vec, name: &[u8]) { + debug_assert!(name.is_ascii()); + for element in name.split(|&c| c == b'.') { + assert!(element.len() < 64, "Service name has a label too long"); + assert_ne!(element.len(), 0, "Service name contains zero length label"); + out.push(element.len() as u8); + for chr in element.iter() { + out.push(*chr); + } + } + out.push(0); +} + +fn read_qname(mut packet: impl Read) -> Result> { + let mut qname = vec![]; + loop { + let mut byte = [0]; + packet.read_exact(&mut byte)?; + if byte[0] == 0 { + break; + } + if !qname.is_empty() { + qname.push(b'.'); + } + let pos = qname.len(); + qname.resize(pos + byte[0] as usize, 0); + packet.read_exact(&mut qname[pos..])?; + } + Ok(qname) +} + +/// Appends a ttl (the number of secs of a duration) to a `Vec`. +fn to_ttl(duration: &Duration) -> u32 { + let secs = duration + .as_secs() + .saturating_add(if duration.subsec_nanos() > 0 { 1 } else { 0 }); + std::cmp::min(secs, From::from(u32::max_value())) as u32 +} + +/// Combines and encodes a `PeerId` and service name for a DNS query. +fn encode_peer_id(peer_id: &PeerId) -> Vec { + // DNS-safe encoding for the Peer ID + let raw_peer_id = data_encoding::BASE32_DNSCURVE.encode(&peer_id.to_bytes()); + // ensure we don't have any labels over 63 bytes long + let encoded_peer_id = segment_peer_id(raw_peer_id); + let mut bytes = Vec::with_capacity(encoded_peer_id.as_bytes().len() + SERVICE_NAME.len() + 1); + bytes.extend_from_slice(encoded_peer_id.as_bytes()); + bytes.push(b'.'); + bytes.extend_from_slice(SERVICE_NAME); + bytes +} + +fn decode_peer_id(peer_id: &[u8]) -> Result { + let mut peer_name = peer_id + .rsplitn(4, |c| *c == b'.') + .last() + .ok_or_else(|| anyhow::anyhow!("doesn't end with ._p2p._udp.local"))? + .to_vec(); + // if we have a segmented name, remove the '.' + peer_name.retain(|c| *c != b'.'); + let bytes = data_encoding::BASE32_DNSCURVE.decode(&peer_name)?; + Ok(PeerId::from_bytes(&bytes)?) +} + +/// If a peer ID is longer than 63 characters, split it into segments to +/// be compatible with RFC 1035. +fn segment_peer_id(peer_id: String) -> String { + // Guard for the most common case + if peer_id.len() <= MAX_LABEL_LENGTH { + return peer_id; + } + + // This will only perform one allocation except in extreme circumstances. + let mut out = String::with_capacity(peer_id.len() + 8); + + for (idx, chr) in peer_id.chars().enumerate() { + if idx > 0 && idx % MAX_LABEL_LENGTH == 0 { + out.push('.'); + } + out.push(chr); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + use libp2p_core::identity; + use std::time::Duration; + + #[test] + fn peer_request_encode_decode() { + let query = MdnsPacket::PeerRequest { + query_id: rand::random(), + }; + let packets = query.to_encoded_packets(); + assert_eq!(packets.len(), 1); + let encoded_query = packets.into_iter().next().unwrap(); + let query2 = MdnsPacket::from_bytes(&encoded_query).unwrap(); + assert_eq!(query, query2); + } + + #[test] + fn peer_response_encode_decode() { + let my_peer_id = identity::Keypair::generate_ed25519().public().to_peer_id(); + let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap(); + let addr2 = "/ip6/::1/udp/10000".parse().unwrap(); + let answer = MdnsPacket::PeerResponse { + query_id: 0xf8f8, + peer_id: my_peer_id, + addresses: vec![addr1, addr2], + ttl: Duration::from_secs(60), + }; + let packets = answer.to_encoded_packets(); + assert_eq!(packets.len(), 1); + let encoded_answer = packets.into_iter().next().unwrap(); + let answer2 = MdnsPacket::from_bytes(&encoded_answer).unwrap(); + assert_eq!(answer, answer2); + } + + #[test] + fn service_response_encode() { + let answer = MdnsPacket::ServiceResponse { + query_id: rand::random(), + ttl: Duration::from_secs(120), + }; + let packets = answer.to_encoded_packets(); + assert_eq!(packets.len(), 1); + } + + #[test] + fn peer_id_encode_decode() { + let my_peer_id = identity::Keypair::generate_ed25519().public().to_peer_id(); + let bytes = encode_peer_id(&my_peer_id); + let my_peer_id2 = decode_peer_id(&bytes).unwrap(); + assert_eq!(my_peer_id, my_peer_id2); + } + + #[test] + fn test_segment_peer_id() { + let str_32 = String::from_utf8(vec![b'x'; 32]).unwrap(); + let str_63 = String::from_utf8(vec![b'x'; 63]).unwrap(); + let str_64 = String::from_utf8(vec![b'x'; 64]).unwrap(); + let str_126 = String::from_utf8(vec![b'x'; 126]).unwrap(); + let str_127 = String::from_utf8(vec![b'x'; 127]).unwrap(); + + assert_eq!(segment_peer_id(str_32.clone()), str_32); + assert_eq!(segment_peer_id(str_63.clone()), str_63); + + assert_eq!(segment_peer_id(str_64), [&str_63, "x"].join(".")); + assert_eq!( + segment_peer_id(str_126), + [&str_63, str_63.as_str()].join(".") + ); + assert_eq!(segment_peer_id(str_127), [&str_63, &str_63, "x"].join(".")); + } +} diff --git a/protocols/mdns/src/query.rs b/protocols/mdns/src/query.rs deleted file mode 100644 index c605298a36a8..000000000000 --- a/protocols/mdns/src/query.rs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::{dns, META_QUERY_SERVICE, SERVICE_NAME}; -use dns_parser::{Packet, RData}; -use libp2p_core::{ - multiaddr::{Multiaddr, Protocol}, - PeerId, -}; -use std::{convert::TryFrom, fmt, net::SocketAddr, str, time::Duration}; - -/// A valid mDNS packet received by the service. -#[derive(Debug)] -pub enum MdnsPacket { - /// A query made by a remote. - Query(MdnsQuery), - /// A response sent by a remote in response to one of our queries. - Response(MdnsResponse), - /// A request for service discovery. - ServiceDiscovery(MdnsServiceDiscovery), -} - -impl MdnsPacket { - pub fn new_from_bytes(buf: &[u8], from: SocketAddr) -> Option { - match Packet::parse(buf) { - Ok(packet) => { - if packet.header.query { - if packet - .questions - .iter() - .any(|q| q.qname.to_string().as_bytes() == SERVICE_NAME) - { - let query = MdnsPacket::Query(MdnsQuery { - from, - query_id: packet.header.id, - }); - Some(query) - } else if packet - .questions - .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? - let discovery = MdnsPacket::ServiceDiscovery(MdnsServiceDiscovery { - from, - query_id: packet.header.id, - }); - Some(discovery) - } else { - None - } - } else { - let resp = MdnsPacket::Response(MdnsResponse::new(packet, from)); - Some(resp) - } - } - Err(err) => { - log::debug!("Parsing mdns packet failed: {:?}", err); - None - } - } - } -} - -/// A received mDNS query. -pub struct MdnsQuery { - /// Sender of the address. - from: SocketAddr, - /// Id of the received DNS query. We need to pass this ID back in the results. - query_id: u16, -} - -impl MdnsQuery { - /// Source address of the packet. - pub fn remote_addr(&self) -> &SocketAddr { - &self.from - } - - /// Query id of the packet. - pub fn query_id(&self) -> u16 { - self.query_id - } -} - -impl fmt::Debug for MdnsQuery { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MdnsQuery") - .field("from", self.remote_addr()) - .field("query_id", &self.query_id) - .finish() - } -} - -/// A received mDNS service discovery query. -pub struct MdnsServiceDiscovery { - /// Sender of the address. - from: SocketAddr, - /// Id of the received DNS query. We need to pass this ID back in the results. - query_id: u16, -} - -impl MdnsServiceDiscovery { - /// Source address of the packet. - pub fn remote_addr(&self) -> &SocketAddr { - &self.from - } - - /// Query id of the packet. - pub fn query_id(&self) -> u16 { - self.query_id - } -} - -impl fmt::Debug for MdnsServiceDiscovery { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MdnsServiceDiscovery") - .field("from", self.remote_addr()) - .field("query_id", &self.query_id) - .finish() - } -} - -/// A received mDNS response. -pub struct MdnsResponse { - peers: Vec, - from: SocketAddr, -} - -impl MdnsResponse { - /// Creates a new `MdnsResponse` based on the provided `Packet`. - pub fn new(packet: Packet<'_>, from: SocketAddr) -> MdnsResponse { - let peers = packet - .answers - .iter() - .filter_map(|record| { - 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 mut peer_name = match record_value.rsplitn(4, |c| c == '.').last() { - Some(n) => n.to_owned(), - None => return None, - }; - - // if we have a segmented name, remove the '.' - peer_name.retain(|c| c != '.'); - - 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::new(&packet, record_value, peer_id, record.ttl)) - }) - .collect(); - - MdnsResponse { peers, from } - } - - /// 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(&self) -> impl Iterator { - self.peers.iter() - } - - /// Source address of the packet. - #[inline] - pub fn remote_addr(&self) -> &SocketAddr { - &self.from - } -} - -impl fmt::Debug for MdnsResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MdnsResponse") - .field("from", self.remote_addr()) - .finish() - } -} - -/// A peer discovered by the service. -pub struct MdnsPeer { - addrs: Vec, - /// Id of the peer. - peer_id: PeerId, - /// TTL of the record in seconds. - ttl: u32, -} - -impl MdnsPeer { - /// Creates a new `MdnsPeer` based on the provided `Packet`. - pub fn new( - packet: &Packet<'_>, - record_value: String, - my_peer_id: PeerId, - ttl: u32, - ) -> MdnsPeer { - let addrs = packet - .additional - .iter() - .filter_map(|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(|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(peer_id)) => { - if let Ok(peer_id) = PeerId::try_from(peer_id) { - if peer_id != my_peer_id { - return None; - } - } else { - return None; - } - } - _ => return None, - }; - Some(addr) - }) - .collect(); - - MdnsPeer { - addrs, - peer_id: my_peer_id, - ttl, - } - } - - /// Returns the id of the peer. - #[inline] - pub fn id(&self) -> &PeerId { - &self.peer_id - } - - /// Returns the requested time-to-live for the record. - #[inline] - pub fn ttl(&self) -> Duration { - Duration::from_secs(u64::from(self.ttl)) - } - - /// Returns the list of addresses the peer says it is listening on. - /// - /// Filters out invalid addresses. - pub fn addresses(&self) -> &Vec { - &self.addrs - } -} - -impl fmt::Debug for MdnsPeer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MdnsPeer") - .field("peer_id", &self.peer_id) - .finish() - } -}