mdns_sd/
service_info.rs

1//! Define `ServiceInfo` to represent a service and its operations.
2
3#[cfg(feature = "logging")]
4use crate::log::debug;
5use crate::{
6    dns_parser::{DnsRecordBox, DnsRecordExt, DnsSrv, RRType},
7    Error, Result,
8};
9use if_addrs::{IfAddr, Interface};
10use std::{
11    cmp,
12    collections::{HashMap, HashSet},
13    convert::TryInto,
14    fmt,
15    net::{IpAddr, Ipv4Addr},
16    str::FromStr,
17};
18
19/// Default TTL values in seconds
20const DNS_HOST_TTL: u32 = 120; // 2 minutes for host records (A, SRV etc) per RFC6762
21const DNS_OTHER_TTL: u32 = 4500; // 75 minutes for non-host records (PTR, TXT etc) per RFC6762
22
23/// Complete info about a Service Instance.
24///
25/// We can construct some PTR, one SRV and one TXT record from this info,
26/// as well as A (IPv4 Address) and AAAA (IPv6 Address) records.
27#[derive(Debug, Clone)]
28pub struct ServiceInfo {
29    ty_domain: String, // <service>.<domain>
30
31    /// See RFC6763 section 7.1 about "Subtypes":
32    /// <https://datatracker.ietf.org/doc/html/rfc6763#section-7.1>
33    sub_domain: Option<String>, // <subservice>._sub.<service>.<domain>
34
35    fullname: String, // <instance>.<service>.<domain>
36    server: String,   // fully qualified name for service host
37    addresses: HashSet<IpAddr>,
38    port: u16,
39    host_ttl: u32,  // used for SRV and Address records
40    other_ttl: u32, // used for PTR and TXT records
41    priority: u16,
42    weight: u16,
43    txt_properties: TxtProperties,
44    addr_auto: bool, // Let the system update addresses automatically.
45
46    status: HashMap<Interface, ServiceStatus>,
47
48    /// Whether we need to probe names before announcing this service.
49    requires_probe: bool,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub(crate) enum ServiceStatus {
54    Probing,
55    Announced,
56    Unknown,
57}
58
59impl ServiceInfo {
60    /// Creates a new service info.
61    ///
62    /// `ty_domain` is the service type and the domain label, for example
63    /// "_my-service._udp.local.".
64    ///
65    /// `my_name` is the instance name, without the service type suffix.
66    ///
67    /// `host_name` is the "host" in the context of DNS. It is used as the "name"
68    /// in the address records (i.e. TYPE_A and TYPE_AAAA records). It means that
69    /// for the same hostname in the same local network, the service resolves in
70    /// the same addresses. Be sure to check it if you see unexpected addresses resolved.
71    ///
72    /// `properties` can be `None` or key/value string pairs, in a type that
73    /// implements [`IntoTxtProperties`] trait. It supports:
74    /// - `HashMap<String, String>`
75    /// - `Option<HashMap<String, String>>`
76    /// - slice of tuple: `&[(K, V)]` where `K` and `V` are [`std::string::ToString`].
77    ///
78    /// Note: The maximum length of a single property string is `255`, Property that exceed the length are truncated.
79    /// > `len(key + value) < u8::MAX`
80    ///
81    /// `ip` can be one or more IP addresses, in a type that implements
82    /// [`AsIpAddrs`] trait. It supports:
83    ///
84    /// - Single IPv4: `"192.168.0.1"`
85    /// - Single IPv6: `"2001:0db8::7334"`
86    /// - Multiple IPv4 separated by comma: `"192.168.0.1,192.168.0.2"`
87    /// - Multiple IPv6 separated by comma: `"2001:0db8::7334,2001:0db8::7335"`
88    /// - A slice of IPv4: `&["192.168.0.1", "192.168.0.2"]`
89    /// - A slice of IPv6: `&["2001:0db8::7334", "2001:0db8::7335"]`
90    /// - A mix of IPv4 and IPv6: `"192.168.0.1,2001:0db8::7334"`
91    /// - All the above formats with [IpAddr] or `String` instead of `&str`.
92    ///
93    /// The host TTL and other TTL are set to default values.
94    pub fn new<Ip: AsIpAddrs, P: IntoTxtProperties>(
95        ty_domain: &str,
96        my_name: &str,
97        host_name: &str,
98        ip: Ip,
99        port: u16,
100        properties: P,
101    ) -> Result<Self> {
102        let (ty_domain, sub_domain) = split_sub_domain(ty_domain);
103
104        let fullname = format!("{}.{}", my_name, ty_domain);
105        let ty_domain = ty_domain.to_string();
106        let sub_domain = sub_domain.map(str::to_string);
107        let server = normalize_hostname(host_name.to_string());
108        let addresses = ip.as_ip_addrs()?;
109        let txt_properties = properties.into_txt_properties();
110
111        // RFC6763 section 6.4: https://www.rfc-editor.org/rfc/rfc6763#section-6.4
112        // The characters of a key MUST be printable US-ASCII values (0x20-0x7E)
113        // [RFC20], excluding '=' (0x3D).
114        for prop in txt_properties.iter() {
115            let key = prop.key();
116            if !key.is_ascii() {
117                return Err(Error::Msg(format!(
118                    "TXT property key {} is not ASCII",
119                    prop.key()
120                )));
121            }
122            if key.contains('=') {
123                return Err(Error::Msg(format!(
124                    "TXT property key {} contains '='",
125                    prop.key()
126                )));
127            }
128        }
129
130        let this = Self {
131            ty_domain,
132            sub_domain,
133            fullname,
134            server,
135            addresses,
136            port,
137            host_ttl: DNS_HOST_TTL,
138            other_ttl: DNS_OTHER_TTL,
139            priority: 0,
140            weight: 0,
141            txt_properties,
142            addr_auto: false,
143            status: HashMap::new(),
144            requires_probe: true,
145        };
146
147        Ok(this)
148    }
149
150    /// Indicates that the library should automatically
151    /// update the addresses of this service, when IP
152    /// address(es) are added or removed on the host.
153    pub const fn enable_addr_auto(mut self) -> Self {
154        self.addr_auto = true;
155        self
156    }
157
158    /// Returns if the service's addresses will be updated
159    /// automatically when the host IP addrs change.
160    pub const fn is_addr_auto(&self) -> bool {
161        self.addr_auto
162    }
163
164    /// Set whether this service info requires name probing for potential name conflicts.
165    ///
166    /// By default, it is true (i.e. requires probing) for every service info. You
167    /// set it to `false` only when you are sure there are no conflicts, or for testing purposes.
168    pub fn set_requires_probe(&mut self, enable: bool) {
169        self.requires_probe = enable;
170    }
171
172    /// Returns whether this service info requires name probing for potential name conflicts.
173    ///
174    /// By default, it returns true for every service info.
175    pub const fn requires_probe(&self) -> bool {
176        self.requires_probe
177    }
178
179    /// Returns the service type including the domain label.
180    ///
181    /// For example: "_my-service._udp.local.".
182    #[inline]
183    pub fn get_type(&self) -> &str {
184        &self.ty_domain
185    }
186
187    /// Returns the service subtype including the domain label,
188    /// if subtype has been defined.
189    ///
190    /// For example: "_printer._sub._http._tcp.local.".
191    #[inline]
192    pub const fn get_subtype(&self) -> &Option<String> {
193        &self.sub_domain
194    }
195
196    /// Returns a reference of the service fullname.
197    ///
198    /// This is useful, for example, in unregister.
199    #[inline]
200    pub fn get_fullname(&self) -> &str {
201        &self.fullname
202    }
203
204    /// Returns the properties from TXT records.
205    #[inline]
206    pub const fn get_properties(&self) -> &TxtProperties {
207        &self.txt_properties
208    }
209
210    /// Returns a property for a given `key`, where `key` is
211    /// case insensitive.
212    ///
213    /// Returns `None` if `key` does not exist.
214    pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
215        self.txt_properties.get(key)
216    }
217
218    /// Returns a property value for a given `key`, where `key` is
219    /// case insensitive.
220    ///
221    /// Returns `None` if `key` does not exist.
222    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
223        self.txt_properties.get_property_val(key)
224    }
225
226    /// Returns a property value string for a given `key`, where `key` is
227    /// case insensitive.
228    ///
229    /// Returns `None` if `key` does not exist.
230    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
231        self.txt_properties.get_property_val_str(key)
232    }
233
234    /// Returns the service's hostname.
235    #[inline]
236    pub fn get_hostname(&self) -> &str {
237        &self.server
238    }
239
240    /// Returns the service's port.
241    #[inline]
242    pub const fn get_port(&self) -> u16 {
243        self.port
244    }
245
246    /// Returns the service's addresses
247    #[inline]
248    pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
249        &self.addresses
250    }
251
252    /// Returns the service's IPv4 addresses only.
253    pub fn get_addresses_v4(&self) -> HashSet<&Ipv4Addr> {
254        let mut ipv4_addresses = HashSet::new();
255
256        for ip in &self.addresses {
257            if let IpAddr::V4(ipv4) = ip {
258                ipv4_addresses.insert(ipv4);
259            }
260        }
261
262        ipv4_addresses
263    }
264
265    /// Returns the service's TTL used for SRV and Address records.
266    #[inline]
267    pub const fn get_host_ttl(&self) -> u32 {
268        self.host_ttl
269    }
270
271    /// Returns the service's TTL used for PTR and TXT records.
272    #[inline]
273    pub const fn get_other_ttl(&self) -> u32 {
274        self.other_ttl
275    }
276
277    /// Returns the service's priority used in SRV records.
278    #[inline]
279    pub const fn get_priority(&self) -> u16 {
280        self.priority
281    }
282
283    /// Returns the service's weight used in SRV records.
284    #[inline]
285    pub const fn get_weight(&self) -> u16 {
286        self.weight
287    }
288
289    /// Returns a list of addresses that are in the same LAN as
290    /// the interface `intf`.
291    pub(crate) fn get_addrs_on_intf(&self, intf: &Interface) -> Vec<IpAddr> {
292        self.addresses
293            .iter()
294            .filter(|a| valid_ip_on_intf(a, intf))
295            .copied()
296            .collect()
297    }
298
299    /// Returns whether the service info is ready to be resolved.
300    pub(crate) fn is_ready(&self) -> bool {
301        let some_missing = self.ty_domain.is_empty()
302            || self.fullname.is_empty()
303            || self.server.is_empty()
304            || self.addresses.is_empty();
305        !some_missing
306    }
307
308    /// Insert `addr` into service info addresses.
309    pub(crate) fn insert_ipaddr(&mut self, addr: IpAddr) {
310        self.addresses.insert(addr);
311    }
312
313    pub(crate) fn remove_ipaddr(&mut self, addr: &IpAddr) {
314        self.addresses.remove(addr);
315    }
316
317    pub(crate) fn generate_txt(&self) -> Vec<u8> {
318        encode_txt(self.get_properties().iter())
319    }
320
321    pub(crate) fn set_port(&mut self, port: u16) {
322        self.port = port;
323    }
324
325    pub(crate) fn set_hostname(&mut self, hostname: String) {
326        self.server = normalize_hostname(hostname);
327    }
328
329    /// Returns true if properties are updated.
330    pub(crate) fn set_properties_from_txt(&mut self, txt: &[u8]) -> bool {
331        let properties = decode_txt_unique(txt);
332        if self.txt_properties.properties != properties {
333            self.txt_properties = TxtProperties { properties };
334            true
335        } else {
336            false
337        }
338    }
339
340    pub(crate) fn set_subtype(&mut self, subtype: String) {
341        self.sub_domain = Some(subtype);
342    }
343
344    /// host_ttl is for SRV and address records
345    /// currently only used for testing.
346    pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
347        self.host_ttl = ttl;
348    }
349
350    /// other_ttl is for PTR and TXT records.
351    pub(crate) fn _set_other_ttl(&mut self, ttl: u32) {
352        self.other_ttl = ttl;
353    }
354
355    pub(crate) fn set_status(&mut self, intf: &Interface, status: ServiceStatus) {
356        match self.status.get_mut(intf) {
357            Some(service_status) => {
358                *service_status = status;
359            }
360            None => {
361                self.status.entry(intf.clone()).or_insert(status);
362            }
363        }
364    }
365
366    pub(crate) fn get_status(&self, intf: &Interface) -> ServiceStatus {
367        self.status
368            .get(intf)
369            .cloned()
370            .unwrap_or(ServiceStatus::Unknown)
371    }
372
373    /// Consumes self and returns a resolved service, i.e. a lite version of `ServiceInfo`.
374    pub fn as_resolved_service(self) -> ResolvedService {
375        ResolvedService {
376            ty_domain: self.ty_domain,
377            sub_ty_domain: self.sub_domain,
378            fullname: self.fullname,
379            host: self.server,
380            port: self.port,
381            addresses: self.addresses,
382            txt_properties: self.txt_properties,
383        }
384    }
385}
386
387/// Removes potentially duplicated ".local." at the end of "hostname".
388fn normalize_hostname(mut hostname: String) -> String {
389    if hostname.ends_with(".local.local.") {
390        let new_len = hostname.len() - "local.".len();
391        hostname.truncate(new_len);
392    }
393    hostname
394}
395
396/// This trait allows for parsing an input into a set of one or multiple [`Ipv4Addr`].
397pub trait AsIpAddrs {
398    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>>;
399}
400
401impl<T: AsIpAddrs> AsIpAddrs for &T {
402    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
403        (*self).as_ip_addrs()
404    }
405}
406
407/// Supports one address or multiple addresses separated by `,`.
408/// For example: "127.0.0.1,127.0.0.2".
409///
410/// If the string is empty, will return an empty set.
411impl AsIpAddrs for &str {
412    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
413        let mut addrs = HashSet::new();
414
415        if !self.is_empty() {
416            let iter = self.split(',').map(str::trim).map(IpAddr::from_str);
417            for addr in iter {
418                let addr = addr.map_err(|err| Error::ParseIpAddr(err.to_string()))?;
419                addrs.insert(addr);
420            }
421        }
422
423        Ok(addrs)
424    }
425}
426
427impl AsIpAddrs for String {
428    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
429        self.as_str().as_ip_addrs()
430    }
431}
432
433/// Support slice. Example: &["127.0.0.1", "127.0.0.2"]
434impl<I: AsIpAddrs> AsIpAddrs for &[I] {
435    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
436        let mut addrs = HashSet::new();
437
438        for result in self.iter().map(I::as_ip_addrs) {
439            addrs.extend(result?);
440        }
441
442        Ok(addrs)
443    }
444}
445
446/// Optimization for zero sized/empty values, as `()` will never take up any space or evaluate to
447/// anything, helpful in contexts where we just want an empty value.
448impl AsIpAddrs for () {
449    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
450        Ok(HashSet::new())
451    }
452}
453
454impl AsIpAddrs for std::net::IpAddr {
455    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
456        let mut ips = HashSet::new();
457        ips.insert(*self);
458
459        Ok(ips)
460    }
461}
462
463/// Represents properties in a TXT record.
464///
465/// The key string of a property is case insensitive, and only
466/// one [`TxtProperty`] is stored for the same key.
467///
468/// [RFC 6763](https://www.rfc-editor.org/rfc/rfc6763#section-6.4):
469/// "A given key SHOULD NOT appear more than once in a TXT record."
470#[derive(Debug, Clone, PartialEq, Eq)]
471pub struct TxtProperties {
472    // Use `Vec` instead of `HashMap` to keep the order of insertions.
473    properties: Vec<TxtProperty>,
474}
475
476impl TxtProperties {
477    /// Returns an iterator for all properties.
478    pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
479        self.properties.iter()
480    }
481
482    /// Returns the number of properties.
483    pub fn len(&self) -> usize {
484        self.properties.len()
485    }
486
487    /// Returns if the properties are empty.
488    pub fn is_empty(&self) -> bool {
489        self.properties.is_empty()
490    }
491
492    /// Returns a property for a given `key`, where `key` is
493    /// case insensitive.
494    pub fn get(&self, key: &str) -> Option<&TxtProperty> {
495        let key = key.to_lowercase();
496        self.properties
497            .iter()
498            .find(|&prop| prop.key.to_lowercase() == key)
499    }
500
501    /// Returns a property value for a given `key`, where `key` is
502    /// case insensitive.
503    ///
504    /// Returns `None` if `key` does not exist.
505    /// Returns `Some(Option<&u8>)` for its value.
506    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
507        self.get(key).map(|x| x.val())
508    }
509
510    /// Returns a property value string for a given `key`, where `key` is
511    /// case insensitive.
512    ///
513    /// Returns `None` if `key` does not exist.
514    /// Returns `Some("")` if its value is `None` or is empty.
515    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
516        self.get(key).map(|x| x.val_str())
517    }
518
519    /// Consumes properties and returns a hashmap, where the keys are the properties keys.
520    ///
521    /// If a property value is empty, return an empty string (because RFC 6763 allows empty values).
522    /// If a property value is non-empty but not valid UTF-8, skip the property and log a message.
523    pub fn into_property_map_str(self) -> HashMap<String, String> {
524        self.properties
525            .into_iter()
526            .filter_map(|property| {
527                let val_string = property.val.map_or(Some(String::new()), |val| {
528                    String::from_utf8(val)
529                        .map_err(|e| {
530                            debug!("Property value contains invalid UTF-8: {e}");
531                        })
532                        .ok()
533                })?;
534                Some((property.key, val_string))
535            })
536            .collect()
537    }
538}
539
540impl fmt::Display for TxtProperties {
541    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542        let delimiter = ", ";
543        let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
544        write!(f, "({})", props.join(delimiter))
545    }
546}
547
548/// Represents a property in a TXT record.
549#[derive(Clone, PartialEq, Eq)]
550pub struct TxtProperty {
551    /// The name of the property. The original cases are kept.
552    key: String,
553
554    /// RFC 6763 says values are bytes, not necessarily UTF-8.
555    /// It is also possible that there is no value, in which case
556    /// the key is a boolean key.
557    val: Option<Vec<u8>>,
558}
559
560impl TxtProperty {
561    /// Returns the key of a property.
562    pub fn key(&self) -> &str {
563        &self.key
564    }
565
566    /// Returns the value of a property, which could be `None`.
567    ///
568    /// To obtain a `&str` of the value, use `val_str()` instead.
569    pub fn val(&self) -> Option<&[u8]> {
570        self.val.as_deref()
571    }
572
573    /// Returns the value of a property as str.
574    pub fn val_str(&self) -> &str {
575        self.val
576            .as_ref()
577            .map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
578    }
579}
580
581/// Supports constructing from a tuple.
582impl<K, V> From<&(K, V)> for TxtProperty
583where
584    K: ToString,
585    V: ToString,
586{
587    fn from(prop: &(K, V)) -> Self {
588        Self {
589            key: prop.0.to_string(),
590            val: Some(prop.1.to_string().into_bytes()),
591        }
592    }
593}
594
595impl<K, V> From<(K, V)> for TxtProperty
596where
597    K: ToString,
598    V: AsRef<[u8]>,
599{
600    fn from(prop: (K, V)) -> Self {
601        Self {
602            key: prop.0.to_string(),
603            val: Some(prop.1.as_ref().into()),
604        }
605    }
606}
607
608/// Support a property that has no value.
609impl From<&str> for TxtProperty {
610    fn from(key: &str) -> Self {
611        Self {
612            key: key.to_string(),
613            val: None,
614        }
615    }
616}
617
618impl fmt::Display for TxtProperty {
619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620        write!(f, "{}={}", self.key, self.val_str())
621    }
622}
623
624/// Mimic the default debug output for a struct, with a twist:
625/// - If self.var is UTF-8, will output it as a string in double quotes.
626/// - If self.var is not UTF-8, will output its bytes as in hex.
627impl fmt::Debug for TxtProperty {
628    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629        let val_string = self.val.as_ref().map_or_else(
630            || "None".to_string(),
631            |v| {
632                std::str::from_utf8(&v[..]).map_or_else(
633                    |_| format!("Some({})", u8_slice_to_hex(&v[..])),
634                    |s| format!("Some(\"{}\")", s),
635                )
636            },
637        );
638
639        write!(
640            f,
641            "TxtProperty {{key: \"{}\", val: {}}}",
642            &self.key, &val_string,
643        )
644    }
645}
646
647const HEX_TABLE: [char; 16] = [
648    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
649];
650
651/// Create a hex string from `slice`, with a "0x" prefix.
652///
653/// For example, [1u8, 2u8] -> "0x0102"
654fn u8_slice_to_hex(slice: &[u8]) -> String {
655    let mut hex = String::with_capacity(slice.len() * 2 + 2);
656    hex.push_str("0x");
657    for b in slice {
658        hex.push(HEX_TABLE[(b >> 4) as usize]);
659        hex.push(HEX_TABLE[(b & 0x0F) as usize]);
660    }
661    hex
662}
663
664/// This trait allows for converting inputs into [`TxtProperties`].
665pub trait IntoTxtProperties {
666    fn into_txt_properties(self) -> TxtProperties;
667}
668
669impl IntoTxtProperties for HashMap<String, String> {
670    fn into_txt_properties(mut self) -> TxtProperties {
671        let properties = self
672            .drain()
673            .map(|(key, val)| TxtProperty {
674                key,
675                val: Some(val.into_bytes()),
676            })
677            .collect();
678        TxtProperties { properties }
679    }
680}
681
682/// Mainly for backward compatibility.
683impl IntoTxtProperties for Option<HashMap<String, String>> {
684    fn into_txt_properties(self) -> TxtProperties {
685        self.map_or_else(
686            || TxtProperties {
687                properties: Vec::new(),
688            },
689            |h| h.into_txt_properties(),
690        )
691    }
692}
693
694/// Support Vec like `[("k1", "v1"), ("k2", "v2")]`.
695impl<'a, T: 'a> IntoTxtProperties for &'a [T]
696where
697    TxtProperty: From<&'a T>,
698{
699    fn into_txt_properties(self) -> TxtProperties {
700        let mut properties = Vec::new();
701        let mut keys = HashSet::new();
702        for t in self.iter() {
703            let prop = TxtProperty::from(t);
704            let key = prop.key.to_lowercase();
705            if keys.insert(key) {
706                // Only push a new entry if the key did not exist.
707                //
708                // RFC 6763: https://www.rfc-editor.org/rfc/rfc6763#section-6.4
709                //
710                // "If a client receives a TXT record containing the same key more than
711                //    once, then the client MUST silently ignore all but the first
712                //    occurrence of that attribute. "
713                properties.push(prop);
714            }
715        }
716        TxtProperties { properties }
717    }
718}
719
720impl IntoTxtProperties for Vec<TxtProperty> {
721    fn into_txt_properties(self) -> TxtProperties {
722        TxtProperties { properties: self }
723    }
724}
725
726// Convert from properties key/value pairs to DNS TXT record content
727fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
728    let mut bytes = Vec::new();
729    for prop in properties {
730        let mut s = prop.key.clone().into_bytes();
731        if let Some(v) = &prop.val {
732            s.extend(b"=");
733            s.extend(v);
734        }
735
736        // Property that exceed the length are truncated
737        let sz: u8 = s.len().try_into().unwrap_or_else(|_| {
738            debug!("Property {} is too long, truncating to 255 bytes", prop.key);
739            s.resize(u8::MAX as usize, 0);
740            u8::MAX
741        });
742
743        // TXT uses (Length,Value) format for each property,
744        // i.e. the first byte is the length.
745        bytes.push(sz);
746        bytes.extend(s);
747    }
748    if bytes.is_empty() {
749        bytes.push(0);
750    }
751    bytes
752}
753
754// Convert from DNS TXT record content to key/value pairs
755pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
756    let mut properties = Vec::new();
757    let mut offset = 0;
758    while offset < txt.len() {
759        let length = txt[offset] as usize;
760        if length == 0 {
761            break; // reached the end
762        }
763        offset += 1; // move over the length byte
764
765        let offset_end = offset + length;
766        if offset_end > txt.len() {
767            debug!("DNS TXT record contains invalid data: Size given for property would be out of range. (offset={}, length={}, offset_end={}, record length={})", offset, length, offset_end, txt.len());
768            break; // Skipping the rest of the record content, as the size for this property would already be out of range.
769        }
770        let kv_bytes = &txt[offset..offset_end];
771
772        // split key and val using the first `=`
773        let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
774            || (kv_bytes.to_vec(), None),
775            |idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
776        );
777
778        // Make sure the key can be stored in UTF-8.
779        match String::from_utf8(k) {
780            Ok(k_string) => {
781                properties.push(TxtProperty {
782                    key: k_string,
783                    val: v,
784                });
785            }
786            Err(e) => debug!("failed to convert to String from key: {}", e),
787        }
788
789        offset += length;
790    }
791
792    properties
793}
794
795fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
796    let mut properties = decode_txt(txt);
797
798    // Remove duplicated keys and retain only the first appearance
799    // of each key.
800    let mut keys = HashSet::new();
801    properties.retain(|p| {
802        let key = p.key().to_lowercase();
803        keys.insert(key) // returns True if key is new.
804    });
805    properties
806}
807
808/// Returns true if `addr` is in the same network of `intf`.
809pub(crate) fn valid_ip_on_intf(addr: &IpAddr, intf: &Interface) -> bool {
810    match (addr, &intf.addr) {
811        (IpAddr::V4(addr), IfAddr::V4(intf)) => {
812            let netmask = u32::from(intf.netmask);
813            let intf_net = u32::from(intf.ip) & netmask;
814            let addr_net = u32::from(*addr) & netmask;
815            addr_net == intf_net
816        }
817        (IpAddr::V6(addr), IfAddr::V6(intf)) => {
818            let netmask = u128::from(intf.netmask);
819            let intf_net = u128::from(intf.ip) & netmask;
820            let addr_net = u128::from(*addr) & netmask;
821            addr_net == intf_net
822        }
823        _ => false,
824    }
825}
826
827/// Returns true if `addr_a` and `addr_b` are in the same network as `intf`.
828pub fn valid_two_addrs_on_intf(addr_a: &IpAddr, addr_b: &IpAddr, intf: &Interface) -> bool {
829    match (addr_a, addr_b, &intf.addr) {
830        (IpAddr::V4(ipv4_a), IpAddr::V4(ipv4_b), IfAddr::V4(intf)) => {
831            let netmask = u32::from(intf.netmask);
832            let intf_net = u32::from(intf.ip) & netmask;
833            let net_a = u32::from(*ipv4_a) & netmask;
834            let net_b = u32::from(*ipv4_b) & netmask;
835            net_a == intf_net && net_b == intf_net
836        }
837        (IpAddr::V6(ipv6_a), IpAddr::V6(ipv6_b), IfAddr::V6(intf)) => {
838            let netmask = u128::from(intf.netmask);
839            let intf_net = u128::from(intf.ip) & netmask;
840            let net_a = u128::from(*ipv6_a) & netmask;
841            let net_b = u128::from(*ipv6_b) & netmask;
842            net_a == intf_net && net_b == intf_net
843        }
844        _ => false,
845    }
846}
847
848/// A probing for a particular name.
849#[derive(Debug)]
850pub(crate) struct Probe {
851    /// All records probing for the same name.
852    pub(crate) records: Vec<DnsRecordBox>,
853
854    /// The fullnames of services that are probing these records.
855    /// These are the original service names, will not change per conflicts.
856    pub(crate) waiting_services: HashSet<String>,
857
858    /// The time (T) to send the first query .
859    pub(crate) start_time: u64,
860
861    /// The time to send the next (including the first) query.
862    pub(crate) next_send: u64,
863}
864
865impl Probe {
866    pub(crate) fn new(start_time: u64) -> Self {
867        // RFC 6762: https://datatracker.ietf.org/doc/html/rfc6762#section-8.1:
868        //
869        // "250 ms after the first query, the host should send a second; then,
870        //   250 ms after that, a third.  If, by 250 ms after the third probe, no
871        //   conflicting Multicast DNS responses have been received, the host may
872        //   move to the next step, announcing. "
873        let next_send = start_time;
874
875        Self {
876            records: Vec::new(),
877            waiting_services: HashSet::new(),
878            start_time,
879            next_send,
880        }
881    }
882
883    /// Add a new record with the same probing name in a sorted order.
884    pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
885        /*
886        RFC 6762: https://datatracker.ietf.org/doc/html/rfc6762#section-8.2.1
887
888        " The records are sorted using the same lexicographical order as
889        described above, that is, if the record classes differ, the record
890        with the lower class number comes first.  If the classes are the same
891        but the rrtypes differ, the record with the lower rrtype number comes
892        first."
893         */
894        let insert_position = self
895            .records
896            .binary_search_by(
897                |existing| match existing.get_class().cmp(&record.get_class()) {
898                    std::cmp::Ordering::Equal => existing.get_type().cmp(&record.get_type()),
899                    other => other,
900                },
901            )
902            .unwrap_or_else(|pos| pos);
903
904        self.records.insert(insert_position, record);
905    }
906
907    /// Compares with `incoming` records. Postpone probe and retry if we yield.
908    pub(crate) fn tiebreaking(&mut self, incoming: &[&DnsRecordBox], now: u64, probe_name: &str) {
909        /*
910        RFC 6762 section 8.2: https://datatracker.ietf.org/doc/html/rfc6762#section-8.2
911        ...
912        if the host finds that its own data is lexicographically later, it
913        simply ignores the other host's probe.  If the host finds that its
914        own data is lexicographically earlier, then it defers to the winning
915        host by waiting one second, and then begins probing for this record
916        again.
917        */
918        let min_len = self.records.len().min(incoming.len());
919
920        // Compare elements up to the length of the shorter vector
921        let mut cmp_result = cmp::Ordering::Equal;
922        for (i, incoming_record) in incoming.iter().enumerate().take(min_len) {
923            match self.records[i].compare(incoming_record.as_ref()) {
924                cmp::Ordering::Equal => continue,
925                other => {
926                    cmp_result = other;
927                    break; // exit loop on first difference
928                }
929            }
930        }
931
932        if cmp_result == cmp::Ordering::Equal {
933            // If all compared records are equal, compare the lengths of the records.
934            cmp_result = self.records.len().cmp(&incoming.len());
935        }
936
937        match cmp_result {
938            cmp::Ordering::Less => {
939                debug!("tiebreaking '{probe_name}': LOST, will wait for one second",);
940                self.start_time = now + 1000; // wait and restart.
941                self.next_send = now + 1000;
942            }
943            ordering => {
944                debug!("tiebreaking '{probe_name}': {:?}", ordering);
945            }
946        }
947    }
948
949    pub(crate) fn update_next_send(&mut self, now: u64) {
950        self.next_send = now + 250;
951    }
952
953    /// Returns whether this probe is finished.
954    pub(crate) fn expired(&self, now: u64) -> bool {
955        // The 2nd query is T + 250ms, the 3rd query is T + 500ms,
956        // The expire time is T + 750ms
957        now >= self.start_time + 750
958    }
959}
960
961/// DNS records of all the registered services.
962pub(crate) struct DnsRegistry {
963    /// keyed by the name of all related records.
964    /*
965     When a host is probing for a group of related records with the same
966    name (e.g., the SRV and TXT record describing a DNS-SD service), only
967    a single question need be placed in the Question Section, since query
968    type "ANY" (255) is used, which will elicit answers for all records
969    with that name.  However, for tiebreaking to work correctly in all
970    cases, the Authority Section must contain *all* the records and
971    proposed rdata being probed for uniqueness.
972     */
973    pub(crate) probing: HashMap<String, Probe>,
974
975    /// Already done probing, or no need to probe.
976    pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
977
978    /// timers of the newly added probes.
979    pub(crate) new_timers: Vec<u64>,
980
981    /// Mapping from original names to new names.
982    pub(crate) name_changes: HashMap<String, String>,
983}
984
985impl DnsRegistry {
986    pub(crate) fn new() -> Self {
987        Self {
988            probing: HashMap::new(),
989            active: HashMap::new(),
990            new_timers: Vec::new(),
991            name_changes: HashMap::new(),
992        }
993    }
994
995    pub(crate) fn is_probing_done<T>(
996        &mut self,
997        answer: &T,
998        service_name: &str,
999        start_time: u64,
1000    ) -> bool
1001    where
1002        T: DnsRecordExt + Send + 'static,
1003    {
1004        if let Some(active_records) = self.active.get(answer.get_name()) {
1005            for record in active_records.iter() {
1006                if answer.matches(record.as_ref()) {
1007                    debug!(
1008                        "found active record {} {}",
1009                        answer.get_type(),
1010                        answer.get_name(),
1011                    );
1012                    return true;
1013                }
1014            }
1015        }
1016
1017        let probe = self
1018            .probing
1019            .entry(answer.get_name().to_string())
1020            .or_insert_with(|| {
1021                debug!("new probe of {}", answer.get_name());
1022                Probe::new(start_time)
1023            });
1024
1025        self.new_timers.push(probe.next_send);
1026
1027        for record in probe.records.iter() {
1028            if answer.matches(record.as_ref()) {
1029                debug!(
1030                    "found existing record {} in probe of '{}'",
1031                    answer.get_type(),
1032                    answer.get_name(),
1033                );
1034                probe.waiting_services.insert(service_name.to_string());
1035                return false; // Found existing probe for the same record.
1036            }
1037        }
1038
1039        debug!(
1040            "insert record {} into probe of {}",
1041            answer.get_type(),
1042            answer.get_name(),
1043        );
1044        probe.insert_record(answer.clone_box());
1045        probe.waiting_services.insert(service_name.to_string());
1046
1047        false
1048    }
1049
1050    /// check all records in "probing" and "active":
1051    /// if the record is SRV, and hostname is set to original, remove it.
1052    /// and create a new SRV with "host" set to "new_name" and put into "probing".
1053    pub(crate) fn update_hostname(
1054        &mut self,
1055        original: &str,
1056        new_name: &str,
1057        probe_time: u64,
1058    ) -> bool {
1059        let mut found_records = Vec::new();
1060        let mut new_timer_added = false;
1061
1062        for (_name, probe) in self.probing.iter_mut() {
1063            probe.records.retain(|record| {
1064                if record.get_type() == RRType::SRV {
1065                    if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1066                        if srv.host() == original {
1067                            let mut new_record = srv.clone();
1068                            new_record.set_host(new_name.to_string());
1069                            found_records.push(new_record);
1070                            return false;
1071                        }
1072                    }
1073                }
1074                true
1075            });
1076        }
1077
1078        for (_name, records) in self.active.iter_mut() {
1079            records.retain(|record| {
1080                if record.get_type() == RRType::SRV {
1081                    if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1082                        if srv.host() == original {
1083                            let mut new_record = srv.clone();
1084                            new_record.set_host(new_name.to_string());
1085                            found_records.push(new_record);
1086                            return false;
1087                        }
1088                    }
1089                }
1090                true
1091            });
1092        }
1093
1094        for record in found_records {
1095            let probe = match self.probing.get_mut(record.get_name()) {
1096                Some(p) => {
1097                    p.start_time = probe_time; // restart this probe.
1098                    p
1099                }
1100                None => {
1101                    let new_probe = self
1102                        .probing
1103                        .entry(record.get_name().to_string())
1104                        .or_insert_with(|| Probe::new(probe_time));
1105                    new_timer_added = true;
1106                    new_probe
1107                }
1108            };
1109
1110            debug!(
1111                "insert record {} with new hostname {new_name} into probe for: {}",
1112                record.get_type(),
1113                record.get_name()
1114            );
1115            probe.insert_record(record.boxed());
1116        }
1117
1118        new_timer_added
1119    }
1120}
1121
1122/// Returns a tuple of (service_type_domain, optional_sub_domain)
1123pub(crate) fn split_sub_domain(domain: &str) -> (&str, Option<&str>) {
1124    if let Some((_, ty_domain)) = domain.rsplit_once("._sub.") {
1125        (ty_domain, Some(domain))
1126    } else {
1127        (domain, None)
1128    }
1129}
1130
1131/// Represents a resolved service as a plain data struct.
1132/// This is from a client (i.e. querier) point of view.
1133#[non_exhaustive]
1134pub struct ResolvedService {
1135    /// Service type and domain. For example, "_http._tcp.local."
1136    pub ty_domain: String,
1137
1138    /// Optional service subtype and domain.
1139    ///
1140    /// See RFC6763 section 7.1 about "Subtypes":
1141    /// <https://datatracker.ietf.org/doc/html/rfc6763#section-7.1>
1142    /// For example, "_printer._sub._http._tcp.local."
1143    pub sub_ty_domain: Option<String>,
1144
1145    /// Full name of the service. For example, "my-service._http._tcp.local."
1146    pub fullname: String,
1147
1148    /// Host name of the service. For example, "my-server1.local."
1149    pub host: String,
1150
1151    /// Port of the service. I.e. TCP or UDP port.
1152    pub port: u16,
1153
1154    /// Addresses of the service. IPv4 or IPv6 addresses.
1155    pub addresses: HashSet<IpAddr>,
1156
1157    /// Properties of the service, decoded from TXT record.
1158    pub txt_properties: TxtProperties,
1159}
1160
1161impl ResolvedService {
1162    /// Returns true if the service data is valid, i.e. ready to be used.
1163    pub fn is_valid(&self) -> bool {
1164        let some_missing = self.ty_domain.is_empty()
1165            || self.fullname.is_empty()
1166            || self.host.is_empty()
1167            || self.addresses.is_empty();
1168        !some_missing
1169    }
1170}
1171
1172#[cfg(test)]
1173mod tests {
1174    use super::{
1175        decode_txt, encode_txt, u8_slice_to_hex, valid_two_addrs_on_intf, ServiceInfo, TxtProperty,
1176    };
1177    use if_addrs::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
1178    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
1179
1180    #[test]
1181    fn test_txt_encode_decode() {
1182        let properties = vec![
1183            TxtProperty::from(&("key1", "value1")),
1184            TxtProperty::from(&("key2", "value2")),
1185        ];
1186
1187        // test encode
1188        let property_count = properties.len();
1189        let encoded = encode_txt(properties.iter());
1190        assert_eq!(
1191            encoded.len(),
1192            "key1=value1".len() + "key2=value2".len() + property_count
1193        );
1194        assert_eq!(encoded[0] as usize, "key1=value1".len());
1195
1196        // test decode
1197        let decoded = decode_txt(&encoded);
1198        assert!(properties[..] == decoded[..]);
1199
1200        // test empty value
1201        let properties = vec![TxtProperty::from(&("key3", ""))];
1202        let property_count = properties.len();
1203        let encoded = encode_txt(properties.iter());
1204        assert_eq!(encoded.len(), "key3=".len() + property_count);
1205
1206        let decoded = decode_txt(&encoded);
1207        assert_eq!(properties, decoded);
1208
1209        // test non-string value
1210        let binary_val: Vec<u8> = vec![123, 234, 0];
1211        let binary_len = binary_val.len();
1212        let properties = vec![TxtProperty::from(("key4", binary_val))];
1213        let property_count = properties.len();
1214        let encoded = encode_txt(properties.iter());
1215        assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
1216
1217        let decoded = decode_txt(&encoded);
1218        assert_eq!(properties, decoded);
1219
1220        // test value that contains '='
1221        let properties = vec![TxtProperty::from(("key5", "val=5"))];
1222        let property_count = properties.len();
1223        let encoded = encode_txt(properties.iter());
1224        assert_eq!(
1225            encoded.len(),
1226            "key5=".len() + "val=5".len() + property_count
1227        );
1228
1229        let decoded = decode_txt(&encoded);
1230        assert_eq!(properties, decoded);
1231
1232        // test a property that has no value.
1233        let properties = vec![TxtProperty::from("key6")];
1234        let property_count = properties.len();
1235        let encoded = encode_txt(properties.iter());
1236        assert_eq!(encoded.len(), "key6".len() + property_count);
1237        let decoded = decode_txt(&encoded);
1238        assert_eq!(properties, decoded);
1239
1240        // test very long property.
1241        let properties = vec![TxtProperty::from(
1242            String::from_utf8(vec![0x30; 1024]).unwrap().as_str(), // A long string of 0 char
1243        )];
1244        let property_count = properties.len();
1245        let encoded = encode_txt(properties.iter());
1246        assert_eq!(encoded.len(), 255 + property_count);
1247        let decoded = decode_txt(&encoded);
1248        assert_eq!(
1249            vec![TxtProperty::from(
1250                String::from_utf8(vec![0x30; 255]).unwrap().as_str()
1251            )],
1252            decoded
1253        );
1254    }
1255
1256    #[test]
1257    fn test_set_properties_from_txt() {
1258        // Three duplicated keys.
1259        let properties = vec![
1260            TxtProperty::from(&("one", "1")),
1261            TxtProperty::from(&("ONE", "2")),
1262            TxtProperty::from(&("One", "3")),
1263        ];
1264        let encoded = encode_txt(properties.iter());
1265
1266        // Simple decode does not remove duplicated keys.
1267        let decoded = decode_txt(&encoded);
1268        assert_eq!(decoded.len(), 3);
1269
1270        // ServiceInfo removes duplicated keys and keeps only the first one.
1271        let mut service_info =
1272            ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
1273        service_info.set_properties_from_txt(&encoded);
1274        assert_eq!(service_info.get_properties().len(), 1);
1275
1276        // Verify the only one property.
1277        let prop = service_info.get_properties().iter().next().unwrap();
1278        assert_eq!(prop.key, "one");
1279        assert_eq!(prop.val_str(), "1");
1280    }
1281
1282    #[test]
1283    fn test_u8_slice_to_hex() {
1284        let bytes = [0x01u8, 0x02u8, 0x03u8];
1285        let hex = u8_slice_to_hex(&bytes);
1286        assert_eq!(hex.as_str(), "0x010203");
1287
1288        let slice = "abcdefghijklmnopqrstuvwxyz";
1289        let hex = u8_slice_to_hex(slice.as_bytes());
1290        assert_eq!(hex.len(), slice.len() * 2 + 2);
1291        assert_eq!(
1292            hex.as_str(),
1293            "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
1294        );
1295    }
1296
1297    #[test]
1298    fn test_txt_property_debug() {
1299        // Test UTF-8 property value.
1300        let prop_1 = TxtProperty {
1301            key: "key1".to_string(),
1302            val: Some("val1".to_string().into()),
1303        };
1304        let prop_1_debug = format!("{:?}", &prop_1);
1305        assert_eq!(
1306            prop_1_debug,
1307            "TxtProperty {key: \"key1\", val: Some(\"val1\")}"
1308        );
1309
1310        // Test non-UTF-8 property value.
1311        let prop_2 = TxtProperty {
1312            key: "key2".to_string(),
1313            val: Some(vec![150u8, 151u8, 152u8]),
1314        };
1315        let prop_2_debug = format!("{:?}", &prop_2);
1316        assert_eq!(
1317            prop_2_debug,
1318            "TxtProperty {key: \"key2\", val: Some(0x969798)}"
1319        );
1320    }
1321
1322    #[test]
1323    fn test_txt_decode_property_size_out_of_bounds() {
1324        // Construct a TXT record with an invalid property length that would be out of bounds.
1325        let encoded: Vec<u8> = vec![
1326            0x0b, // Length 11
1327            b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1328            b'1', // key1=value1 (Length 11)
1329            0x10, // Length 16 (Would be out of bounds)
1330            b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1331            b'2', // key2=value2 (Length 11)
1332        ];
1333        // Decode the record content
1334        let decoded = decode_txt(&encoded);
1335        // We expect the out of bounds length for the second property to have caused the rest of the record content to be skipped.
1336        // Test that we only parsed the first property.
1337        assert_eq!(decoded.len(), 1);
1338        // Test that the key of the property we parsed is "key1"
1339        assert_eq!(decoded[0].key, "key1");
1340    }
1341
1342    #[test]
1343    fn test_valid_two_addrs_on_intf() {
1344        // test IPv4
1345
1346        let ipv4_netmask = Ipv4Addr::new(192, 168, 1, 0);
1347        let ipv4_intf_addr = IfAddr::V4(Ifv4Addr {
1348            ip: Ipv4Addr::new(192, 168, 1, 10),
1349            netmask: ipv4_netmask,
1350            prefixlen: 24,
1351            broadcast: None,
1352        });
1353        let ipv4_intf = Interface {
1354            name: "e0".to_string(),
1355            addr: ipv4_intf_addr,
1356            index: Some(1),
1357            #[cfg(windows)]
1358            adapter_name: "ethernet".to_string(),
1359        };
1360        let ipv4_a = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10));
1361        let ipv4_b = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 11));
1362
1363        let result = valid_two_addrs_on_intf(&ipv4_a, &ipv4_b, &ipv4_intf);
1364        assert!(result);
1365
1366        let ipv4_c = IpAddr::V4(Ipv4Addr::new(172, 17, 0, 1));
1367        let result = valid_two_addrs_on_intf(&ipv4_a, &ipv4_c, &ipv4_intf);
1368        assert!(!result);
1369
1370        // test IPv6 (generated by AI)
1371
1372        let ipv6_netmask = Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0); // Equivalent to /32 prefix length
1373        let ipv6_intf_addr = IfAddr::V6(Ifv6Addr {
1374            ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
1375            netmask: ipv6_netmask,
1376            prefixlen: 32,
1377            broadcast: None,
1378        });
1379        let ipv6_intf = Interface {
1380            name: "eth0".to_string(),
1381            addr: ipv6_intf_addr,
1382            index: Some(2),
1383            #[cfg(windows)]
1384            adapter_name: "ethernet".to_string(),
1385        };
1386        let ipv6_a = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
1387        let ipv6_b = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2));
1388
1389        let result = valid_two_addrs_on_intf(&ipv6_a, &ipv6_b, &ipv6_intf);
1390        assert!(result); // Expect true since both addresses are in the same subnet
1391
1392        let ipv6_c = IpAddr::V6(Ipv6Addr::new(0x2002, 0xdb8, 0, 0, 0, 0, 0, 1));
1393        let result = valid_two_addrs_on_intf(&ipv6_a, &ipv6_c, &ipv6_intf);
1394        assert!(!result); // Expect false since addresses are in different subnets
1395    }
1396}