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