1#[cfg(feature = "logging")]
4use crate::log::{debug, trace};
5use crate::{
6 dns_parser::{DnsIncoming, DnsRecordBox, DnsRecordExt, DnsSrv, RRType, ScopedIp},
7 Error, IfKind, InterfaceId, Result,
8};
9use if_addrs::{IfAddr, Interface};
10use std::net::Ipv6Addr;
11use std::{
12 cmp,
13 collections::{HashMap, HashSet},
14 convert::TryInto,
15 fmt,
16 net::{IpAddr, Ipv4Addr},
17 str::FromStr,
18};
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23const DNS_HOST_TTL: u32 = 120; const DNS_OTHER_TTL: u32 = 4500; #[derive(Debug)]
29pub(crate) struct MyIntf {
30 pub(crate) name: String,
32
33 pub(crate) index: u32,
35
36 pub(crate) addrs: HashSet<IfAddr>,
38}
39
40impl MyIntf {
41 pub(crate) fn next_ifaddr_v4(&self) -> Option<&IfAddr> {
42 self.addrs.iter().find(|a| a.ip().is_ipv4())
43 }
44
45 pub(crate) fn next_ifaddr_v6(&self) -> Option<&IfAddr> {
46 self.addrs.iter().find(|a| a.ip().is_ipv6())
47 }
48}
49
50impl From<&MyIntf> for InterfaceId {
51 fn from(my_intf: &MyIntf) -> Self {
52 InterfaceId {
53 name: my_intf.name.clone(),
54 index: my_intf.index,
55 }
56 }
57}
58
59fn escape_instance_name(name: &str) -> String {
68 let mut result = String::with_capacity(name.len() + 10); for ch in name.chars() {
71 match ch {
72 '.' => {
73 result.push('\\');
74 result.push('.');
75 }
76 '\\' => {
77 result.push('\\');
78 result.push('\\');
79 }
80 _ => result.push(ch),
81 }
82 }
83
84 result
85}
86
87#[derive(Debug, Clone)]
92pub struct ServiceInfo {
93 ty_domain: String,
97
98 sub_domain: Option<String>, fullname: String, server: String, addresses: HashSet<IpAddr>,
105 port: u16,
106 host_ttl: u32, other_ttl: u32, priority: u16,
109 weight: u16,
110 txt_properties: TxtProperties,
111 addr_auto: bool, status: HashMap<u32, ServiceStatus>, requires_probe: bool,
117
118 supported_intfs: Vec<IfKind>,
120
121 is_link_local_only: bool,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub(crate) enum ServiceStatus {
127 Probing,
128 Announced,
129 Unknown,
130}
131
132impl ServiceInfo {
133 pub fn new<Ip: AsIpAddrs, P: IntoTxtProperties>(
169 ty_domain: &str,
170 my_name: &str,
171 host_name: &str,
172 ip: Ip,
173 port: u16,
174 properties: P,
175 ) -> Result<Self> {
176 let (ty_domain, sub_domain) = split_sub_domain(ty_domain);
177
178 let escaped_name = escape_instance_name(my_name);
179 let fullname = format!("{escaped_name}.{ty_domain}");
180 let ty_domain = ty_domain.to_string();
181 let sub_domain = sub_domain.map(str::to_string);
182 let server = normalize_hostname(host_name.to_string());
183 let addresses = ip.as_ip_addrs()?;
184 let txt_properties = properties.into_txt_properties();
185
186 for prop in txt_properties.iter() {
190 let key = prop.key();
191 if !key.is_ascii() {
192 return Err(Error::Msg(format!(
193 "TXT property key {} is not ASCII",
194 prop.key()
195 )));
196 }
197 if key.contains('=') {
198 return Err(Error::Msg(format!(
199 "TXT property key {} contains '='",
200 prop.key()
201 )));
202 }
203 }
204
205 let this = Self {
206 ty_domain,
207 sub_domain,
208 fullname,
209 server,
210 addresses,
211 port,
212 host_ttl: DNS_HOST_TTL,
213 other_ttl: DNS_OTHER_TTL,
214 priority: 0,
215 weight: 0,
216 txt_properties,
217 addr_auto: false,
218 status: HashMap::new(),
219 requires_probe: true,
220 is_link_local_only: false,
221 supported_intfs: vec![IfKind::All],
222 };
223
224 Ok(this)
225 }
226
227 pub const fn enable_addr_auto(mut self) -> Self {
231 self.addr_auto = true;
232 self
233 }
234
235 pub const fn is_addr_auto(&self) -> bool {
238 self.addr_auto
239 }
240
241 pub fn set_requires_probe(&mut self, enable: bool) {
246 self.requires_probe = enable;
247 }
248
249 pub fn set_link_local_only(&mut self, is_link_local_only: bool) {
253 self.is_link_local_only = is_link_local_only;
254 }
255
256 pub fn set_interfaces(&mut self, intfs: Vec<IfKind>) {
261 self.supported_intfs = intfs;
262 }
263
264 pub const fn requires_probe(&self) -> bool {
268 self.requires_probe
269 }
270
271 #[inline]
275 pub fn get_type(&self) -> &str {
276 &self.ty_domain
277 }
278
279 #[inline]
284 pub const fn get_subtype(&self) -> &Option<String> {
285 &self.sub_domain
286 }
287
288 pub(crate) fn matches_type_or_subtype(&self, name: &str) -> bool {
290 name == self.get_type() || self.get_subtype().as_ref().is_some_and(|v| v == name)
291 }
292
293 #[inline]
297 pub fn get_fullname(&self) -> &str {
298 &self.fullname
299 }
300
301 #[inline]
303 pub const fn get_properties(&self) -> &TxtProperties {
304 &self.txt_properties
305 }
306
307 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
312 self.txt_properties.get(key)
313 }
314
315 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
320 self.txt_properties.get_property_val(key)
321 }
322
323 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
328 self.txt_properties.get_property_val_str(key)
329 }
330
331 #[inline]
333 pub fn get_hostname(&self) -> &str {
334 &self.server
335 }
336
337 #[inline]
339 pub const fn get_port(&self) -> u16 {
340 self.port
341 }
342
343 #[inline]
345 pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
346 &self.addresses
347 }
348
349 pub fn get_addresses_v4(&self) -> HashSet<&Ipv4Addr> {
351 let mut ipv4_addresses = HashSet::new();
352
353 for ip in &self.addresses {
354 if let IpAddr::V4(ipv4) = ip {
355 ipv4_addresses.insert(ipv4);
356 }
357 }
358
359 ipv4_addresses
360 }
361
362 #[inline]
364 pub const fn get_host_ttl(&self) -> u32 {
365 self.host_ttl
366 }
367
368 #[inline]
370 pub const fn get_other_ttl(&self) -> u32 {
371 self.other_ttl
372 }
373
374 #[inline]
376 pub const fn get_priority(&self) -> u16 {
377 self.priority
378 }
379
380 #[inline]
382 pub const fn get_weight(&self) -> u16 {
383 self.weight
384 }
385
386 pub(crate) fn get_addrs_on_my_intf_v4(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
388 self.addresses
389 .iter()
390 .filter(|a| a.is_ipv4() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
391 .copied()
392 .collect()
393 }
394
395 pub(crate) fn get_addrs_on_my_intf_v6(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
396 self.addresses
397 .iter()
398 .filter(|a| a.is_ipv6() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
399 .copied()
400 .collect()
401 }
402
403 pub(crate) fn _is_ready(&self) -> bool {
405 let some_missing = self.ty_domain.is_empty()
406 || self.fullname.is_empty()
407 || self.server.is_empty()
408 || self.addresses.is_empty();
409 !some_missing
410 }
411
412 pub(crate) fn insert_ipaddr(&mut self, intf: &Interface) {
414 if self.is_address_supported(intf) {
415 self.addresses.insert(intf.addr.ip());
416 } else {
417 trace!(
418 "skipping unsupported address {} for service {}",
419 intf.addr.ip(),
420 self.fullname
421 );
422 }
423 }
424
425 pub(crate) fn remove_ipaddr(&mut self, addr: &IpAddr) {
426 self.addresses.remove(addr);
427 }
428
429 pub(crate) fn generate_txt(&self) -> Vec<u8> {
430 encode_txt(self.get_properties().iter())
431 }
432
433 pub(crate) fn _set_port(&mut self, port: u16) {
434 self.port = port;
435 }
436
437 pub(crate) fn _set_hostname(&mut self, hostname: String) {
438 self.server = normalize_hostname(hostname);
439 }
440
441 pub(crate) fn _set_properties_from_txt(&mut self, txt: &[u8]) -> bool {
443 let properties = decode_txt_unique(txt);
444 if self.txt_properties.properties != properties {
445 self.txt_properties = TxtProperties { properties };
446 true
447 } else {
448 false
449 }
450 }
451
452 pub(crate) fn _set_subtype(&mut self, subtype: String) {
453 self.sub_domain = Some(subtype);
454 }
455
456 pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
459 self.host_ttl = ttl;
460 }
461
462 pub(crate) fn _set_other_ttl(&mut self, ttl: u32) {
464 self.other_ttl = ttl;
465 }
466
467 pub(crate) fn set_status(&mut self, if_index: u32, status: ServiceStatus) {
468 match self.status.get_mut(&if_index) {
469 Some(service_status) => {
470 *service_status = status;
471 }
472 None => {
473 self.status.entry(if_index).or_insert(status);
474 }
475 }
476 }
477
478 pub(crate) fn get_status(&self, intf: u32) -> ServiceStatus {
479 self.status
480 .get(&intf)
481 .cloned()
482 .unwrap_or(ServiceStatus::Unknown)
483 }
484
485 pub fn as_resolved_service(self) -> ResolvedService {
487 let addresses: HashSet<ScopedIp> = self.addresses.into_iter().map(|a| a.into()).collect();
488 ResolvedService {
489 ty_domain: self.ty_domain,
490 sub_ty_domain: self.sub_domain,
491 fullname: self.fullname,
492 host: self.server,
493 port: self.port,
494 addresses,
495 txt_properties: self.txt_properties,
496 }
497 }
498
499 fn is_address_supported(&self, intf: &Interface) -> bool {
500 let addr = intf.ip();
501 let interface_supported = self.supported_intfs.iter().any(|i| match i {
502 IfKind::Name(name) => *name == intf.name,
503 IfKind::IPv4 => addr.is_ipv4(),
504 IfKind::IPv6 => addr.is_ipv6(),
505 IfKind::Addr(a) => *a == addr,
506 IfKind::LoopbackV4 => matches!(addr, IpAddr::V4(ipv4) if ipv4.is_loopback()),
507 IfKind::LoopbackV6 => matches!(addr, IpAddr::V6(ipv6) if ipv6.is_loopback()),
508 IfKind::IndexV4(idx) => intf.index == Some(*idx) && addr.is_ipv4(),
509 IfKind::IndexV6(idx) => intf.index == Some(*idx) && addr.is_ipv6(),
510 IfKind::All => true,
511 });
512
513 let passes_link_local = !self.is_link_local_only
514 || match &addr {
515 IpAddr::V4(ipv4) => ipv4.is_link_local(),
516 IpAddr::V6(ipv6) => is_unicast_link_local(ipv6),
517 };
518 debug!(
519 "matching inserted address {} on intf {}: passes_link_local={}, interface_supported={}",
520 addr, addr, passes_link_local, interface_supported
521 );
522 interface_supported && passes_link_local
523 }
524}
525
526fn normalize_hostname(mut hostname: String) -> String {
528 if hostname.ends_with(".local.local.") {
529 let new_len = hostname.len() - "local.".len();
530 hostname.truncate(new_len);
531 }
532 hostname
533}
534
535pub trait AsIpAddrs {
537 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>>;
538}
539
540impl<T: AsIpAddrs> AsIpAddrs for &T {
541 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
542 (*self).as_ip_addrs()
543 }
544}
545
546impl AsIpAddrs for &str {
551 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
552 let mut addrs = HashSet::new();
553
554 if !self.is_empty() {
555 let iter = self.split(',').map(str::trim).map(IpAddr::from_str);
556 for addr in iter {
557 let addr = addr.map_err(|err| Error::ParseIpAddr(err.to_string()))?;
558 addrs.insert(addr);
559 }
560 }
561
562 Ok(addrs)
563 }
564}
565
566impl AsIpAddrs for String {
567 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
568 self.as_str().as_ip_addrs()
569 }
570}
571
572impl<I: AsIpAddrs> AsIpAddrs for &[I] {
574 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
575 let mut addrs = HashSet::new();
576
577 for result in self.iter().map(I::as_ip_addrs) {
578 addrs.extend(result?);
579 }
580
581 Ok(addrs)
582 }
583}
584
585impl AsIpAddrs for () {
588 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
589 Ok(HashSet::new())
590 }
591}
592
593impl AsIpAddrs for std::net::IpAddr {
594 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
595 let mut ips = HashSet::new();
596 ips.insert(*self);
597
598 Ok(ips)
599 }
600}
601
602impl AsIpAddrs for Box<dyn AsIpAddrs> {
603 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
604 self.as_ref().as_ip_addrs()
605 }
606}
607
608#[derive(Debug, Clone, PartialEq, Eq)]
616#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
617#[cfg_attr(feature = "serde", serde(transparent))]
618pub struct TxtProperties {
619 properties: Vec<TxtProperty>,
621}
622
623impl Default for TxtProperties {
624 fn default() -> Self {
625 TxtProperties::new()
626 }
627}
628
629impl TxtProperties {
630 pub fn new() -> Self {
631 TxtProperties {
632 properties: Vec::new(),
633 }
634 }
635
636 pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
638 self.properties.iter()
639 }
640
641 pub fn len(&self) -> usize {
643 self.properties.len()
644 }
645
646 pub fn is_empty(&self) -> bool {
648 self.properties.is_empty()
649 }
650
651 pub fn get(&self, key: &str) -> Option<&TxtProperty> {
654 let key = key.to_lowercase();
655 self.properties
656 .iter()
657 .find(|&prop| prop.key.to_lowercase() == key)
658 }
659
660 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
666 self.get(key).map(|x| x.val())
667 }
668
669 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
675 self.get(key).map(|x| x.val_str())
676 }
677
678 pub fn into_property_map_str(self) -> HashMap<String, String> {
683 self.properties
684 .into_iter()
685 .filter_map(|property| {
686 let val_string = property.val.map_or(Some(String::new()), |val| {
687 String::from_utf8(val)
688 .map_err(|e| {
689 debug!("Property value contains invalid UTF-8: {e}");
690 })
691 .ok()
692 })?;
693 Some((property.key, val_string))
694 })
695 .collect()
696 }
697}
698
699impl fmt::Display for TxtProperties {
700 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
701 let delimiter = ", ";
702 let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
703 write!(f, "({})", props.join(delimiter))
704 }
705}
706
707impl From<&[u8]> for TxtProperties {
708 fn from(txt: &[u8]) -> Self {
709 let properties = decode_txt_unique(txt);
710 TxtProperties { properties }
711 }
712}
713
714#[derive(Clone, PartialEq, Eq)]
716#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
717pub struct TxtProperty {
718 key: String,
720
721 #[cfg_attr(feature = "serde", serde(rename = "value"))]
725 val: Option<Vec<u8>>,
726}
727
728impl TxtProperty {
729 pub fn key(&self) -> &str {
731 &self.key
732 }
733
734 pub fn val(&self) -> Option<&[u8]> {
738 self.val.as_deref()
739 }
740
741 pub fn val_str(&self) -> &str {
743 self.val
744 .as_ref()
745 .map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
746 }
747}
748
749impl<K, V> From<&(K, V)> for TxtProperty
751where
752 K: ToString,
753 V: ToString,
754{
755 fn from(prop: &(K, V)) -> Self {
756 Self {
757 key: prop.0.to_string(),
758 val: Some(prop.1.to_string().into_bytes()),
759 }
760 }
761}
762
763impl<K, V> From<(K, V)> for TxtProperty
764where
765 K: ToString,
766 V: AsRef<[u8]>,
767{
768 fn from(prop: (K, V)) -> Self {
769 Self {
770 key: prop.0.to_string(),
771 val: Some(prop.1.as_ref().into()),
772 }
773 }
774}
775
776impl From<&str> for TxtProperty {
778 fn from(key: &str) -> Self {
779 Self {
780 key: key.to_string(),
781 val: None,
782 }
783 }
784}
785
786impl fmt::Display for TxtProperty {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 write!(f, "{}={}", self.key, self.val_str())
789 }
790}
791
792impl fmt::Debug for TxtProperty {
796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
797 let val_string = self.val.as_ref().map_or_else(
798 || "None".to_string(),
799 |v| {
800 std::str::from_utf8(&v[..]).map_or_else(
801 |_| format!("Some({})", u8_slice_to_hex(&v[..])),
802 |s| format!("Some(\"{s}\")"),
803 )
804 },
805 );
806
807 write!(
808 f,
809 "TxtProperty {{key: \"{}\", val: {}}}",
810 &self.key, &val_string,
811 )
812 }
813}
814
815const HEX_TABLE: [char; 16] = [
816 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
817];
818
819fn u8_slice_to_hex(slice: &[u8]) -> String {
823 let mut hex = String::with_capacity(slice.len() * 2 + 2);
824 hex.push_str("0x");
825 for b in slice {
826 hex.push(HEX_TABLE[(b >> 4) as usize]);
827 hex.push(HEX_TABLE[(b & 0x0F) as usize]);
828 }
829 hex
830}
831
832pub trait IntoTxtProperties {
834 fn into_txt_properties(self) -> TxtProperties;
835}
836
837impl IntoTxtProperties for HashMap<String, String> {
838 fn into_txt_properties(mut self) -> TxtProperties {
839 let properties = self
840 .drain()
841 .map(|(key, val)| TxtProperty {
842 key,
843 val: Some(val.into_bytes()),
844 })
845 .collect();
846 TxtProperties { properties }
847 }
848}
849
850impl IntoTxtProperties for Option<HashMap<String, String>> {
852 fn into_txt_properties(self) -> TxtProperties {
853 self.map_or_else(
854 || TxtProperties {
855 properties: Vec::new(),
856 },
857 |h| h.into_txt_properties(),
858 )
859 }
860}
861
862impl<'a, T: 'a> IntoTxtProperties for &'a [T]
864where
865 TxtProperty: From<&'a T>,
866{
867 fn into_txt_properties(self) -> TxtProperties {
868 let mut properties = Vec::new();
869 let mut keys = HashSet::new();
870 for t in self.iter() {
871 let prop = TxtProperty::from(t);
872 let key = prop.key.to_lowercase();
873 if keys.insert(key) {
874 properties.push(prop);
882 }
883 }
884 TxtProperties { properties }
885 }
886}
887
888impl IntoTxtProperties for Vec<TxtProperty> {
889 fn into_txt_properties(self) -> TxtProperties {
890 TxtProperties { properties: self }
891 }
892}
893
894fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
896 let mut bytes = Vec::new();
897 for prop in properties {
898 let mut s = prop.key.clone().into_bytes();
899 if let Some(v) = &prop.val {
900 s.extend(b"=");
901 s.extend(v);
902 }
903
904 let sz: u8 = s.len().try_into().unwrap_or_else(|_| {
906 debug!("Property {} is too long, truncating to 255 bytes", prop.key);
907 s.resize(u8::MAX as usize, 0);
908 u8::MAX
909 });
910
911 bytes.push(sz);
914 bytes.extend(s);
915 }
916 if bytes.is_empty() {
917 bytes.push(0);
918 }
919 bytes
920}
921
922pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
924 let mut properties = Vec::new();
925 let mut offset = 0;
926 while offset < txt.len() {
927 let length = txt[offset] as usize;
928 if length == 0 {
929 break; }
931 offset += 1; let offset_end = offset + length;
934 if offset_end > txt.len() {
935 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());
936 break; }
938 let kv_bytes = &txt[offset..offset_end];
939
940 let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
942 || (kv_bytes.to_vec(), None),
943 |idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
944 );
945
946 match String::from_utf8(k) {
948 Ok(k_string) => {
949 properties.push(TxtProperty {
950 key: k_string,
951 val: v,
952 });
953 }
954 Err(e) => debug!("failed to convert to String from key: {}", e),
955 }
956
957 offset += length;
958 }
959
960 properties
961}
962
963fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
964 let mut properties = decode_txt(txt);
965
966 let mut keys = HashSet::new();
969 properties.retain(|p| {
970 let key = p.key().to_lowercase();
971 keys.insert(key) });
973 properties
974}
975
976pub(crate) fn valid_ip_on_intf(addr: &IpAddr, if_addr: &IfAddr) -> bool {
978 match (addr, if_addr) {
979 (IpAddr::V4(addr), IfAddr::V4(if_v4)) => {
980 let netmask = u32::from(if_v4.netmask);
981 let intf_net = u32::from(if_v4.ip) & netmask;
982 let addr_net = u32::from(*addr) & netmask;
983 addr_net == intf_net
984 }
985 (IpAddr::V6(addr), IfAddr::V6(if_v6)) => {
986 let netmask = u128::from(if_v6.netmask);
987 let intf_net = u128::from(if_v6.ip) & netmask;
988 let addr_net = u128::from(*addr) & netmask;
989 addr_net == intf_net
990 }
991 _ => false,
992 }
993}
994
995#[derive(Debug)]
997pub(crate) struct Probe {
998 pub(crate) records: Vec<DnsRecordBox>,
1000
1001 pub(crate) waiting_services: HashSet<String>,
1004
1005 pub(crate) start_time: u64,
1007
1008 pub(crate) next_send: u64,
1010}
1011
1012impl Probe {
1013 pub(crate) fn new(start_time: u64) -> Self {
1014 let next_send = start_time;
1021
1022 Self {
1023 records: Vec::new(),
1024 waiting_services: HashSet::new(),
1025 start_time,
1026 next_send,
1027 }
1028 }
1029
1030 pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
1032 let insert_position = self
1042 .records
1043 .binary_search_by(
1044 |existing| match existing.get_class().cmp(&record.get_class()) {
1045 std::cmp::Ordering::Equal => existing.get_type().cmp(&record.get_type()),
1046 other => other,
1047 },
1048 )
1049 .unwrap_or_else(|pos| pos);
1050
1051 self.records.insert(insert_position, record);
1052 }
1053
1054 pub(crate) fn tiebreaking(&mut self, msg: &DnsIncoming, probe_name: &str) {
1056 let now = crate::current_time_millis();
1057
1058 if self.start_time >= now {
1062 return;
1063 }
1064
1065 let incoming: Vec<_> = msg
1066 .authorities()
1067 .iter()
1068 .filter(|r| r.get_name() == probe_name)
1069 .collect();
1070 let min_len = self.records.len().min(incoming.len());
1080
1081 let mut cmp_result = cmp::Ordering::Equal;
1083 for (i, incoming_record) in incoming.iter().enumerate().take(min_len) {
1084 match self.records[i].compare(incoming_record.as_ref()) {
1085 cmp::Ordering::Equal => continue,
1086 other => {
1087 cmp_result = other;
1088 break; }
1090 }
1091 }
1092
1093 if cmp_result == cmp::Ordering::Equal {
1094 cmp_result = self.records.len().cmp(&incoming.len());
1096 }
1097
1098 match cmp_result {
1099 cmp::Ordering::Less => {
1100 debug!("tiebreaking '{probe_name}': LOST, will wait for one second",);
1101 self.start_time = now + 1000; self.next_send = now + 1000;
1103 }
1104 ordering => {
1105 debug!("tiebreaking '{probe_name}': {:?}", ordering);
1106 }
1107 }
1108 }
1109
1110 pub(crate) fn update_next_send(&mut self, now: u64) {
1111 self.next_send = now + 250;
1112 }
1113
1114 pub(crate) fn expired(&self, now: u64) -> bool {
1116 now >= self.start_time + 750
1119 }
1120}
1121
1122pub(crate) struct DnsRegistry {
1124 pub(crate) probing: HashMap<String, Probe>,
1135
1136 pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
1139
1140 pub(crate) new_timers: Vec<u64>,
1142
1143 pub(crate) name_changes: HashMap<String, String>,
1145}
1146
1147impl DnsRegistry {
1148 pub(crate) fn new() -> Self {
1149 Self {
1150 probing: HashMap::new(),
1151 active: HashMap::new(),
1152 new_timers: Vec::new(),
1153 name_changes: HashMap::new(),
1154 }
1155 }
1156
1157 pub(crate) fn resolve_name<'a>(&'a self, name: &'a str) -> &'a str {
1159 match self.name_changes.get(name) {
1160 Some(new_name) => new_name,
1161 None => name,
1162 }
1163 }
1164
1165 pub(crate) fn is_probing_done<T>(
1166 &mut self,
1167 answer: &T,
1168 service_name: &str,
1169 start_time: u64,
1170 ) -> bool
1171 where
1172 T: DnsRecordExt + Send + 'static,
1173 {
1174 if let Some(active_records) = self.active.get(answer.get_name()) {
1175 for record in active_records.iter() {
1176 if answer.matches(record.as_ref()) {
1177 debug!(
1178 "found active record {} {}",
1179 answer.get_type(),
1180 answer.get_name(),
1181 );
1182 return true;
1183 }
1184 }
1185 }
1186
1187 let probe = self
1188 .probing
1189 .entry(answer.get_name().to_string())
1190 .or_insert_with(|| {
1191 debug!("new probe of {}", answer.get_name());
1192 Probe::new(start_time)
1193 });
1194
1195 self.new_timers.push(probe.next_send);
1196
1197 for record in probe.records.iter() {
1198 if answer.matches(record.as_ref()) {
1199 debug!(
1200 "found existing record {} in probe of '{}'",
1201 answer.get_type(),
1202 answer.get_name(),
1203 );
1204 probe.waiting_services.insert(service_name.to_string());
1205 return false; }
1207 }
1208
1209 debug!(
1210 "insert record {} into probe of {}",
1211 answer.get_type(),
1212 answer.get_name(),
1213 );
1214 probe.insert_record(answer.clone_box());
1215 probe.waiting_services.insert(service_name.to_string());
1216
1217 false
1218 }
1219
1220 pub(crate) fn update_hostname(
1224 &mut self,
1225 original: &str,
1226 new_name: &str,
1227 probe_time: u64,
1228 ) -> bool {
1229 let mut found_records = Vec::new();
1230 let mut new_timer_added = false;
1231
1232 for (_name, probe) in self.probing.iter_mut() {
1233 probe.records.retain(|record| {
1234 if record.get_type() == RRType::SRV {
1235 if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1236 if srv.host() == original {
1237 let mut new_record = srv.clone();
1238 new_record.set_host(new_name.to_string());
1239 found_records.push(new_record);
1240 return false;
1241 }
1242 }
1243 }
1244 true
1245 });
1246 }
1247
1248 for (_name, records) in self.active.iter_mut() {
1249 records.retain(|record| {
1250 if record.get_type() == RRType::SRV {
1251 if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1252 if srv.host() == original {
1253 let mut new_record = srv.clone();
1254 new_record.set_host(new_name.to_string());
1255 found_records.push(new_record);
1256 return false;
1257 }
1258 }
1259 }
1260 true
1261 });
1262 }
1263
1264 for record in found_records {
1265 let probe = match self.probing.get_mut(record.get_name()) {
1266 Some(p) => {
1267 p.start_time = probe_time; p
1269 }
1270 None => {
1271 let new_probe = self
1272 .probing
1273 .entry(record.get_name().to_string())
1274 .or_insert_with(|| Probe::new(probe_time));
1275 new_timer_added = true;
1276 new_probe
1277 }
1278 };
1279
1280 debug!(
1281 "insert record {} with new hostname {new_name} into probe for: {}",
1282 record.get_type(),
1283 record.get_name()
1284 );
1285 probe.insert_record(record.boxed());
1286 }
1287
1288 new_timer_added
1289 }
1290}
1291
1292pub(crate) fn split_sub_domain(domain: &str) -> (&str, Option<&str>) {
1294 if let Some((_, ty_domain)) = domain.rsplit_once("._sub.") {
1295 (ty_domain, Some(domain))
1296 } else {
1297 (domain, None)
1298 }
1299}
1300
1301pub(crate) fn is_unicast_link_local(addr: &Ipv6Addr) -> bool {
1307 (addr.segments()[0] & 0xffc0) == 0xfe80
1308}
1309
1310#[derive(Clone, Debug)]
1313#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1314#[non_exhaustive]
1315pub struct ResolvedService {
1316 pub ty_domain: String,
1318
1319 pub sub_ty_domain: Option<String>,
1325
1326 pub fullname: String,
1328
1329 pub host: String,
1331
1332 pub port: u16,
1334
1335 pub addresses: HashSet<ScopedIp>,
1337
1338 pub txt_properties: TxtProperties,
1340}
1341
1342impl ResolvedService {
1343 pub fn is_valid(&self) -> bool {
1345 let some_missing = self.ty_domain.is_empty()
1346 || self.fullname.is_empty()
1347 || self.host.is_empty()
1348 || self.addresses.is_empty();
1349 !some_missing
1350 }
1351
1352 #[inline]
1353 pub const fn get_subtype(&self) -> &Option<String> {
1354 &self.sub_ty_domain
1355 }
1356
1357 #[inline]
1358 pub fn get_fullname(&self) -> &str {
1359 &self.fullname
1360 }
1361
1362 #[inline]
1363 pub fn get_hostname(&self) -> &str {
1364 &self.host
1365 }
1366
1367 #[inline]
1368 pub fn get_port(&self) -> u16 {
1369 self.port
1370 }
1371
1372 #[inline]
1373 pub fn get_addresses(&self) -> &HashSet<ScopedIp> {
1374 &self.addresses
1375 }
1376
1377 pub fn get_addresses_v4(&self) -> HashSet<Ipv4Addr> {
1378 self.addresses
1379 .iter()
1380 .filter_map(|ip| match ip {
1381 ScopedIp::V4(ipv4) => Some(*ipv4.addr()),
1382 _ => None,
1383 })
1384 .collect()
1385 }
1386
1387 #[inline]
1388 pub fn get_properties(&self) -> &TxtProperties {
1389 &self.txt_properties
1390 }
1391
1392 #[inline]
1393 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
1394 self.txt_properties.get(key)
1395 }
1396
1397 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
1398 self.txt_properties.get_property_val(key)
1399 }
1400
1401 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
1402 self.txt_properties.get_property_val_str(key)
1403 }
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use super::{decode_txt, encode_txt, u8_slice_to_hex, ServiceInfo, TxtProperty};
1409 use crate::IfKind;
1410 use if_addrs::{IfAddr, IfOperStatus, Ifv4Addr, Ifv6Addr, Interface};
1411 use std::net::{Ipv4Addr, Ipv6Addr};
1412
1413 #[test]
1414 fn test_txt_encode_decode() {
1415 let properties = [
1416 TxtProperty::from(&("key1", "value1")),
1417 TxtProperty::from(&("key2", "value2")),
1418 ];
1419
1420 let property_count = properties.len();
1422 let encoded = encode_txt(properties.iter());
1423 assert_eq!(
1424 encoded.len(),
1425 "key1=value1".len() + "key2=value2".len() + property_count
1426 );
1427 assert_eq!(encoded[0] as usize, "key1=value1".len());
1428
1429 let decoded = decode_txt(&encoded);
1431 assert!(properties[..] == decoded[..]);
1432
1433 let properties = vec![TxtProperty::from(&("key3", ""))];
1435 let property_count = properties.len();
1436 let encoded = encode_txt(properties.iter());
1437 assert_eq!(encoded.len(), "key3=".len() + property_count);
1438
1439 let decoded = decode_txt(&encoded);
1440 assert_eq!(properties, decoded);
1441
1442 let binary_val: Vec<u8> = vec![123, 234, 0];
1444 let binary_len = binary_val.len();
1445 let properties = vec![TxtProperty::from(("key4", binary_val))];
1446 let property_count = properties.len();
1447 let encoded = encode_txt(properties.iter());
1448 assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
1449
1450 let decoded = decode_txt(&encoded);
1451 assert_eq!(properties, decoded);
1452
1453 let properties = vec![TxtProperty::from(("key5", "val=5"))];
1455 let property_count = properties.len();
1456 let encoded = encode_txt(properties.iter());
1457 assert_eq!(
1458 encoded.len(),
1459 "key5=".len() + "val=5".len() + property_count
1460 );
1461
1462 let decoded = decode_txt(&encoded);
1463 assert_eq!(properties, decoded);
1464
1465 let properties = vec![TxtProperty::from("key6")];
1467 let property_count = properties.len();
1468 let encoded = encode_txt(properties.iter());
1469 assert_eq!(encoded.len(), "key6".len() + property_count);
1470 let decoded = decode_txt(&encoded);
1471 assert_eq!(properties, decoded);
1472
1473 let properties = [TxtProperty::from(
1475 String::from_utf8(vec![0x30; 1024]).unwrap().as_str(), )];
1477 let property_count = properties.len();
1478 let encoded = encode_txt(properties.iter());
1479 assert_eq!(encoded.len(), 255 + property_count);
1480 let decoded = decode_txt(&encoded);
1481 assert_eq!(
1482 vec![TxtProperty::from(
1483 String::from_utf8(vec![0x30; 255]).unwrap().as_str()
1484 )],
1485 decoded
1486 );
1487 }
1488
1489 #[test]
1490 fn test_set_properties_from_txt() {
1491 let properties = [
1493 TxtProperty::from(&("one", "1")),
1494 TxtProperty::from(&("ONE", "2")),
1495 TxtProperty::from(&("One", "3")),
1496 ];
1497 let encoded = encode_txt(properties.iter());
1498
1499 let decoded = decode_txt(&encoded);
1501 assert_eq!(decoded.len(), 3);
1502
1503 let mut service_info =
1505 ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
1506 service_info._set_properties_from_txt(&encoded);
1507 assert_eq!(service_info.get_properties().len(), 1);
1508
1509 let prop = service_info.get_properties().iter().next().unwrap();
1511 assert_eq!(prop.key, "one");
1512 assert_eq!(prop.val_str(), "1");
1513 }
1514
1515 #[test]
1516 fn test_u8_slice_to_hex() {
1517 let bytes = [0x01u8, 0x02u8, 0x03u8];
1518 let hex = u8_slice_to_hex(&bytes);
1519 assert_eq!(hex.as_str(), "0x010203");
1520
1521 let slice = "abcdefghijklmnopqrstuvwxyz";
1522 let hex = u8_slice_to_hex(slice.as_bytes());
1523 assert_eq!(hex.len(), slice.len() * 2 + 2);
1524 assert_eq!(
1525 hex.as_str(),
1526 "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
1527 );
1528 }
1529
1530 #[test]
1531 fn test_txt_property_debug() {
1532 let prop_1 = TxtProperty {
1534 key: "key1".to_string(),
1535 val: Some("val1".to_string().into()),
1536 };
1537 let prop_1_debug = format!("{:?}", &prop_1);
1538 assert_eq!(
1539 prop_1_debug,
1540 "TxtProperty {key: \"key1\", val: Some(\"val1\")}"
1541 );
1542
1543 let prop_2 = TxtProperty {
1545 key: "key2".to_string(),
1546 val: Some(vec![150u8, 151u8, 152u8]),
1547 };
1548 let prop_2_debug = format!("{:?}", &prop_2);
1549 assert_eq!(
1550 prop_2_debug,
1551 "TxtProperty {key: \"key2\", val: Some(0x969798)}"
1552 );
1553 }
1554
1555 #[test]
1556 fn test_txt_decode_property_size_out_of_bounds() {
1557 let encoded: Vec<u8> = vec![
1559 0x0b, b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1561 b'1', 0x10, b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1564 b'2', ];
1566 let decoded = decode_txt(&encoded);
1568 assert_eq!(decoded.len(), 1);
1571 assert_eq!(decoded[0].key, "key1");
1573 }
1574
1575 #[test]
1576 fn test_is_address_supported() {
1577 let mut service_info =
1578 ServiceInfo::new("_test._tcp", "prop_test", "testhost", "", 1234, None).unwrap();
1579
1580 let intf_v6 = Interface {
1581 name: "foo".to_string(),
1582 index: Some(1),
1583 addr: IfAddr::V6(Ifv6Addr {
1584 ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1585 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1586 broadcast: None,
1587 prefixlen: 16,
1588 }),
1589 oper_status: IfOperStatus::Up,
1590 is_p2p: false,
1591 #[cfg(windows)]
1592 adapter_name: String::new(),
1593 };
1594
1595 let intf_v4 = Interface {
1596 name: "bar".to_string(),
1597 index: Some(1),
1598 addr: IfAddr::V4(Ifv4Addr {
1599 ip: Ipv4Addr::new(192, 1, 2, 3),
1600 netmask: Ipv4Addr::new(255, 255, 0, 0),
1601 broadcast: None,
1602 prefixlen: 16,
1603 }),
1604 oper_status: IfOperStatus::Up,
1605 is_p2p: false,
1606 #[cfg(windows)]
1607 adapter_name: String::new(),
1608 };
1609
1610 let intf_baz = Interface {
1611 name: "baz".to_string(),
1612 index: Some(1),
1613 addr: IfAddr::V6(Ifv6Addr {
1614 ip: Ipv6Addr::new(0x2003, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1615 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1616 broadcast: None,
1617 prefixlen: 16,
1618 }),
1619 oper_status: IfOperStatus::Up,
1620 is_p2p: false,
1621 #[cfg(windows)]
1622 adapter_name: String::new(),
1623 };
1624
1625 let intf_loopback_v4 = Interface {
1626 name: "foo".to_string(),
1627 index: Some(1),
1628 addr: IfAddr::V4(Ifv4Addr {
1629 ip: Ipv4Addr::new(127, 0, 0, 1),
1630 netmask: Ipv4Addr::new(255, 255, 255, 255),
1631 broadcast: None,
1632 prefixlen: 16,
1633 }),
1634 oper_status: IfOperStatus::Up,
1635 is_p2p: false,
1636 #[cfg(windows)]
1637 adapter_name: String::new(),
1638 };
1639
1640 let intf_loopback_v6 = Interface {
1641 name: "foo".to_string(),
1642 index: Some(1),
1643 addr: IfAddr::V6(Ifv6Addr {
1644 ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1),
1645 netmask: Ipv6Addr::new(
1646 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
1647 ),
1648 broadcast: None,
1649 prefixlen: 16,
1650 }),
1651 oper_status: IfOperStatus::Up,
1652 is_p2p: false,
1653 #[cfg(windows)]
1654 adapter_name: String::new(),
1655 };
1656
1657 let intf_link_local_v4 = Interface {
1658 name: "foo".to_string(),
1659 index: Some(1),
1660 addr: IfAddr::V4(Ifv4Addr {
1661 ip: Ipv4Addr::new(169, 254, 0, 1),
1662 netmask: Ipv4Addr::new(255, 255, 0, 0),
1663 broadcast: None,
1664 prefixlen: 16,
1665 }),
1666 oper_status: IfOperStatus::Up,
1667 is_p2p: false,
1668 #[cfg(windows)]
1669 adapter_name: String::new(),
1670 };
1671
1672 let intf_link_local_v6 = Interface {
1673 name: "foo".to_string(),
1674 index: Some(1),
1675 addr: IfAddr::V6(Ifv6Addr {
1676 ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0x1234, 0, 0, 1),
1677 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1678 broadcast: None,
1679 prefixlen: 16,
1680 }),
1681 oper_status: IfOperStatus::Up,
1682 is_p2p: false,
1683 #[cfg(windows)]
1684 adapter_name: String::new(),
1685 };
1686
1687 assert!(service_info.is_address_supported(&intf_v6));
1689
1690 service_info.set_interfaces(vec![
1692 IfKind::Name("foo".to_string()),
1693 IfKind::Name("bar".to_string()),
1694 ]);
1695 assert!(!service_info.is_address_supported(&intf_baz));
1696
1697 service_info.set_link_local_only(true);
1699 assert!(!service_info.is_address_supported(&intf_v4));
1700 assert!(!service_info.is_address_supported(&intf_v6));
1701 assert!(service_info.is_address_supported(&intf_link_local_v4));
1702 assert!(service_info.is_address_supported(&intf_link_local_v6));
1703 service_info.set_link_local_only(false);
1704
1705 service_info.set_interfaces(vec![IfKind::All]);
1707 assert!(service_info.is_address_supported(&intf_v6));
1708 assert!(service_info.is_address_supported(&intf_v4));
1709
1710 service_info.set_interfaces(vec![IfKind::IPv6]);
1712 assert!(service_info.is_address_supported(&intf_v6));
1713 assert!(!service_info.is_address_supported(&intf_v4));
1714
1715 service_info.set_interfaces(vec![IfKind::IPv4]);
1717 assert!(service_info.is_address_supported(&intf_v4));
1718 assert!(!service_info.is_address_supported(&intf_v6));
1719
1720 service_info.set_interfaces(vec![IfKind::Addr(intf_v6.ip())]);
1722 assert!(service_info.is_address_supported(&intf_v6));
1723 assert!(!service_info.is_address_supported(&intf_v4));
1724
1725 service_info.set_interfaces(vec![IfKind::LoopbackV4]);
1727 assert!(service_info.is_address_supported(&intf_loopback_v4));
1728 assert!(!service_info.is_address_supported(&intf_loopback_v6));
1729
1730 service_info.set_interfaces(vec![IfKind::LoopbackV6]);
1732 assert!(!service_info.is_address_supported(&intf_loopback_v4));
1733 assert!(service_info.is_address_supported(&intf_loopback_v6));
1734 }
1735
1736 #[test]
1737 fn test_scoped_ip_set_detects_interface_id_change() {
1738 use crate::{InterfaceId, ScopedIp, ScopedIpV4};
1739 use std::collections::HashSet;
1740
1741 let intf1 = InterfaceId {
1742 name: "en0".to_string(),
1743 index: 1,
1744 };
1745 let intf2 = InterfaceId {
1746 name: "en1".to_string(),
1747 index: 2,
1748 };
1749 let addr = Ipv4Addr::new(192, 168, 1, 100);
1750
1751 let scoped_v4_one_intf = ScopedIpV4::new(addr, intf1);
1752 let mut scoped_v4_two_intfs = scoped_v4_one_intf.clone();
1753 scoped_v4_two_intfs.add_interface_id(intf2);
1754
1755 assert_ne!(scoped_v4_one_intf, scoped_v4_two_intfs);
1756
1757 let set_old: HashSet<ScopedIp> = HashSet::from([ScopedIp::V4(scoped_v4_one_intf)]);
1758 let set_new: HashSet<ScopedIp> = HashSet::from([ScopedIp::V4(scoped_v4_two_intfs)]);
1759
1760 assert_ne!(set_old, set_new);
1761 }
1762
1763 #[cfg(test)]
1764 #[cfg(feature = "serde")]
1765 mod serde {
1766 use super::{Ipv4Addr, Ipv6Addr};
1767 use crate::{ResolvedService, ScopedIp, TxtProperties};
1768
1769 use std::collections::HashSet;
1770 use std::net::IpAddr;
1771
1772 #[test]
1773 fn test_deserialize_serialize() -> Result<(), Box<dyn std::error::Error>> {
1774 let addresses = HashSet::from([
1775 ScopedIp::from(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
1776 ScopedIp::from(IpAddr::V6(Ipv6Addr::new(
1777 0xfe80, 0x2001, 0x0db8, 0x85a3, 0x0000, 0x8a2e, 0x0370, 0x7334,
1778 ))),
1779 ]);
1780
1781 let service = ResolvedService {
1782 ty_domain: "_http._tcp.local.".to_owned(),
1783 sub_ty_domain: None,
1784 fullname: "example._http._tcp.local.".to_owned(),
1785 host: "example.local.".to_owned(),
1786 port: 1234,
1787 addresses,
1788 txt_properties: TxtProperties::new(),
1789 };
1790
1791 let json = serde_json::to_value(&service)?;
1792
1793 let parsed: ResolvedService = serde_json::from_value(json)?;
1794
1795 assert!(compare(&service, &parsed));
1796
1797 Ok(())
1798 }
1799
1800 fn compare(service: &ResolvedService, other: &ResolvedService) -> bool {
1801 service.ty_domain == other.ty_domain
1802 && service.sub_ty_domain == other.sub_ty_domain
1803 && service.fullname == other.fullname
1804 && service.host == other.host
1805 && service.port == other.port
1806 && service.addresses == other.addresses
1807 && service.txt_properties == other.txt_properties
1808 }
1809 }
1810}