libp2p_mdns/behaviour/iface/
query.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
21use std::{
22    fmt,
23    net::SocketAddr,
24    str,
25    time::{Duration, Instant},
26};
27
28use hickory_proto::{
29    op::Message,
30    rr::{Name, RData},
31};
32use libp2p_core::multiaddr::{Multiaddr, Protocol};
33use libp2p_identity::PeerId;
34use libp2p_swarm::_address_translation;
35
36use super::dns;
37use crate::{META_QUERY_SERVICE_FQDN, SERVICE_NAME_FQDN};
38
39/// A valid mDNS packet received by the service.
40#[derive(Debug)]
41pub(crate) enum MdnsPacket {
42    /// A query made by a remote.
43    Query(MdnsQuery),
44    /// A response sent by a remote in response to one of our queries.
45    Response(MdnsResponse),
46    /// A request for service discovery.
47    ServiceDiscovery(MdnsServiceDiscovery),
48}
49
50impl MdnsPacket {
51    pub(crate) fn new_from_bytes(
52        buf: &[u8],
53        from: SocketAddr,
54    ) -> Result<Option<MdnsPacket>, hickory_proto::ProtoError> {
55        let packet = Message::from_vec(buf)?;
56
57        if packet.query().is_none() {
58            return Ok(Some(MdnsPacket::Response(MdnsResponse::new(&packet, from))));
59        }
60
61        if packet
62            .queries()
63            .iter()
64            .any(|q| q.name().to_utf8() == SERVICE_NAME_FQDN)
65        {
66            return Ok(Some(MdnsPacket::Query(MdnsQuery {
67                from,
68                query_id: packet.header().id(),
69            })));
70        }
71
72        if packet
73            .queries()
74            .iter()
75            .any(|q| q.name().to_utf8() == META_QUERY_SERVICE_FQDN)
76        {
77            // TODO: what if multiple questions,
78            // one with SERVICE_NAME and one with META_QUERY_SERVICE?
79            return Ok(Some(MdnsPacket::ServiceDiscovery(MdnsServiceDiscovery {
80                from,
81                query_id: packet.header().id(),
82            })));
83        }
84
85        Ok(None)
86    }
87}
88
89/// A received mDNS query.
90pub(crate) struct MdnsQuery {
91    /// Sender of the address.
92    from: SocketAddr,
93    /// Id of the received DNS query. We need to pass this ID back in the results.
94    query_id: u16,
95}
96
97impl MdnsQuery {
98    /// Source address of the packet.
99    pub(crate) fn remote_addr(&self) -> &SocketAddr {
100        &self.from
101    }
102
103    /// Query id of the packet.
104    pub(crate) fn query_id(&self) -> u16 {
105        self.query_id
106    }
107}
108
109impl fmt::Debug for MdnsQuery {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.debug_struct("MdnsQuery")
112            .field("from", self.remote_addr())
113            .field("query_id", &self.query_id)
114            .finish()
115    }
116}
117
118/// A received mDNS service discovery query.
119pub(crate) struct MdnsServiceDiscovery {
120    /// Sender of the address.
121    from: SocketAddr,
122    /// Id of the received DNS query. We need to pass this ID back in the results.
123    query_id: u16,
124}
125
126impl MdnsServiceDiscovery {
127    /// Source address of the packet.
128    pub(crate) fn remote_addr(&self) -> &SocketAddr {
129        &self.from
130    }
131
132    /// Query id of the packet.
133    pub(crate) fn query_id(&self) -> u16 {
134        self.query_id
135    }
136}
137
138impl fmt::Debug for MdnsServiceDiscovery {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        f.debug_struct("MdnsServiceDiscovery")
141            .field("from", self.remote_addr())
142            .field("query_id", &self.query_id)
143            .finish()
144    }
145}
146
147/// A received mDNS response.
148pub(crate) struct MdnsResponse {
149    peers: Vec<MdnsPeer>,
150    from: SocketAddr,
151}
152
153impl MdnsResponse {
154    /// Creates a new `MdnsResponse` based on the provided `Packet`.
155    pub(crate) fn new(packet: &Message, from: SocketAddr) -> MdnsResponse {
156        let peers = packet
157            .answers()
158            .iter()
159            .filter_map(|record| {
160                if record.name().to_string() != SERVICE_NAME_FQDN {
161                    return None;
162                }
163
164                let RData::PTR(record_value) = record.data() else {
165                    return None;
166                };
167
168                MdnsPeer::new(packet, record_value, record.ttl())
169            })
170            .collect();
171
172        MdnsResponse { peers, from }
173    }
174
175    pub(crate) fn extract_discovered(
176        &self,
177        now: Instant,
178        local_peer_id: PeerId,
179    ) -> impl Iterator<Item = (PeerId, Multiaddr, Instant)> + '_ {
180        self.discovered_peers()
181            .filter(move |peer| peer.id() != &local_peer_id)
182            .flat_map(move |peer| {
183                let observed = self.observed_address();
184                let new_expiration = now + peer.ttl();
185
186                peer.addresses().iter().filter_map(move |address| {
187                    let new_addr = _address_translation(address, &observed)?;
188                    let new_addr = new_addr.with_p2p(*peer.id()).ok()?;
189
190                    Some((*peer.id(), new_addr, new_expiration))
191                })
192            })
193    }
194
195    /// Source address of the packet.
196    pub(crate) fn remote_addr(&self) -> &SocketAddr {
197        &self.from
198    }
199
200    fn observed_address(&self) -> Multiaddr {
201        // We replace the IP address with the address we observe the
202        // remote as and the address they listen on.
203        let obs_ip = Protocol::from(self.remote_addr().ip());
204        let obs_port = Protocol::Udp(self.remote_addr().port());
205
206        Multiaddr::empty().with(obs_ip).with(obs_port)
207    }
208
209    /// Returns the list of peers that have been reported in this packet.
210    ///
211    /// > **Note**: Keep in mind that this will also contain the responses we sent ourselves.
212    fn discovered_peers(&self) -> impl Iterator<Item = &MdnsPeer> {
213        self.peers.iter()
214    }
215}
216
217impl fmt::Debug for MdnsResponse {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        f.debug_struct("MdnsResponse")
220            .field("from", self.remote_addr())
221            .finish()
222    }
223}
224
225/// A peer discovered by the service.
226pub(crate) struct MdnsPeer {
227    addrs: Vec<Multiaddr>,
228    /// Id of the peer.
229    peer_id: PeerId,
230    /// TTL of the record in seconds.
231    ttl: u32,
232}
233
234impl MdnsPeer {
235    /// Creates a new `MdnsPeer` based on the provided `Packet`.
236    pub(crate) fn new(packet: &Message, record_value: &Name, ttl: u32) -> Option<MdnsPeer> {
237        let mut my_peer_id: Option<PeerId> = None;
238        let addrs = packet
239            .additionals()
240            .iter()
241            .filter_map(|add_record| {
242                if add_record.name() != record_value {
243                    return None;
244                }
245
246                if let RData::TXT(ref txt) = add_record.data() {
247                    Some(txt)
248                } else {
249                    None
250                }
251            })
252            .flat_map(|txt| txt.iter())
253            .filter_map(|txt| {
254                // TODO: wrong, txt can be multiple character strings
255                let addr = dns::decode_character_string(txt).ok()?;
256
257                if !addr.starts_with(b"dnsaddr=") {
258                    return None;
259                }
260
261                let mut addr = str::from_utf8(&addr[8..]).ok()?.parse::<Multiaddr>().ok()?;
262
263                match addr.pop() {
264                    Some(Protocol::P2p(peer_id)) => {
265                        if let Some(pid) = &my_peer_id {
266                            if peer_id != *pid {
267                                return None;
268                            }
269                        } else {
270                            my_peer_id.replace(peer_id);
271                        }
272                    }
273                    _ => return None,
274                };
275                Some(addr)
276            })
277            .collect();
278
279        my_peer_id.map(|peer_id| MdnsPeer {
280            addrs,
281            peer_id,
282            ttl,
283        })
284    }
285
286    /// Returns the id of the peer.
287    #[inline]
288    pub(crate) fn id(&self) -> &PeerId {
289        &self.peer_id
290    }
291
292    /// Returns the requested time-to-live for the record.
293    #[inline]
294    pub(crate) fn ttl(&self) -> Duration {
295        Duration::from_secs(u64::from(self.ttl))
296    }
297
298    /// Returns the list of addresses the peer says it is listening on.
299    ///
300    /// Filters out invalid addresses.
301    pub(crate) fn addresses(&self) -> &Vec<Multiaddr> {
302        &self.addrs
303    }
304}
305
306impl fmt::Debug for MdnsPeer {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        f.debug_struct("MdnsPeer")
309            .field("peer_id", &self.peer_id)
310            .finish()
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::{super::dns::build_query_response, *};
317
318    #[test]
319    fn test_create_mdns_peer() {
320        let ttl = 300;
321        let peer_id = PeerId::random();
322
323        let mut addr1: Multiaddr = "/ip4/1.2.3.4/tcp/5000".parse().expect("bad multiaddress");
324        let mut addr2: Multiaddr = "/ip6/::1/udp/10000".parse().expect("bad multiaddress");
325        addr1.push(Protocol::P2p(peer_id));
326        addr2.push(Protocol::P2p(peer_id));
327
328        let packets = build_query_response(
329            0xf8f8,
330            peer_id,
331            vec![&addr1, &addr2].into_iter(),
332            Duration::from_secs(60),
333        );
334
335        for bytes in packets {
336            let packet = Message::from_vec(&bytes).expect("unable to parse packet");
337            let record_value = packet
338                .answers()
339                .iter()
340                .filter_map(|record| {
341                    if record.name().to_utf8() != SERVICE_NAME_FQDN {
342                        return None;
343                    }
344                    let RData::PTR(record_value) = record.data() else {
345                        return None;
346                    };
347                    Some(record_value)
348                })
349                .next()
350                .expect("empty record value");
351
352            let peer = MdnsPeer::new(&packet, record_value, ttl).expect("fail to create peer");
353            assert_eq!(peer.peer_id, peer_id);
354        }
355    }
356}