libp2p_mdns/behaviour/iface/
dns.rs1use 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
31const MAX_TXT_VALUE_LENGTH: usize = 255;
36
37const MAX_TXT_RECORD_SIZE: usize = MAX_TXT_VALUE_LENGTH + 45;
40
41const MAX_PACKET_SIZE: usize = 9000 - 68;
44
45const MAX_RECORDS_PER_PACKET: usize = (MAX_PACKET_SIZE - 100) / MAX_TXT_RECORD_SIZE;
49
50pub(crate) type MdnsPacket = Vec<u8>;
52pub(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 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 Ok(Cow::Borrowed(from))
70}
71
72pub(crate) fn build_query() -> MdnsPacket {
74 let mut out = Vec::with_capacity(33);
75
76 append_u16(&mut out, rand::random());
78
79 append_u16(&mut out, 0x0);
81
82 append_u16(&mut out, 0x1);
84
85 append_u16(&mut out, 0x0);
87 append_u16(&mut out, 0x0);
88 append_u16(&mut out, 0x0);
89
90 append_qname(&mut out, SERVICE_NAME);
93
94 append_u16(&mut out, 0x0c);
96 append_u16(&mut out, 0x01);
97
98 debug_assert_eq!(out.capacity(), out.len());
101 out
102}
103
104pub(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 let ttl = duration_to_secs(ttl);
115
116 let addresses = addresses.take(65535);
118
119 let peer_name_bytes = generate_peer_name();
120 debug_assert!(peer_name_bytes.len() <= 0xffff);
121
122 let mut packets = Vec::new();
124
125 let mut records = Vec::with_capacity(addresses.len() * MAX_TXT_RECORD_SIZE);
127
128 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 !records.is_empty() {
151 packets.push(query_response_packet(id, &peer_name_bytes, &records, ttl));
152 }
153
154 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
168pub(crate) fn build_service_discovery_response(id: u16, ttl: Duration) -> MdnsPacket {
170 let ttl = duration_to_secs(ttl);
172
173 let mut out = Vec::with_capacity(69);
175
176 append_u16(&mut out, id);
177 append_u16(&mut out, 0x8400);
179 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 append_qname(&mut out, META_QUERY_SERVICE);
188
189 append_u16(&mut out, 0x000c);
191 append_u16(&mut out, 0x8001);
192
193 append_u32(&mut out, ttl);
195
196 {
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 debug_assert_eq!(out.capacity(), out.len());
207 out
208}
209
210fn 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 append_u16(&mut out, 0x8400);
217 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 append_qname(&mut out, SERVICE_NAME);
226
227 append_u16(&mut out, 0x000c);
229 append_u16(&mut out, 0x0001);
230
231 append_u32(&mut out, ttl);
233
234 append_u16(&mut out, peer_id.len() as u16);
236 out.extend_from_slice(peer_id);
237
238 for record in records {
240 out.extend_from_slice(record);
241 }
242
243 out
244}
245
246fn 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
254fn 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
262fn 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
268fn random_string(length: usize) -> String {
270 thread_rng()
271 .sample_iter(&Alphanumeric)
272 .take(length)
273 .map(char::from)
274 .collect()
275}
276
277fn generate_peer_name() -> Vec<u8> {
279 let peer_name = random_string(32 + thread_rng().gen_range(0..32));
282
283 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
290fn 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 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 }