libp2p_mdns/behaviour/iface/
dns.rs1use 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
30const MAX_TXT_VALUE_LENGTH: usize = 255;
35
36const MAX_TXT_RECORD_SIZE: usize = MAX_TXT_VALUE_LENGTH + 45;
39
40const MAX_PACKET_SIZE: usize = 9000 - 68;
43
44const MAX_RECORDS_PER_PACKET: usize = (MAX_PACKET_SIZE - 100) / MAX_TXT_RECORD_SIZE;
48
49pub(crate) type MdnsPacket = Vec<u8>;
51pub(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 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 Ok(Cow::Borrowed(from))
69}
70
71pub(crate) fn build_query() -> MdnsPacket {
73 let mut out = Vec::with_capacity(33);
74
75 append_u16(&mut out, rand::random());
77
78 append_u16(&mut out, 0x0);
80
81 append_u16(&mut out, 0x1);
83
84 append_u16(&mut out, 0x0);
86 append_u16(&mut out, 0x0);
87 append_u16(&mut out, 0x0);
88
89 append_qname(&mut out, SERVICE_NAME);
92
93 append_u16(&mut out, 0x0c);
95 append_u16(&mut out, 0x01);
96
97 debug_assert_eq!(out.capacity(), out.len());
100 out
101}
102
103pub(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 let ttl = duration_to_secs(ttl);
114
115 let addresses = addresses.take(65535);
117
118 let peer_name_bytes = generate_peer_name();
119 debug_assert!(peer_name_bytes.len() <= 0xffff);
120
121 let mut packets = Vec::new();
123
124 let mut records = Vec::with_capacity(addresses.len() * MAX_TXT_RECORD_SIZE);
126
127 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 !records.is_empty() {
150 packets.push(query_response_packet(id, &peer_name_bytes, &records, ttl));
151 }
152
153 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
167pub(crate) fn build_service_discovery_response(id: u16, ttl: Duration) -> MdnsPacket {
169 let ttl = duration_to_secs(ttl);
171
172 let mut out = Vec::with_capacity(69);
174
175 append_u16(&mut out, id);
176 append_u16(&mut out, 0x8400);
178 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 append_qname(&mut out, META_QUERY_SERVICE);
187
188 append_u16(&mut out, 0x000c);
190 append_u16(&mut out, 0x8001);
191
192 append_u32(&mut out, ttl);
194
195 {
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 debug_assert_eq!(out.capacity(), out.len());
206 out
207}
208
209fn 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 append_u16(&mut out, 0x8400);
216 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 append_qname(&mut out, SERVICE_NAME);
225
226 append_u16(&mut out, 0x000c);
228 append_u16(&mut out, 0x0001);
229
230 append_u32(&mut out, ttl);
232
233 append_u16(&mut out, peer_id.len() as u16);
235 out.extend_from_slice(peer_id);
236
237 for record in records {
239 out.extend_from_slice(record);
240 }
241
242 out
243}
244
245fn 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
253fn 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
261fn 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
267fn random_string(length: usize) -> String {
269 thread_rng()
270 .sample_iter(&Alphanumeric)
271 .take(length)
272 .map(char::from)
273 .collect()
274}
275
276fn generate_peer_name() -> Vec<u8> {
278 let peer_name = random_string(32 + thread_rng().gen_range(0..32));
281
282 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
289fn 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
312fn 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
341fn append_txt_record(
343 out: &mut Vec<u8>,
344 name: &[u8],
345 ttl_secs: u32,
346 value: &str,
347) -> Result<(), MdnsResponseError> {
348 out.extend_from_slice(name);
350
351 out.push(0x00);
353 out.push(0x10); out.push(0x80);
355 out.push(0x01);
356
357 append_u32(out, ttl_secs);
359
360 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#[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 }