libp2p_mdns/behaviour/iface/
dns.rs

1// Copyright 2018 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21//! (M)DNS encoding and decoding on top of the `dns_parser` library.
22
23use crate::{META_QUERY_SERVICE, SERVICE_NAME};
24use libp2p_core::Multiaddr;
25use libp2p_identity::PeerId;
26use rand::distributions::Alphanumeric;
27use rand::{thread_rng, Rng};
28use std::{borrow::Cow, cmp, error, fmt, str, time::Duration};
29
30/// DNS TXT records can have up to 255 characters as a single string value.
31///
32/// Current values are usually around 170-190 bytes long, varying primarily
33/// with the length of the contained `Multiaddr`.
34const MAX_TXT_VALUE_LENGTH: usize = 255;
35
36/// A conservative maximum size (in bytes) of a complete TXT record,
37/// as encoded by [`append_txt_record`].
38const MAX_TXT_RECORD_SIZE: usize = MAX_TXT_VALUE_LENGTH + 45;
39
40/// The maximum DNS packet size is 9000 bytes less the maximum
41/// sizes of the IP (60) and UDP (8) headers.
42const MAX_PACKET_SIZE: usize = 9000 - 68;
43
44/// A conservative maximum number of records that can be packed into
45/// a single DNS UDP packet, allowing up to 100 bytes of MDNS packet
46/// header data to be added by [`query_response_packet()`].
47const MAX_RECORDS_PER_PACKET: usize = (MAX_PACKET_SIZE - 100) / MAX_TXT_RECORD_SIZE;
48
49/// An encoded MDNS packet.
50pub(crate) type MdnsPacket = Vec<u8>;
51/// Decodes a `<character-string>` (as defined by RFC1035) into a `Vec` of ASCII characters.
52// TODO: better error type?
53pub(crate) fn decode_character_string(mut from: &[u8]) -> Result<Cow<'_, [u8]>, ()> {
54    if from.is_empty() {
55        return Ok(Cow::Owned(Vec::new()));
56    }
57
58    // Remove the initial and trailing " if any.
59    if from[0] == b'"' {
60        if from.len() == 1 || from.last() != Some(&b'"') {
61            return Err(());
62        }
63        let len = from.len();
64        from = &from[1..len - 1];
65    }
66
67    // TODO: remove the backslashes if any
68    Ok(Cow::Borrowed(from))
69}
70
71/// Builds the binary representation of a DNS query to send on the network.
72pub(crate) fn build_query() -> MdnsPacket {
73    let mut out = Vec::with_capacity(33);
74
75    // Program-generated transaction ID; unused by our implementation.
76    append_u16(&mut out, rand::random());
77
78    // 0x0 flag for a regular query.
79    append_u16(&mut out, 0x0);
80
81    // Number of questions.
82    append_u16(&mut out, 0x1);
83
84    // Number of answers, authorities, and additionals.
85    append_u16(&mut out, 0x0);
86    append_u16(&mut out, 0x0);
87    append_u16(&mut out, 0x0);
88
89    // Our single question.
90    // The name.
91    append_qname(&mut out, SERVICE_NAME);
92
93    // Flags.
94    append_u16(&mut out, 0x0c);
95    append_u16(&mut out, 0x01);
96
97    // Since the output is constant, we reserve the right amount ahead of time.
98    // If this assert fails, adjust the capacity of `out` in the source code.
99    debug_assert_eq!(out.capacity(), out.len());
100    out
101}
102
103/// Builds the response to an address discovery DNS query.
104///
105/// If there are more than 2^16-1 addresses, ignores the rest.
106pub(crate) fn build_query_response<'a>(
107    id: u16,
108    peer_id: PeerId,
109    addresses: impl ExactSizeIterator<Item = &'a Multiaddr>,
110    ttl: Duration,
111) -> Vec<MdnsPacket> {
112    // Convert the TTL into seconds.
113    let ttl = duration_to_secs(ttl);
114
115    // Add a limit to 2^16-1 addresses, as the protocol limits to this number.
116    let addresses = addresses.take(65535);
117
118    let peer_name_bytes = generate_peer_name();
119    debug_assert!(peer_name_bytes.len() <= 0xffff);
120
121    // The accumulated response packets.
122    let mut packets = Vec::new();
123
124    // The records accumulated per response packet.
125    let mut records = Vec::with_capacity(addresses.len() * MAX_TXT_RECORD_SIZE);
126
127    // Encode the addresses as TXT records, and multiple TXT records into a
128    // response packet.
129    for addr in addresses {
130        let txt_to_send = format!("dnsaddr={}/p2p/{}", addr, peer_id.to_base58());
131        let mut txt_record = Vec::with_capacity(txt_to_send.len());
132        match append_txt_record(&mut txt_record, &peer_name_bytes, ttl, &txt_to_send) {
133            Ok(()) => {
134                records.push(txt_record);
135            }
136            Err(e) => {
137                log::warn!("Excluding address {} from response: {:?}", addr, e);
138            }
139        }
140
141        if records.len() == MAX_RECORDS_PER_PACKET {
142            packets.push(query_response_packet(id, &peer_name_bytes, &records, ttl));
143            records.clear();
144        }
145    }
146
147    // If there are still unpacked records, i.e. if the number of records is not
148    // a multiple of `MAX_RECORDS_PER_PACKET`, create a final packet.
149    if !records.is_empty() {
150        packets.push(query_response_packet(id, &peer_name_bytes, &records, ttl));
151    }
152
153    // If no packets have been built at all, because `addresses` is empty,
154    // construct an empty response packet.
155    if packets.is_empty() {
156        packets.push(query_response_packet(
157            id,
158            &peer_name_bytes,
159            &Vec::new(),
160            ttl,
161        ));
162    }
163
164    packets
165}
166
167/// Builds the response to a service discovery DNS query.
168pub(crate) fn build_service_discovery_response(id: u16, ttl: Duration) -> MdnsPacket {
169    // Convert the TTL into seconds.
170    let ttl = duration_to_secs(ttl);
171
172    // This capacity was determined empirically.
173    let mut out = Vec::with_capacity(69);
174
175    append_u16(&mut out, id);
176    // 0x84 flag for an answer.
177    append_u16(&mut out, 0x8400);
178    // Number of questions, answers, authorities, additionals.
179    append_u16(&mut out, 0x0);
180    append_u16(&mut out, 0x1);
181    append_u16(&mut out, 0x0);
182    append_u16(&mut out, 0x0);
183
184    // Our single answer.
185    // The name.
186    append_qname(&mut out, META_QUERY_SERVICE);
187
188    // Flags.
189    append_u16(&mut out, 0x000c);
190    append_u16(&mut out, 0x8001);
191
192    // TTL for the answer
193    append_u32(&mut out, ttl);
194
195    // Service name.
196    {
197        let mut name = Vec::with_capacity(SERVICE_NAME.len() + 2);
198        append_qname(&mut name, SERVICE_NAME);
199        append_u16(&mut out, name.len() as u16);
200        out.extend_from_slice(&name);
201    }
202
203    // Since the output size is constant, we reserve the right amount ahead of time.
204    // If this assert fails, adjust the capacity of `out` in the source code.
205    debug_assert_eq!(out.capacity(), out.len());
206    out
207}
208
209/// Constructs an MDNS query response packet for an address lookup.
210fn query_response_packet(id: u16, peer_id: &[u8], records: &[Vec<u8>], ttl: u32) -> MdnsPacket {
211    let mut out = Vec::with_capacity(records.len() * MAX_TXT_RECORD_SIZE);
212
213    append_u16(&mut out, id);
214    // 0x84 flag for an answer.
215    append_u16(&mut out, 0x8400);
216    // Number of questions, answers, authorities, additionals.
217    append_u16(&mut out, 0x0);
218    append_u16(&mut out, 0x1);
219    append_u16(&mut out, 0x0);
220    append_u16(&mut out, records.len() as u16);
221
222    // Our single answer.
223    // The name.
224    append_qname(&mut out, SERVICE_NAME);
225
226    // Flags.
227    append_u16(&mut out, 0x000c);
228    append_u16(&mut out, 0x0001);
229
230    // TTL for the answer
231    append_u32(&mut out, ttl);
232
233    // Peer Id.
234    append_u16(&mut out, peer_id.len() as u16);
235    out.extend_from_slice(peer_id);
236
237    // The TXT records.
238    for record in records {
239        out.extend_from_slice(record);
240    }
241
242    out
243}
244
245/// Returns the number of secs of a duration.
246fn duration_to_secs(duration: Duration) -> u32 {
247    let secs = duration
248        .as_secs()
249        .saturating_add(u64::from(duration.subsec_nanos() > 0));
250    cmp::min(secs, From::from(u32::max_value())) as u32
251}
252
253/// Appends a big-endian u32 to `out`.
254fn append_u32(out: &mut Vec<u8>, value: u32) {
255    out.push(((value >> 24) & 0xff) as u8);
256    out.push(((value >> 16) & 0xff) as u8);
257    out.push(((value >> 8) & 0xff) as u8);
258    out.push((value & 0xff) as u8);
259}
260
261/// Appends a big-endian u16 to `out`.
262fn append_u16(out: &mut Vec<u8>, value: u16) {
263    out.push(((value >> 8) & 0xff) as u8);
264    out.push((value & 0xff) as u8);
265}
266
267/// Generates and returns a random alphanumeric string of `length` size.
268fn random_string(length: usize) -> String {
269    thread_rng()
270        .sample_iter(&Alphanumeric)
271        .take(length)
272        .map(char::from)
273        .collect()
274}
275
276/// Generates a random peer name as bytes for a DNS query.
277fn generate_peer_name() -> Vec<u8> {
278    // Use a variable-length random string for mDNS peer name.
279    // See https://github.com/libp2p/rust-libp2p/pull/2311/
280    let peer_name = random_string(32 + thread_rng().gen_range(0..32));
281
282    // allocate with a little extra padding for QNAME encoding
283    let mut peer_name_bytes = Vec::with_capacity(peer_name.len() + 32);
284    append_qname(&mut peer_name_bytes, peer_name.as_bytes());
285
286    peer_name_bytes
287}
288
289/// Appends a `QNAME` (as defined by RFC1035) to the `Vec`.
290///
291/// # Panic
292///
293/// Panics if `name` has a zero-length component or a component that is too long.
294/// This is fine considering that this function is not public and is only called in a controlled
295/// environment.
296///
297fn append_qname(out: &mut Vec<u8>, name: &[u8]) {
298    debug_assert!(name.is_ascii());
299
300    for element in name.split(|&c| c == b'.') {
301        assert!(element.len() < 64, "Service name has a label too long");
302        assert_ne!(element.len(), 0, "Service name contains zero length label");
303        out.push(element.len() as u8);
304        for chr in element.iter() {
305            out.push(*chr);
306        }
307    }
308
309    out.push(0);
310}
311
312/// Appends a `<character-string>` (as defined by RFC1035) to the `Vec`.
313fn append_character_string(out: &mut Vec<u8>, ascii_str: &str) -> Result<(), MdnsResponseError> {
314    if !ascii_str.is_ascii() {
315        return Err(MdnsResponseError::NonAsciiMultiaddr);
316    }
317
318    if !ascii_str.bytes().any(|c| c == b' ') {
319        out.extend_from_slice(ascii_str.as_bytes());
320        return Ok(());
321    }
322
323    out.push(b'"');
324
325    for &chr in ascii_str.as_bytes() {
326        if chr == b'\\' {
327            out.push(b'\\');
328            out.push(b'\\');
329        } else if chr == b'"' {
330            out.push(b'\\');
331            out.push(b'"');
332        } else {
333            out.push(chr);
334        }
335    }
336
337    out.push(b'"');
338    Ok(())
339}
340
341/// Appends a TXT record to `out`.
342fn append_txt_record(
343    out: &mut Vec<u8>,
344    name: &[u8],
345    ttl_secs: u32,
346    value: &str,
347) -> Result<(), MdnsResponseError> {
348    // The name.
349    out.extend_from_slice(name);
350
351    // Flags.
352    out.push(0x00);
353    out.push(0x10); // TXT record.
354    out.push(0x80);
355    out.push(0x01);
356
357    // TTL for the answer
358    append_u32(out, ttl_secs);
359
360    // Add the strings.
361    if value.len() > MAX_TXT_VALUE_LENGTH {
362        return Err(MdnsResponseError::TxtRecordTooLong);
363    }
364    let mut buffer = vec![value.len() as u8];
365    append_character_string(&mut buffer, value)?;
366
367    append_u16(out, buffer.len() as u16);
368    out.extend_from_slice(&buffer);
369    Ok(())
370}
371
372/// Errors that can occur on encoding an MDNS response.
373#[derive(Debug)]
374enum MdnsResponseError {
375    TxtRecordTooLong,
376    NonAsciiMultiaddr,
377}
378
379impl fmt::Display for MdnsResponseError {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        match self {
382            MdnsResponseError::TxtRecordTooLong => {
383                write!(f, "TXT record invalid because it is too long")
384            }
385            MdnsResponseError::NonAsciiMultiaddr => write!(
386                f,
387                "A multiaddr contains non-ASCII characters when serialized"
388            ),
389        }
390    }
391}
392
393impl error::Error for MdnsResponseError {}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398    use libp2p_identity as identity;
399    use std::time::Duration;
400    use trust_dns_proto::op::Message;
401
402    #[test]
403    fn build_query_correct() {
404        let query = build_query();
405        assert!(Message::from_vec(&query).is_ok());
406    }
407
408    #[test]
409    fn build_query_response_correct() {
410        let my_peer_id = identity::Keypair::generate_ed25519().public().to_peer_id();
411        let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap();
412        let addr2 = "/ip6/::1/udp/10000".parse().unwrap();
413        let packets = build_query_response(
414            0xf8f8,
415            my_peer_id,
416            vec![&addr1, &addr2].into_iter(),
417            Duration::from_secs(60),
418        );
419        for packet in packets {
420            assert!(Message::from_vec(&packet).is_ok());
421        }
422    }
423
424    #[test]
425    fn build_service_discovery_response_correct() {
426        let query = build_service_discovery_response(0x1234, Duration::from_secs(120));
427        assert!(Message::from_vec(&query).is_ok());
428    }
429
430    #[test]
431    fn test_random_string() {
432        let varsize = thread_rng().gen_range(0..32);
433        let size = 32 + varsize;
434        let name = random_string(size);
435        assert_eq!(name.len(), size);
436    }
437
438    // TODO: test limits and errors
439}