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 std::{borrow::Cow, cmp, error, fmt, str, time::Duration};
24
25use libp2p_core::Multiaddr;
26use libp2p_identity::PeerId;
27use rand::{distributions::Alphanumeric, thread_rng, Rng};
28
29use crate::{META_QUERY_SERVICE, SERVICE_NAME};
30
31/// DNS TXT records can have up to 255 characters as a single string value.
32///
33/// Current values are usually around 170-190 bytes long, varying primarily
34/// with the length of the contained `Multiaddr`.
35const MAX_TXT_VALUE_LENGTH: usize = 255;
36
37/// A conservative maximum size (in bytes) of a complete TXT record,
38/// as encoded by [`append_txt_record`].
39const MAX_TXT_RECORD_SIZE: usize = MAX_TXT_VALUE_LENGTH + 45;
40
41/// The maximum DNS packet size is 9000 bytes less the maximum
42/// sizes of the IP (60) and UDP (8) headers.
43const MAX_PACKET_SIZE: usize = 9000 - 68;
44
45/// A conservative maximum number of records that can be packed into
46/// a single DNS UDP packet, allowing up to 100 bytes of MDNS packet
47/// header data to be added by [`query_response_packet()`].
48const MAX_RECORDS_PER_PACKET: usize = (MAX_PACKET_SIZE - 100) / MAX_TXT_RECORD_SIZE;
49
50/// An encoded MDNS packet.
51pub(crate) type MdnsPacket = Vec<u8>;
52/// Decodes a `<character-string>` (as defined by RFC1035) into a `Vec` of ASCII characters.
53// TODO: better error type?
54pub(crate) fn decode_character_string(mut from: &[u8]) -> Result<Cow<'_, [u8]>, ()> {
55    if from.is_empty() {
56        return Ok(Cow::Owned(Vec::new()));
57    }
58
59    // Remove the initial and trailing " if any.
60    if from[0] == b'"' {
61        if from.len() == 1 || from.last() != Some(&b'"') {
62            return Err(());
63        }
64        let len = from.len();
65        from = &from[1..len - 1];
66    }
67
68    // TODO: remove the backslashes if any
69    Ok(Cow::Borrowed(from))
70}
71
72/// Builds the binary representation of a DNS query to send on the network.
73pub(crate) fn build_query() -> MdnsPacket {
74    let mut out = Vec::with_capacity(33);
75
76    // Program-generated transaction ID; unused by our implementation.
77    append_u16(&mut out, rand::random());
78
79    // 0x0 flag for a regular query.
80    append_u16(&mut out, 0x0);
81
82    // Number of questions.
83    append_u16(&mut out, 0x1);
84
85    // Number of answers, authorities, and additionals.
86    append_u16(&mut out, 0x0);
87    append_u16(&mut out, 0x0);
88    append_u16(&mut out, 0x0);
89
90    // Our single question.
91    // The name.
92    append_qname(&mut out, SERVICE_NAME);
93
94    // Flags.
95    append_u16(&mut out, 0x0c);
96    append_u16(&mut out, 0x01);
97
98    // Since the output is constant, we reserve the right amount ahead of time.
99    // If this assert fails, adjust the capacity of `out` in the source code.
100    debug_assert_eq!(out.capacity(), out.len());
101    out
102}
103
104/// Builds the response to an address discovery DNS query.
105///
106/// If there are more than 2^16-1 addresses, ignores the rest.
107pub(crate) fn build_query_response<'a>(
108    id: u16,
109    peer_id: PeerId,
110    addresses: impl ExactSizeIterator<Item = &'a Multiaddr>,
111    ttl: Duration,
112) -> Vec<MdnsPacket> {
113    // Convert the TTL into seconds.
114    let ttl = duration_to_secs(ttl);
115
116    // Add a limit to 2^16-1 addresses, as the protocol limits to this number.
117    let addresses = addresses.take(65535);
118
119    let peer_name_bytes = generate_peer_name();
120    debug_assert!(peer_name_bytes.len() <= 0xffff);
121
122    // The accumulated response packets.
123    let mut packets = Vec::new();
124
125    // The records accumulated per response packet.
126    let mut records = Vec::with_capacity(addresses.len() * MAX_TXT_RECORD_SIZE);
127
128    // Encode the addresses as TXT records, and multiple TXT records into a
129    // response packet.
130    for addr in addresses {
131        let txt_to_send = format!("dnsaddr={}/p2p/{}", addr, peer_id.to_base58());
132        let mut txt_record = Vec::with_capacity(txt_to_send.len());
133        match append_txt_record(&mut txt_record, &peer_name_bytes, ttl, &txt_to_send) {
134            Ok(()) => {
135                records.push(txt_record);
136            }
137            Err(e) => {
138                tracing::warn!(address=%addr, "Excluding address from response: {:?}", e);
139            }
140        }
141
142        if records.len() == MAX_RECORDS_PER_PACKET {
143            packets.push(query_response_packet(id, &peer_name_bytes, &records, ttl));
144            records.clear();
145        }
146    }
147
148    // If there are still unpacked records, i.e. if the number of records is not
149    // a multiple of `MAX_RECORDS_PER_PACKET`, create a final packet.
150    if !records.is_empty() {
151        packets.push(query_response_packet(id, &peer_name_bytes, &records, ttl));
152    }
153
154    // If no packets have been built at all, because `addresses` is empty,
155    // construct an empty response packet.
156    if packets.is_empty() {
157        packets.push(query_response_packet(
158            id,
159            &peer_name_bytes,
160            &Vec::new(),
161            ttl,
162        ));
163    }
164
165    packets
166}
167
168/// Builds the response to a service discovery DNS query.
169pub(crate) fn build_service_discovery_response(id: u16, ttl: Duration) -> MdnsPacket {
170    // Convert the TTL into seconds.
171    let ttl = duration_to_secs(ttl);
172
173    // This capacity was determined empirically.
174    let mut out = Vec::with_capacity(69);
175
176    append_u16(&mut out, id);
177    // 0x84 flag for an answer.
178    append_u16(&mut out, 0x8400);
179    // Number of questions, answers, authorities, additionals.
180    append_u16(&mut out, 0x0);
181    append_u16(&mut out, 0x1);
182    append_u16(&mut out, 0x0);
183    append_u16(&mut out, 0x0);
184
185    // Our single answer.
186    // The name.
187    append_qname(&mut out, META_QUERY_SERVICE);
188
189    // Flags.
190    append_u16(&mut out, 0x000c);
191    append_u16(&mut out, 0x8001);
192
193    // TTL for the answer
194    append_u32(&mut out, ttl);
195
196    // Service name.
197    {
198        let mut name = Vec::with_capacity(SERVICE_NAME.len() + 2);
199        append_qname(&mut name, SERVICE_NAME);
200        append_u16(&mut out, name.len() as u16);
201        out.extend_from_slice(&name);
202    }
203
204    // Since the output size is constant, we reserve the right amount ahead of time.
205    // If this assert fails, adjust the capacity of `out` in the source code.
206    debug_assert_eq!(out.capacity(), out.len());
207    out
208}
209
210/// Constructs an MDNS query response packet for an address lookup.
211fn query_response_packet(id: u16, peer_id: &[u8], records: &[Vec<u8>], ttl: u32) -> MdnsPacket {
212    let mut out = Vec::with_capacity(records.len() * MAX_TXT_RECORD_SIZE);
213
214    append_u16(&mut out, id);
215    // 0x84 flag for an answer.
216    append_u16(&mut out, 0x8400);
217    // Number of questions, answers, authorities, additionals.
218    append_u16(&mut out, 0x0);
219    append_u16(&mut out, 0x1);
220    append_u16(&mut out, 0x0);
221    append_u16(&mut out, records.len() as u16);
222
223    // Our single answer.
224    // The name.
225    append_qname(&mut out, SERVICE_NAME);
226
227    // Flags.
228    append_u16(&mut out, 0x000c);
229    append_u16(&mut out, 0x0001);
230
231    // TTL for the answer
232    append_u32(&mut out, ttl);
233
234    // Peer Id.
235    append_u16(&mut out, peer_id.len() as u16);
236    out.extend_from_slice(peer_id);
237
238    // The TXT records.
239    for record in records {
240        out.extend_from_slice(record);
241    }
242
243    out
244}
245
246/// Returns the number of secs of a duration.
247fn duration_to_secs(duration: Duration) -> u32 {
248    let secs = duration
249        .as_secs()
250        .saturating_add(u64::from(duration.subsec_nanos() > 0));
251    cmp::min(secs, From::from(u32::MAX)) as u32
252}
253
254/// Appends a big-endian u32 to `out`.
255fn append_u32(out: &mut Vec<u8>, value: u32) {
256    out.push(((value >> 24) & 0xff) as u8);
257    out.push(((value >> 16) & 0xff) as u8);
258    out.push(((value >> 8) & 0xff) as u8);
259    out.push((value & 0xff) as u8);
260}
261
262/// Appends a big-endian u16 to `out`.
263fn append_u16(out: &mut Vec<u8>, value: u16) {
264    out.push(((value >> 8) & 0xff) as u8);
265    out.push((value & 0xff) as u8);
266}
267
268/// Generates and returns a random alphanumeric string of `length` size.
269fn random_string(length: usize) -> String {
270    thread_rng()
271        .sample_iter(&Alphanumeric)
272        .take(length)
273        .map(char::from)
274        .collect()
275}
276
277/// Generates a random peer name as bytes for a DNS query.
278fn generate_peer_name() -> Vec<u8> {
279    // Use a variable-length random string for mDNS peer name.
280    // See https://github.com/libp2p/rust-libp2p/pull/2311/
281    let peer_name = random_string(32 + thread_rng().gen_range(0..32));
282
283    // allocate with a little extra padding for QNAME encoding
284    let mut peer_name_bytes = Vec::with_capacity(peer_name.len() + 32);
285    append_qname(&mut peer_name_bytes, peer_name.as_bytes());
286
287    peer_name_bytes
288}
289
290/// Appends a `QNAME` (as defined by RFC1035) to the `Vec`.
291///
292/// # Panic
293///
294/// Panics if `name` has a zero-length component or a component that is too long.
295/// This is fine considering that this function is not public and is only called in a controlled
296/// environment.
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 hickory_proto::op::Message;
398    use libp2p_identity as identity;
399
400    use super::*;
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}