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
503impl AsIpAddrs for Box<dyn AsIpAddrs> {
504    fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
505        self.as_ref().as_ip_addrs()
506    }
507}
508
509/// Represents properties in a TXT record.
510///
511/// The key string of a property is case insensitive, and only
512/// one [`TxtProperty`] is stored for the same key.
513///
514/// [RFC 6763](https://www.rfc-editor.org/rfc/rfc6763#section-6.4):
515/// "A given key SHOULD NOT appear more than once in a TXT record."
516#[derive(Debug, Clone, PartialEq, Eq)]
517pub struct TxtProperties {
518    // Use `Vec` instead of `HashMap` to keep the order of insertions.
519    properties: Vec<TxtProperty>,
520}
521
522impl Default for TxtProperties {
523    fn default() -> Self {
524        TxtProperties::new()
525    }
526}
527
528impl TxtProperties {
529    pub fn new() -> Self {
530        TxtProperties {
531            properties: Vec::new(),
532        }
533    }
534
535    /// Returns an iterator for all properties.
536    pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
537        self.properties.iter()
538    }
539
540    /// Returns the number of properties.
541    pub fn len(&self) -> usize {
542        self.properties.len()
543    }
544
545    /// Returns if the properties are empty.
546    pub fn is_empty(&self) -> bool {
547        self.properties.is_empty()
548    }
549
550    /// Returns a property for a given `key`, where `key` is
551    /// case insensitive.
552    pub fn get(&self, key: &str) -> Option<&TxtProperty> {
553        let key = key.to_lowercase();
554        self.properties
555            .iter()
556            .find(|&prop| prop.key.to_lowercase() == key)
557    }
558
559    /// Returns a property value for a given `key`, where `key` is
560    /// case insensitive.
561    ///
562    /// Returns `None` if `key` does not exist.
563    /// Returns `Some(Option<&u8>)` for its value.
564    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
565        self.get(key).map(|x| x.val())
566    }
567
568    /// Returns a property value string for a given `key`, where `key` is
569    /// case insensitive.
570    ///
571    /// Returns `None` if `key` does not exist.
572    /// Returns `Some("")` if its value is `None` or is empty.
573    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
574        self.get(key).map(|x| x.val_str())
575    }
576
577    /// Consumes properties and returns a hashmap, where the keys are the properties keys.
578    ///
579    /// If a property value is empty, return an empty string (because RFC 6763 allows empty values).
580    /// If a property value is non-empty but not valid UTF-8, skip the property and log a message.
581    pub fn into_property_map_str(self) -> HashMap<String, String> {
582        self.properties
583            .into_iter()
584            .filter_map(|property| {
585                let val_string = property.val.map_or(Some(String::new()), |val| {
586                    String::from_utf8(val)
587                        .map_err(|e| {
588                            debug!("Property value contains invalid UTF-8: {e}");
589                        })
590                        .ok()
591                })?;
592                Some((property.key, val_string))
593            })
594            .collect()
595    }
596}
597
598impl fmt::Display for TxtProperties {
599    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
600        let delimiter = ", ";
601        let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
602        write!(f, "({})", props.join(delimiter))
603    }
604}
605
606impl From<&[u8]> for TxtProperties {
607    fn from(txt: &[u8]) -> Self {
608        let properties = decode_txt_unique(txt);
609        TxtProperties { properties }
610    }
611}
612
613/// Represents a property in a TXT record.
614#[derive(Clone, PartialEq, Eq)]
615pub struct TxtProperty {
616    /// The name of the property. The original cases are kept.
617    key: String,
618
619    /// RFC 6763 says values are bytes, not necessarily UTF-8.
620    /// It is also possible that there is no value, in which case
621    /// the key is a boolean key.
622    val: Option<Vec<u8>>,
623}
624
625impl TxtProperty {
626    /// Returns the key of a property.
627    pub fn key(&self) -> &str {
628        &self.key
629    }
630
631    /// Returns the value of a property, which could be `None`.
632    ///
633    /// To obtain a `&str` of the value, use `val_str()` instead.
634    pub fn val(&self) -> Option<&[u8]> {
635        self.val.as_deref()
636    }
637
638    /// Returns the value of a property as str.
639    pub fn val_str(&self) -> &str {
640        self.val
641            .as_ref()
642            .map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
643    }
644}
645
646/// Supports constructing from a tuple.
647impl<K, V> From<&(K, V)> for TxtProperty
648where
649    K: ToString,
650    V: ToString,
651{
652    fn from(prop: &(K, V)) -> Self {
653        Self {
654            key: prop.0.to_string(),
655            val: Some(prop.1.to_string().into_bytes()),
656        }
657    }
658}
659
660impl<K, V> From<(K, V)> for TxtProperty
661where
662    K: ToString,
663    V: AsRef<[u8]>,
664{
665    fn from(prop: (K, V)) -> Self {
666        Self {
667            key: prop.0.to_string(),
668            val: Some(prop.1.as_ref().into()),
669        }
670    }
671}
672
673/// Support a property that has no value.
674impl From<&str> for TxtProperty {
675    fn from(key: &str) -> Self {
676        Self {
677            key: key.to_string(),
678            val: None,
679        }
680    }
681}
682
683impl fmt::Display for TxtProperty {
684    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685        write!(f, "{}={}", self.key, self.val_str())
686    }
687}
688
689/// Mimic the default debug output for a struct, with a twist:
690/// - If self.var is UTF-8, will output it as a string in double quotes.
691/// - If self.var is not UTF-8, will output its bytes as in hex.
692impl fmt::Debug for TxtProperty {
693    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
694        let val_string = self.val.as_ref().map_or_else(
695            || "None".to_string(),
696            |v| {
697                std::str::from_utf8(&v[..]).map_or_else(
698                    |_| format!("Some({})", u8_slice_to_hex(&v[..])),
699                    |s| format!("Some(\"{s}\")"),
700                )
701            },
702        );
703
704        write!(
705            f,
706            "TxtProperty {{key: \"{}\", val: {}}}",
707            &self.key, &val_string,
708        )
709    }
710}
711
712const HEX_TABLE: [char; 16] = [
713    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
714];
715
716/// Create a hex string from `slice`, with a "0x" prefix.
717///
718/// For example, [1u8, 2u8] -> "0x0102"
719fn u8_slice_to_hex(slice: &[u8]) -> String {
720    let mut hex = String::with_capacity(slice.len() * 2 + 2);
721    hex.push_str("0x");
722    for b in slice {
723        hex.push(HEX_TABLE[(b >> 4) as usize]);
724        hex.push(HEX_TABLE[(b & 0x0F) as usize]);
725    }
726    hex
727}
728
729/// This trait allows for converting inputs into [`TxtProperties`].
730pub trait IntoTxtProperties {
731    fn into_txt_properties(self) -> TxtProperties;
732}
733
734impl IntoTxtProperties for HashMap<String, String> {
735    fn into_txt_properties(mut self) -> TxtProperties {
736        let properties = self
737            .drain()
738            .map(|(key, val)| TxtProperty {
739                key,
740                val: Some(val.into_bytes()),
741            })
742            .collect();
743        TxtProperties { properties }
744    }
745}
746
747/// Mainly for backward compatibility.
748impl IntoTxtProperties for Option<HashMap<String, String>> {
749    fn into_txt_properties(self) -> TxtProperties {
750        self.map_or_else(
751            || TxtProperties {
752                properties: Vec::new(),
753            },
754            |h| h.into_txt_properties(),
755        )
756    }
757}
758
759/// Support Vec like `[("k1", "v1"), ("k2", "v2")]`.
760impl<'a, T: 'a> IntoTxtProperties for &'a [T]
761where
762    TxtProperty: From<&'a T>,
763{
764    fn into_txt_properties(self) -> TxtProperties {
765        let mut properties = Vec::new();
766        let mut keys = HashSet::new();
767        for t in self.iter() {
768            let prop = TxtProperty::from(t);
769            let key = prop.key.to_lowercase();
770            if keys.insert(key) {
771                // Only push a new entry if the key did not exist.
772                //
773                // RFC 6763: https://www.rfc-editor.org/rfc/rfc6763#section-6.4
774                //
775                // "If a client receives a TXT record containing the same key more than
776                //    once, then the client MUST silently ignore all but the first
777                //    occurrence of that attribute. "
778                properties.push(prop);
779            }
780        }
781        TxtProperties { properties }
782    }
783}
784
785impl IntoTxtProperties for Vec<TxtProperty> {
786    fn into_txt_properties(self) -> TxtProperties {
787        TxtProperties { properties: self }
788    }
789}
790
791// Convert from properties key/value pairs to DNS TXT record content
792fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
793    let mut bytes = Vec::new();
794    for prop in properties {
795        let mut s = prop.key.clone().into_bytes();
796        if let Some(v) = &prop.val {
797            s.extend(b"=");
798            s.extend(v);
799        }
800
801        // Property that exceed the length are truncated
802        let sz: u8 = s.len().try_into().unwrap_or_else(|_| {
803            debug!("Property {} is too long, truncating to 255 bytes", prop.key);
804            s.resize(u8::MAX as usize, 0);
805            u8::MAX
806        });
807
808        // TXT uses (Length,Value) format for each property,
809        // i.e. the first byte is the length.
810        bytes.push(sz);
811        bytes.extend(s);
812    }
813    if bytes.is_empty() {
814        bytes.push(0);
815    }
816    bytes
817}
818
819// Convert from DNS TXT record content to key/value pairs
820pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
821    let mut properties = Vec::new();
822    let mut offset = 0;
823    while offset < txt.len() {
824        let length = txt[offset] as usize;
825        if length == 0 {
826            break; // reached the end
827        }
828        offset += 1; // move over the length byte
829
830        let offset_end = offset + length;
831        if offset_end > txt.len() {
832            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());
833            break; // Skipping the rest of the record content, as the size for this property would already be out of range.
834        }
835        let kv_bytes = &txt[offset..offset_end];
836
837        // split key and val using the first `=`
838        let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
839            || (kv_bytes.to_vec(), None),
840            |idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
841        );
842
843        // Make sure the key can be stored in UTF-8.
844        match String::from_utf8(k) {
845            Ok(k_string) => {
846                properties.push(TxtProperty {
847                    key: k_string,
848                    val: v,
849                });
850            }
851            Err(e) => debug!("failed to convert to String from key: {}", e),
852        }
853
854        offset += length;
855    }
856
857    properties
858}
859
860fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
861    let mut properties = decode_txt(txt);
862
863    // Remove duplicated keys and retain only the first appearance
864    // of each key.
865    let mut keys = HashSet::new();
866    properties.retain(|p| {
867        let key = p.key().to_lowercase();
868        keys.insert(key) // returns True if key is new.
869    });
870    properties
871}
872
873/// Returns true if `addr` is in the same network of `intf`.
874pub(crate) fn valid_ip_on_intf(addr: &IpAddr, if_addr: &IfAddr) -> bool {
875    match (addr, if_addr) {
876        (IpAddr::V4(addr), IfAddr::V4(if_v4)) => {
877            let netmask = u32::from(if_v4.netmask);
878            let intf_net = u32::from(if_v4.ip) & netmask;
879            let addr_net = u32::from(*addr) & netmask;
880            addr_net == intf_net
881        }
882        (IpAddr::V6(addr), IfAddr::V6(if_v6)) => {
883            let netmask = u128::from(if_v6.netmask);
884            let intf_net = u128::from(if_v6.ip) & netmask;
885            let addr_net = u128::from(*addr) & netmask;
886            addr_net == intf_net
887        }
888        _ => false,
889    }
890}
891
892/// A probing for a particular name.
893#[derive(Debug)]
894pub(crate) struct Probe {
895    /// All records probing for the same name.
896    pub(crate) records: Vec<DnsRecordBox>,
897
898    /// The fullnames of services that are probing these records.
899    /// These are the original service names, will not change per conflicts.
900    pub(crate) waiting_services: HashSet<String>,
901
902    /// The time (T) to send the first query .
903    pub(crate) start_time: u64,
904
905    /// The time to send the next (including the first) query.
906    pub(crate) next_send: u64,
907}
908
909impl Probe {
910    pub(crate) fn new(start_time: u64) -> Self {
911        // RFC 6762: https://datatracker.ietf.org/doc/html/rfc6762#section-8.1:
912        //
913        // "250 ms after the first query, the host should send a second; then,
914        //   250 ms after that, a third.  If, by 250 ms after the third probe, no
915        //   conflicting Multicast DNS responses have been received, the host may
916        //   move to the next step, announcing. "
917        let next_send = start_time;
918
919        Self {
920            records: Vec::new(),
921            waiting_services: HashSet::new(),
922            start_time,
923            next_send,
924        }
925    }
926
927    /// Add a new record with the same probing name in a sorted order.
928    pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
929        /*
930        RFC 6762: https://datatracker.ietf.org/doc/html/rfc6762#section-8.2.1
931
932        " The records are sorted using the same lexicographical order as
933        described above, that is, if the record classes differ, the record
934        with the lower class number comes first.  If the classes are the same
935        but the rrtypes differ, the record with the lower rrtype number comes
936        first."
937         */
938        let insert_position = self
939            .records
940            .binary_search_by(
941                |existing| match existing.get_class().cmp(&record.get_class()) {
942                    std::cmp::Ordering::Equal => existing.get_type().cmp(&record.get_type()),
943                    other => other,
944                },
945            )
946            .unwrap_or_else(|pos| pos);
947
948        self.records.insert(insert_position, record);
949    }
950
951    /// Compares with `incoming` records. Postpone probe and retry if we yield.
952    pub(crate) fn tiebreaking(&mut self, incoming: &[&DnsRecordBox], now: u64, probe_name: &str) {
953        /*
954        RFC 6762 section 8.2: https://datatracker.ietf.org/doc/html/rfc6762#section-8.2
955        ...
956        if the host finds that its own data is lexicographically later, it
957        simply ignores the other host's probe.  If the host finds that its
958        own data is lexicographically earlier, then it defers to the winning
959        host by waiting one second, and then begins probing for this record
960        again.
961        */
962        let min_len = self.records.len().min(incoming.len());
963
964        // Compare elements up to the length of the shorter vector
965        let mut cmp_result = cmp::Ordering::Equal;
966        for (i, incoming_record) in incoming.iter().enumerate().take(min_len) {
967            match self.records[i].compare(incoming_record.as_ref()) {
968                cmp::Ordering::Equal => continue,
969                other => {
970                    cmp_result = other;
971                    break; // exit loop on first difference
972                }
973            }
974        }
975
976        if cmp_result == cmp::Ordering::Equal {
977            // If all compared records are equal, compare the lengths of the records.
978            cmp_result = self.records.len().cmp(&incoming.len());
979        }
980
981        match cmp_result {
982            cmp::Ordering::Less => {
983                debug!("tiebreaking '{probe_name}': LOST, will wait for one second",);
984                self.start_time = now + 1000; // wait and restart.
985                self.next_send = now + 1000;
986            }
987            ordering => {
988                debug!("tiebreaking '{probe_name}': {:?}", ordering);
989            }
990        }
991    }
992
993    pub(crate) fn update_next_send(&mut self, now: u64) {
994        self.next_send = now + 250;
995    }
996
997    /// Returns whether this probe is finished.
998    pub(crate) fn expired(&self, now: u64) -> bool {
999        // The 2nd query is T + 250ms, the 3rd query is T + 500ms,
1000        // The expire time is T + 750ms
1001        now >= self.start_time + 750
1002    }
1003}
1004
1005/// DNS records of all the registered services.
1006pub(crate) struct DnsRegistry {
1007    /// keyed by the name of all related records.
1008    /*
1009     When a host is probing for a group of related records with the same
1010    name (e.g., the SRV and TXT record describing a DNS-SD service), only
1011    a single question need be placed in the Question Section, since query
1012    type "ANY" (255) is used, which will elicit answers for all records
1013    with that name.  However, for tiebreaking to work correctly in all
1014    cases, the Authority Section must contain *all* the records and
1015    proposed rdata being probed for uniqueness.
1016     */
1017    pub(crate) probing: HashMap<String, Probe>,
1018
1019    /// Already done probing, or no need to probe.
1020    pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
1021
1022    /// timers of the newly added probes.
1023    pub(crate) new_timers: Vec<u64>,
1024
1025    /// Mapping from original names to new names.
1026    pub(crate) name_changes: HashMap<String, String>,
1027}
1028
1029impl DnsRegistry {
1030    pub(crate) fn new() -> Self {
1031        Self {
1032            probing: HashMap::new(),
1033            active: HashMap::new(),
1034            new_timers: Vec::new(),
1035            name_changes: HashMap::new(),
1036        }
1037    }
1038
1039    pub(crate) fn is_probing_done<T>(
1040        &mut self,
1041        answer: &T,
1042        service_name: &str,
1043        start_time: u64,
1044    ) -> bool
1045    where
1046        T: DnsRecordExt + Send + 'static,
1047    {
1048        if let Some(active_records) = self.active.get(answer.get_name()) {
1049            for record in active_records.iter() {
1050                if answer.matches(record.as_ref()) {
1051                    debug!(
1052                        "found active record {} {}",
1053                        answer.get_type(),
1054                        answer.get_name(),
1055                    );
1056                    return true;
1057                }
1058            }
1059        }
1060
1061        let probe = self
1062            .probing
1063            .entry(answer.get_name().to_string())
1064            .or_insert_with(|| {
1065                debug!("new probe of {}", answer.get_name());
1066                Probe::new(start_time)
1067            });
1068
1069        self.new_timers.push(probe.next_send);
1070
1071        for record in probe.records.iter() {
1072            if answer.matches(record.as_ref()) {
1073                debug!(
1074                    "found existing record {} in probe of '{}'",
1075                    answer.get_type(),
1076                    answer.get_name(),
1077                );
1078                probe.waiting_services.insert(service_name.to_string());
1079                return false; // Found existing probe for the same record.
1080            }
1081        }
1082
1083        debug!(
1084            "insert record {} into probe of {}",
1085            answer.get_type(),
1086            answer.get_name(),
1087        );
1088        probe.insert_record(answer.clone_box());
1089        probe.waiting_services.insert(service_name.to_string());
1090
1091        false
1092    }
1093
1094    /// check all records in "probing" and "active":
1095    /// if the record is SRV, and hostname is set to original, remove it.
1096    /// and create a new SRV with "host" set to "new_name" and put into "probing".
1097    pub(crate) fn update_hostname(
1098        &mut self,
1099        original: &str,
1100        new_name: &str,
1101        probe_time: u64,
1102    ) -> bool {
1103        let mut found_records = Vec::new();
1104        let mut new_timer_added = false;
1105
1106        for (_name, probe) in self.probing.iter_mut() {
1107            probe.records.retain(|record| {
1108                if record.get_type() == RRType::SRV {
1109                    if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1110                        if srv.host() == original {
1111                            let mut new_record = srv.clone();
1112                            new_record.set_host(new_name.to_string());
1113                            found_records.push(new_record);
1114                            return false;
1115                        }
1116                    }
1117                }
1118                true
1119            });
1120        }
1121
1122        for (_name, records) in self.active.iter_mut() {
1123            records.retain(|record| {
1124                if record.get_type() == RRType::SRV {
1125                    if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1126                        if srv.host() == original {
1127                            let mut new_record = srv.clone();
1128                            new_record.set_host(new_name.to_string());
1129                            found_records.push(new_record);
1130                            return false;
1131                        }
1132                    }
1133                }
1134                true
1135            });
1136        }
1137
1138        for record in found_records {
1139            let probe = match self.probing.get_mut(record.get_name()) {
1140                Some(p) => {
1141                    p.start_time = probe_time; // restart this probe.
1142                    p
1143                }
1144                None => {
1145                    let new_probe = self
1146                        .probing
1147                        .entry(record.get_name().to_string())
1148                        .or_insert_with(|| Probe::new(probe_time));
1149                    new_timer_added = true;
1150                    new_probe
1151                }
1152            };
1153
1154            debug!(
1155                "insert record {} with new hostname {new_name} into probe for: {}",
1156                record.get_type(),
1157                record.get_name()
1158            );
1159            probe.insert_record(record.boxed());
1160        }
1161
1162        new_timer_added
1163    }
1164}
1165
1166/// Returns a tuple of (service_type_domain, optional_sub_domain)
1167pub(crate) fn split_sub_domain(domain: &str) -> (&str, Option<&str>) {
1168    if let Some((_, ty_domain)) = domain.rsplit_once("._sub.") {
1169        (ty_domain, Some(domain))
1170    } else {
1171        (domain, None)
1172    }
1173}
1174
1175/// Represents a resolved service as a plain data struct.
1176/// This is from a client (i.e. querier) point of view.
1177#[derive(Clone, Debug)]
1178#[non_exhaustive]
1179pub struct ResolvedService {
1180    /// Service type and domain. For example, "_http._tcp.local."
1181    pub ty_domain: String,
1182
1183    /// Optional service subtype and domain.
1184    ///
1185    /// See RFC6763 section 7.1 about "Subtypes":
1186    /// <https://datatracker.ietf.org/doc/html/rfc6763#section-7.1>
1187    /// For example, "_printer._sub._http._tcp.local."
1188    pub sub_ty_domain: Option<String>,
1189
1190    /// Full name of the service. For example, "my-service._http._tcp.local."
1191    pub fullname: String,
1192
1193    /// Host name of the service. For example, "my-server1.local."
1194    pub host: String,
1195
1196    /// Port of the service. I.e. TCP or UDP port.
1197    pub port: u16,
1198
1199    /// Addresses of the service. IPv4 or IPv6 addresses.
1200    pub addresses: HashSet<ScopedIp>,
1201
1202    /// Properties of the service, decoded from TXT record.
1203    pub txt_properties: TxtProperties,
1204}
1205
1206impl ResolvedService {
1207    /// Returns true if the service data is valid, i.e. ready to be used.
1208    pub fn is_valid(&self) -> bool {
1209        let some_missing = self.ty_domain.is_empty()
1210            || self.fullname.is_empty()
1211            || self.host.is_empty()
1212            || self.addresses.is_empty();
1213        !some_missing
1214    }
1215
1216    #[inline]
1217    pub const fn get_subtype(&self) -> &Option<String> {
1218        &self.sub_ty_domain
1219    }
1220
1221    #[inline]
1222    pub fn get_fullname(&self) -> &str {
1223        &self.fullname
1224    }
1225
1226    #[inline]
1227    pub fn get_hostname(&self) -> &str {
1228        &self.host
1229    }
1230
1231    #[inline]
1232    pub fn get_port(&self) -> u16 {
1233        self.port
1234    }
1235
1236    #[inline]
1237    pub fn get_addresses(&self) -> &HashSet<ScopedIp> {
1238        &self.addresses
1239    }
1240
1241    pub fn get_addresses_v4(&self) -> HashSet<Ipv4Addr> {
1242        self.addresses
1243            .iter()
1244            .filter_map(|ip| match ip {
1245                ScopedIp::V4(ipv4) => Some(*ipv4.addr()),
1246                _ => None,
1247            })
1248            .collect()
1249    }
1250
1251    #[inline]
1252    pub fn get_properties(&self) -> &TxtProperties {
1253        &self.txt_properties
1254    }
1255
1256    #[inline]
1257    pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
1258        self.txt_properties.get(key)
1259    }
1260
1261    pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
1262        self.txt_properties.get_property_val(key)
1263    }
1264
1265    pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
1266        self.txt_properties.get_property_val_str(key)
1267    }
1268}
1269
1270#[cfg(test)]
1271mod tests {
1272    use super::{decode_txt, encode_txt, u8_slice_to_hex, ServiceInfo, TxtProperty};
1273
1274    #[test]
1275    fn test_txt_encode_decode() {
1276        let properties = vec![
1277            TxtProperty::from(&("key1", "value1")),
1278            TxtProperty::from(&("key2", "value2")),
1279        ];
1280
1281        // test encode
1282        let property_count = properties.len();
1283        let encoded = encode_txt(properties.iter());
1284        assert_eq!(
1285            encoded.len(),
1286            "key1=value1".len() + "key2=value2".len() + property_count
1287        );
1288        assert_eq!(encoded[0] as usize, "key1=value1".len());
1289
1290        // test decode
1291        let decoded = decode_txt(&encoded);
1292        assert!(properties[..] == decoded[..]);
1293
1294        // test empty value
1295        let properties = vec![TxtProperty::from(&("key3", ""))];
1296        let property_count = properties.len();
1297        let encoded = encode_txt(properties.iter());
1298        assert_eq!(encoded.len(), "key3=".len() + property_count);
1299
1300        let decoded = decode_txt(&encoded);
1301        assert_eq!(properties, decoded);
1302
1303        // test non-string value
1304        let binary_val: Vec<u8> = vec![123, 234, 0];
1305        let binary_len = binary_val.len();
1306        let properties = vec![TxtProperty::from(("key4", binary_val))];
1307        let property_count = properties.len();
1308        let encoded = encode_txt(properties.iter());
1309        assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
1310
1311        let decoded = decode_txt(&encoded);
1312        assert_eq!(properties, decoded);
1313
1314        // test value that contains '='
1315        let properties = vec![TxtProperty::from(("key5", "val=5"))];
1316        let property_count = properties.len();
1317        let encoded = encode_txt(properties.iter());
1318        assert_eq!(
1319            encoded.len(),
1320            "key5=".len() + "val=5".len() + property_count
1321        );
1322
1323        let decoded = decode_txt(&encoded);
1324        assert_eq!(properties, decoded);
1325
1326        // test a property that has no value.
1327        let properties = vec![TxtProperty::from("key6")];
1328        let property_count = properties.len();
1329        let encoded = encode_txt(properties.iter());
1330        assert_eq!(encoded.len(), "key6".len() + property_count);
1331        let decoded = decode_txt(&encoded);
1332        assert_eq!(properties, decoded);
1333
1334        // test very long property.
1335        let properties = vec![TxtProperty::from(
1336            String::from_utf8(vec![0x30; 1024]).unwrap().as_str(), // A long string of 0 char
1337        )];
1338        let property_count = properties.len();
1339        let encoded = encode_txt(properties.iter());
1340        assert_eq!(encoded.len(), 255 + property_count);
1341        let decoded = decode_txt(&encoded);
1342        assert_eq!(
1343            vec![TxtProperty::from(
1344                String::from_utf8(vec![0x30; 255]).unwrap().as_str()
1345            )],
1346            decoded
1347        );
1348    }
1349
1350    #[test]
1351    fn test_set_properties_from_txt() {
1352        // Three duplicated keys.
1353        let properties = vec![
1354            TxtProperty::from(&("one", "1")),
1355            TxtProperty::from(&("ONE", "2")),
1356            TxtProperty::from(&("One", "3")),
1357        ];
1358        let encoded = encode_txt(properties.iter());
1359
1360        // Simple decode does not remove duplicated keys.
1361        let decoded = decode_txt(&encoded);
1362        assert_eq!(decoded.len(), 3);
1363
1364        // ServiceInfo removes duplicated keys and keeps only the first one.
1365        let mut service_info =
1366            ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
1367        service_info._set_properties_from_txt(&encoded);
1368        assert_eq!(service_info.get_properties().len(), 1);
1369
1370        // Verify the only one property.
1371        let prop = service_info.get_properties().iter().next().unwrap();
1372        assert_eq!(prop.key, "one");
1373        assert_eq!(prop.val_str(), "1");
1374    }
1375
1376    #[test]
1377    fn test_u8_slice_to_hex() {
1378        let bytes = [0x01u8, 0x02u8, 0x03u8];
1379        let hex = u8_slice_to_hex(&bytes);
1380        assert_eq!(hex.as_str(), "0x010203");
1381
1382        let slice = "abcdefghijklmnopqrstuvwxyz";
1383        let hex = u8_slice_to_hex(slice.as_bytes());
1384        assert_eq!(hex.len(), slice.len() * 2 + 2);
1385        assert_eq!(
1386            hex.as_str(),
1387            "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
1388        );
1389    }
1390
1391    #[test]
1392    fn test_txt_property_debug() {
1393        // Test UTF-8 property value.
1394        let prop_1 = TxtProperty {
1395            key: "key1".to_string(),
1396            val: Some("val1".to_string().into()),
1397        };
1398        let prop_1_debug = format!("{:?}", &prop_1);
1399        assert_eq!(
1400            prop_1_debug,
1401            "TxtProperty {key: \"key1\", val: Some(\"val1\")}"
1402        );
1403
1404        // Test non-UTF-8 property value.
1405        let prop_2 = TxtProperty {
1406            key: "key2".to_string(),
1407            val: Some(vec![150u8, 151u8, 152u8]),
1408        };
1409        let prop_2_debug = format!("{:?}", &prop_2);
1410        assert_eq!(
1411            prop_2_debug,
1412            "TxtProperty {key: \"key2\", val: Some(0x969798)}"
1413        );
1414    }
1415
1416    #[test]
1417    fn test_txt_decode_property_size_out_of_bounds() {
1418        // Construct a TXT record with an invalid property length that would be out of bounds.
1419        let encoded: Vec<u8> = vec![
1420            0x0b, // Length 11
1421            b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1422            b'1', // key1=value1 (Length 11)
1423            0x10, // Length 16 (Would be out of bounds)
1424            b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1425            b'2', // key2=value2 (Length 11)
1426        ];
1427        // Decode the record content
1428        let decoded = decode_txt(&encoded);
1429        // We expect the out of bounds length for the second property to have caused the rest of the record content to be skipped.
1430        // Test that we only parsed the first property.
1431        assert_eq!(decoded.len(), 1);
1432        // Test that the key of the property we parsed is "key1"
1433        assert_eq!(decoded[0].key, "key1");
1434    }
1435}