1#[cfg(feature = "logging")]
4use crate::log::{debug, trace};
5use crate::{
6 dns_parser::{DnsIncoming, DnsOutgoing, 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 fmt,
15 net::{IpAddr, Ipv4Addr},
16 str::FromStr,
17 time::{Duration, Instant},
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 pub(crate) max_packet_size_v4: usize,
41
42 pub(crate) max_packet_size_v6: usize,
44}
45
46impl MyIntf {
47 pub(crate) fn next_ifaddr_v4(&self) -> Option<&IfAddr> {
48 self.addrs.iter().find(|a| a.ip().is_ipv4())
49 }
50
51 pub(crate) fn next_ifaddr_v6(&self) -> Option<&IfAddr> {
52 self.addrs.iter().find(|a| a.ip().is_ipv6())
53 }
54
55 pub(crate) fn max_packet_size(&self, is_ipv4: bool) -> usize {
57 if is_ipv4 {
58 self.max_packet_size_v4
59 } else {
60 self.max_packet_size_v6
61 }
62 }
63}
64
65impl From<&MyIntf> for InterfaceId {
66 fn from(my_intf: &MyIntf) -> Self {
67 InterfaceId {
68 name: my_intf.name.clone(),
69 index: my_intf.index,
70 }
71 }
72}
73
74fn escape_instance_name(name: &str) -> String {
83 let mut result = String::with_capacity(name.len() + 10); for ch in name.chars() {
86 match ch {
87 '.' => {
88 result.push('\\');
89 result.push('.');
90 }
91 '\\' => {
92 result.push('\\');
93 result.push('\\');
94 }
95 _ => result.push(ch),
96 }
97 }
98
99 result
100}
101
102#[derive(Debug, Clone)]
107pub struct ServiceInfo {
108 ty_domain: String,
112
113 sub_domain: Option<String>, fullname: String, server: String, addresses: HashSet<IpAddr>,
120 port: u16,
121 host_ttl: u32, other_ttl: u32, priority: u16,
124 weight: u16,
125 txt_properties: TxtProperties,
126 addr_auto: bool, status: HashMap<u32, ServiceStatus>, requires_probe: bool,
132
133 supported_intfs: Vec<IfKind>,
135
136 is_link_local_only: bool,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub(crate) enum ServiceStatus {
142 Probing,
143 Announced,
144 Unknown,
145}
146
147impl ServiceInfo {
148 pub fn new<Ip: AsIpAddrs, P: IntoTxtProperties>(
184 ty_domain: &str,
185 my_name: &str,
186 host_name: &str,
187 ip: Ip,
188 port: u16,
189 properties: P,
190 ) -> Result<Self> {
191 let (ty_domain, sub_domain) = split_sub_domain(ty_domain);
192
193 let escaped_name = escape_instance_name(my_name);
194 let fullname = format!("{escaped_name}.{ty_domain}");
195 let ty_domain = ty_domain.to_string();
196 let sub_domain = sub_domain.map(str::to_string);
197 let server = normalize_hostname(host_name.to_string());
198 let addresses = ip.as_ip_addrs()?;
199 let txt_properties = properties.into_txt_properties();
200
201 for prop in txt_properties.iter() {
205 let key = prop.key();
206 if !key.is_ascii() {
207 return Err(Error::Msg(format!(
208 "TXT property key {} is not ASCII",
209 prop.key()
210 )));
211 }
212 if key.contains('=') {
213 return Err(Error::Msg(format!(
214 "TXT property key {} contains '='",
215 prop.key()
216 )));
217 }
218
219 let prop_len = key.len() + prop.val().map_or(0, |v| v.len() + 1);
222 if prop_len > u8::MAX as usize {
223 return Err(Error::Msg(format!(
224 "TXT property '{}' has length {} bytes, exceeding the 255-byte limit",
225 key, prop_len
226 )));
227 }
228 }
229
230 let this = Self {
231 ty_domain,
232 sub_domain,
233 fullname,
234 server,
235 addresses,
236 port,
237 host_ttl: DNS_HOST_TTL,
238 other_ttl: DNS_OTHER_TTL,
239 priority: 0,
240 weight: 0,
241 txt_properties,
242 addr_auto: false,
243 status: HashMap::new(),
244 requires_probe: true,
245 is_link_local_only: false,
246 supported_intfs: vec![IfKind::All],
247 };
248
249 Ok(this)
250 }
251
252 pub const fn enable_addr_auto(mut self) -> Self {
256 self.addr_auto = true;
257 self
258 }
259
260 pub const fn is_addr_auto(&self) -> bool {
263 self.addr_auto
264 }
265
266 pub fn set_requires_probe(&mut self, enable: bool) {
271 self.requires_probe = enable;
272 }
273
274 pub fn set_link_local_only(&mut self, is_link_local_only: bool) {
278 self.is_link_local_only = is_link_local_only;
279 }
280
281 pub fn set_interfaces(&mut self, intfs: Vec<IfKind>) {
286 self.supported_intfs = intfs;
287 }
288
289 pub const fn requires_probe(&self) -> bool {
293 self.requires_probe
294 }
295
296 #[inline]
300 pub fn get_type(&self) -> &str {
301 &self.ty_domain
302 }
303
304 #[inline]
309 pub const fn get_subtype(&self) -> &Option<String> {
310 &self.sub_domain
311 }
312
313 pub(crate) fn matches_type_or_subtype(&self, name: &str) -> bool {
315 name == self.get_type() || self.get_subtype().as_ref().is_some_and(|v| v == name)
316 }
317
318 #[inline]
322 pub fn get_fullname(&self) -> &str {
323 &self.fullname
324 }
325
326 #[inline]
328 pub const fn get_properties(&self) -> &TxtProperties {
329 &self.txt_properties
330 }
331
332 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
337 self.txt_properties.get(key)
338 }
339
340 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
345 self.txt_properties.get_property_val(key)
346 }
347
348 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
353 self.txt_properties.get_property_val_str(key)
354 }
355
356 #[inline]
358 pub fn get_hostname(&self) -> &str {
359 &self.server
360 }
361
362 #[inline]
364 pub const fn get_port(&self) -> u16 {
365 self.port
366 }
367
368 #[inline]
370 pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
371 &self.addresses
372 }
373
374 pub fn get_addresses_v4(&self) -> HashSet<&Ipv4Addr> {
376 let mut ipv4_addresses = HashSet::new();
377
378 for ip in &self.addresses {
379 if let IpAddr::V4(ipv4) = ip {
380 ipv4_addresses.insert(ipv4);
381 }
382 }
383
384 ipv4_addresses
385 }
386
387 #[inline]
389 pub const fn get_host_ttl(&self) -> u32 {
390 self.host_ttl
391 }
392
393 #[inline]
395 pub const fn get_other_ttl(&self) -> u32 {
396 self.other_ttl
397 }
398
399 #[inline]
401 pub const fn get_priority(&self) -> u16 {
402 self.priority
403 }
404
405 #[inline]
407 pub const fn get_weight(&self) -> u16 {
408 self.weight
409 }
410
411 pub(crate) fn get_addrs_on_my_intf_v4(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
413 self.addresses
414 .iter()
415 .filter(|a| a.is_ipv4() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
416 .copied()
417 .collect()
418 }
419
420 pub(crate) fn get_addrs_on_my_intf_v6(&self, my_intf: &MyIntf) -> Vec<IpAddr> {
421 self.addresses
422 .iter()
423 .filter(|a| a.is_ipv6() && my_intf.addrs.iter().any(|x| valid_ip_on_intf(a, x)))
424 .copied()
425 .collect()
426 }
427
428 pub(crate) fn _is_ready(&self) -> bool {
430 let some_missing = self.ty_domain.is_empty()
431 || self.fullname.is_empty()
432 || self.server.is_empty()
433 || self.addresses.is_empty();
434 !some_missing
435 }
436
437 pub(crate) fn insert_ipaddr(&mut self, intf: &Interface) -> bool {
441 if self.is_address_supported(intf) {
442 self.addresses.insert(intf.addr.ip());
443 true
444 } else {
445 trace!(
446 "skipping unsupported address {} for service {}",
447 intf.addr.ip(),
448 self.fullname
449 );
450 false
451 }
452 }
453
454 pub(crate) fn remove_ipaddr(&mut self, addr: &IpAddr) {
455 self.addresses.remove(addr);
456 }
457
458 pub(crate) fn generate_txt(&self) -> Vec<u8> {
459 encode_txt(self.get_properties().iter())
460 }
461
462 pub(crate) fn _set_port(&mut self, port: u16) {
463 self.port = port;
464 }
465
466 pub(crate) fn _set_hostname(&mut self, hostname: String) {
467 self.server = normalize_hostname(hostname);
468 }
469
470 pub(crate) fn _set_properties_from_txt(&mut self, txt: &[u8]) -> bool {
472 let properties = decode_txt_unique(txt);
473 if self.txt_properties.properties != properties {
474 self.txt_properties = TxtProperties { properties };
475 true
476 } else {
477 false
478 }
479 }
480
481 pub(crate) fn _set_subtype(&mut self, subtype: String) {
482 self.sub_domain = Some(subtype);
483 }
484
485 pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
488 self.host_ttl = ttl;
489 }
490
491 pub(crate) fn _set_other_ttl(&mut self, ttl: u32) {
493 self.other_ttl = ttl;
494 }
495
496 pub(crate) fn set_status(&mut self, if_index: u32, status: ServiceStatus) {
497 match self.status.get_mut(&if_index) {
498 Some(service_status) => {
499 *service_status = status;
500 }
501 None => {
502 self.status.entry(if_index).or_insert(status);
503 }
504 }
505 }
506
507 pub(crate) fn get_status(&self, intf: u32) -> ServiceStatus {
508 self.status
509 .get(&intf)
510 .cloned()
511 .unwrap_or(ServiceStatus::Unknown)
512 }
513
514 pub fn as_resolved_service(self) -> ResolvedService {
516 let addresses: HashSet<ScopedIp> = self.addresses.into_iter().map(|a| a.into()).collect();
517 ResolvedService {
518 ty_domain: self.ty_domain,
519 sub_ty_domain: self.sub_domain,
520 fullname: self.fullname,
521 host: self.server,
522 port: self.port,
523 addresses,
524 txt_properties: self.txt_properties,
525 }
526 }
527
528 pub(crate) fn is_address_supported(&self, intf: &Interface) -> bool {
529 let interface_supported = self.supported_intfs.iter().any(|i| i.matches(intf));
530 let addr = intf.ip();
531 let passes_link_local = !self.is_link_local_only
532 || match &addr {
533 IpAddr::V4(ipv4) => ipv4.is_link_local(),
534 IpAddr::V6(ipv6) => is_unicast_link_local(ipv6),
535 };
536 debug!(
537 "matching inserted address {} on intf {}: passes_link_local={}, interface_supported={}",
538 addr, addr, passes_link_local, interface_supported
539 );
540 interface_supported && passes_link_local
541 }
542}
543
544fn normalize_hostname(mut hostname: String) -> String {
546 if hostname.ends_with(".local.local.") {
547 let new_len = hostname.len() - "local.".len();
548 hostname.truncate(new_len);
549 }
550 hostname
551}
552
553pub trait AsIpAddrs {
555 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>>;
556}
557
558impl<T: AsIpAddrs> AsIpAddrs for &T {
559 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
560 (*self).as_ip_addrs()
561 }
562}
563
564impl AsIpAddrs for &str {
569 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
570 let mut addrs = HashSet::new();
571
572 if !self.is_empty() {
573 let iter = self.split(',').map(str::trim).map(IpAddr::from_str);
574 for addr in iter {
575 let addr = addr.map_err(|err| Error::ParseIpAddr(err.to_string()))?;
576 addrs.insert(addr);
577 }
578 }
579
580 Ok(addrs)
581 }
582}
583
584impl AsIpAddrs for String {
585 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
586 self.as_str().as_ip_addrs()
587 }
588}
589
590impl<I: AsIpAddrs> AsIpAddrs for &[I] {
592 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
593 let mut addrs = HashSet::new();
594
595 for result in self.iter().map(I::as_ip_addrs) {
596 addrs.extend(result?);
597 }
598
599 Ok(addrs)
600 }
601}
602
603impl AsIpAddrs for () {
606 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
607 Ok(HashSet::new())
608 }
609}
610
611impl AsIpAddrs for std::net::IpAddr {
612 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
613 let mut ips = HashSet::new();
614 ips.insert(*self);
615
616 Ok(ips)
617 }
618}
619
620impl AsIpAddrs for Box<dyn AsIpAddrs> {
621 fn as_ip_addrs(&self) -> Result<HashSet<IpAddr>> {
622 self.as_ref().as_ip_addrs()
623 }
624}
625
626#[derive(Debug, Clone, PartialEq, Eq)]
634#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
635#[cfg_attr(feature = "serde", serde(transparent))]
636pub struct TxtProperties {
637 properties: Vec<TxtProperty>,
639}
640
641impl Default for TxtProperties {
642 fn default() -> Self {
643 TxtProperties::new()
644 }
645}
646
647impl TxtProperties {
648 pub fn new() -> Self {
649 TxtProperties {
650 properties: Vec::new(),
651 }
652 }
653
654 pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
656 self.properties.iter()
657 }
658
659 pub fn len(&self) -> usize {
661 self.properties.len()
662 }
663
664 pub fn is_empty(&self) -> bool {
666 self.properties.is_empty()
667 }
668
669 pub fn get(&self, key: &str) -> Option<&TxtProperty> {
672 let key = key.to_lowercase();
673 self.properties
674 .iter()
675 .find(|&prop| prop.key.to_lowercase() == key)
676 }
677
678 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
684 self.get(key).map(|x| x.val())
685 }
686
687 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
693 self.get(key).map(|x| x.val_str())
694 }
695
696 pub fn into_property_map_str(self) -> HashMap<String, String> {
701 self.properties
702 .into_iter()
703 .filter_map(|property| {
704 let val_string = property.val.map_or(Some(String::new()), |val| {
705 String::from_utf8(val)
706 .map_err(|e| {
707 debug!("Property value contains invalid UTF-8: {e}");
708 })
709 .ok()
710 })?;
711 Some((property.key, val_string))
712 })
713 .collect()
714 }
715}
716
717impl fmt::Display for TxtProperties {
718 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
719 let delimiter = ", ";
720 let props: Vec<String> = self.properties.iter().map(|p| p.to_string()).collect();
721 write!(f, "({})", props.join(delimiter))
722 }
723}
724
725impl From<&[u8]> for TxtProperties {
726 fn from(txt: &[u8]) -> Self {
727 let properties = decode_txt_unique(txt);
728 TxtProperties { properties }
729 }
730}
731
732#[derive(Clone, PartialEq, Eq)]
734#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
735pub struct TxtProperty {
736 key: String,
738
739 #[cfg_attr(feature = "serde", serde(rename = "value"))]
743 val: Option<Vec<u8>>,
744}
745
746impl TxtProperty {
747 pub fn key(&self) -> &str {
749 &self.key
750 }
751
752 pub fn val(&self) -> Option<&[u8]> {
756 self.val.as_deref()
757 }
758
759 pub fn val_str(&self) -> &str {
761 self.val
762 .as_ref()
763 .map_or("", |v| std::str::from_utf8(&v[..]).unwrap_or_default())
764 }
765}
766
767impl<K, V> From<&(K, V)> for TxtProperty
769where
770 K: ToString,
771 V: ToString,
772{
773 fn from(prop: &(K, V)) -> Self {
774 Self {
775 key: prop.0.to_string(),
776 val: Some(prop.1.to_string().into_bytes()),
777 }
778 }
779}
780
781impl<K, V> From<(K, V)> for TxtProperty
782where
783 K: ToString,
784 V: AsRef<[u8]>,
785{
786 fn from(prop: (K, V)) -> Self {
787 Self {
788 key: prop.0.to_string(),
789 val: Some(prop.1.as_ref().into()),
790 }
791 }
792}
793
794impl From<&str> for TxtProperty {
796 fn from(key: &str) -> Self {
797 Self {
798 key: key.to_string(),
799 val: None,
800 }
801 }
802}
803
804impl fmt::Display for TxtProperty {
805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806 write!(f, "{}={}", self.key, self.val_str())
807 }
808}
809
810impl fmt::Debug for TxtProperty {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 let val_string = self.val.as_ref().map_or_else(
816 || "None".to_string(),
817 |v| {
818 std::str::from_utf8(&v[..]).map_or_else(
819 |_| format!("Some({})", u8_slice_to_hex(&v[..])),
820 |s| format!("Some(\"{s}\")"),
821 )
822 },
823 );
824
825 write!(
826 f,
827 "TxtProperty {{key: \"{}\", val: {}}}",
828 &self.key, &val_string,
829 )
830 }
831}
832
833const HEX_TABLE: [char; 16] = [
834 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
835];
836
837fn u8_slice_to_hex(slice: &[u8]) -> String {
841 let mut hex = String::with_capacity(slice.len() * 2 + 2);
842 hex.push_str("0x");
843 for b in slice {
844 hex.push(HEX_TABLE[(b >> 4) as usize]);
845 hex.push(HEX_TABLE[(b & 0x0F) as usize]);
846 }
847 hex
848}
849
850pub trait IntoTxtProperties {
852 fn into_txt_properties(self) -> TxtProperties;
853}
854
855impl IntoTxtProperties for HashMap<String, String> {
856 fn into_txt_properties(mut self) -> TxtProperties {
857 let properties = self
858 .drain()
859 .map(|(key, val)| TxtProperty {
860 key,
861 val: Some(val.into_bytes()),
862 })
863 .collect();
864 TxtProperties { properties }
865 }
866}
867
868impl IntoTxtProperties for Option<HashMap<String, String>> {
870 fn into_txt_properties(self) -> TxtProperties {
871 self.map_or_else(
872 || TxtProperties {
873 properties: Vec::new(),
874 },
875 |h| h.into_txt_properties(),
876 )
877 }
878}
879
880impl<'a, T: 'a> IntoTxtProperties for &'a [T]
882where
883 TxtProperty: From<&'a T>,
884{
885 fn into_txt_properties(self) -> TxtProperties {
886 let mut properties = Vec::new();
887 let mut keys = HashSet::new();
888 for t in self.iter() {
889 let prop = TxtProperty::from(t);
890 let key = prop.key.to_lowercase();
891 if keys.insert(key) {
892 properties.push(prop);
900 }
901 }
902 TxtProperties { properties }
903 }
904}
905
906impl IntoTxtProperties for Vec<TxtProperty> {
907 fn into_txt_properties(self) -> TxtProperties {
908 TxtProperties { properties: self }
909 }
910}
911
912fn encode_txt<'a>(properties: impl Iterator<Item = &'a TxtProperty>) -> Vec<u8> {
914 let mut bytes = Vec::new();
915 for prop in properties {
916 let mut s = prop.key.clone().into_bytes();
917 if let Some(v) = &prop.val {
918 s.extend(b"=");
919 s.extend(v);
920 }
921
922 debug_assert!(
923 s.len() <= u8::MAX as usize,
924 "TXT property '{}' exceeds 255 bytes; should have been validated in ServiceInfo::new()",
925 prop.key
926 );
927 s.truncate(u8::MAX as usize);
928 let sz: u8 = s.len() as u8;
929
930 bytes.push(sz);
933 bytes.extend(s);
934 }
935 if bytes.is_empty() {
936 bytes.push(0);
937 }
938 bytes
939}
940
941pub(crate) fn decode_txt(txt: &[u8]) -> Vec<TxtProperty> {
943 let mut properties = Vec::new();
944 let mut offset = 0;
945 while offset < txt.len() {
946 let length = txt[offset] as usize;
947 if length == 0 {
948 break; }
950 offset += 1; let offset_end = offset + length;
953 if offset_end > txt.len() {
954 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());
955 break; }
957 let kv_bytes = &txt[offset..offset_end];
958
959 let (k, v) = kv_bytes.iter().position(|&x| x == b'=').map_or_else(
961 || (kv_bytes.to_vec(), None),
962 |idx| (kv_bytes[..idx].to_vec(), Some(kv_bytes[idx + 1..].to_vec())),
963 );
964
965 match String::from_utf8(k) {
967 Ok(k_string) => {
968 properties.push(TxtProperty {
969 key: k_string,
970 val: v,
971 });
972 }
973 Err(e) => debug!("failed to convert to String from key: {}", e),
974 }
975
976 offset += length;
977 }
978
979 properties
980}
981
982fn decode_txt_unique(txt: &[u8]) -> Vec<TxtProperty> {
983 let mut properties = decode_txt(txt);
984
985 let mut keys = HashSet::new();
988 properties.retain(|p| {
989 let key = p.key().to_lowercase();
990 keys.insert(key) });
992 properties
993}
994
995pub(crate) fn valid_ip_on_intf(addr: &IpAddr, if_addr: &IfAddr) -> bool {
997 match (addr, if_addr) {
998 (IpAddr::V4(addr), IfAddr::V4(if_v4)) => {
999 let netmask = u32::from(if_v4.netmask);
1000 let intf_net = u32::from(if_v4.ip) & netmask;
1001 let addr_net = u32::from(*addr) & netmask;
1002 addr_net == intf_net
1003 }
1004 (IpAddr::V6(addr), IfAddr::V6(if_v6)) => {
1005 let netmask = u128::from(if_v6.netmask);
1006 let intf_net = u128::from(if_v6.ip) & netmask;
1007 let addr_net = u128::from(*addr) & netmask;
1008 addr_net == intf_net
1009 }
1010 _ => false,
1011 }
1012}
1013
1014#[derive(Debug)]
1016pub(crate) struct Probe {
1017 pub(crate) records: Vec<DnsRecordBox>,
1019
1020 pub(crate) waiting_services: HashSet<String>,
1023
1024 pub(crate) start_time: Instant,
1026
1027 pub(crate) next_send: Instant,
1029}
1030
1031impl Probe {
1032 pub(crate) fn new(start_time: Instant) -> Self {
1033 let next_send = start_time;
1040
1041 Self {
1042 records: Vec::new(),
1043 waiting_services: HashSet::new(),
1044 start_time,
1045 next_send,
1046 }
1047 }
1048
1049 pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
1051 let insert_position = self
1061 .records
1062 .binary_search_by(
1063 |existing| match existing.get_class().cmp(&record.get_class()) {
1064 std::cmp::Ordering::Equal => existing.get_type().cmp(&record.get_type()),
1065 other => other,
1066 },
1067 )
1068 .unwrap_or_else(|pos| pos);
1069
1070 self.records.insert(insert_position, record);
1071 }
1072
1073 pub(crate) fn tiebreaking(&mut self, msg: &DnsIncoming, probe_name: &str) {
1075 let now = Instant::now();
1076
1077 if self.start_time >= now {
1081 return;
1082 }
1083
1084 let incoming: Vec<_> = msg
1085 .authorities()
1086 .iter()
1087 .filter(|r| r.get_name() == probe_name)
1088 .collect();
1089 let min_len = self.records.len().min(incoming.len());
1099
1100 let mut cmp_result = cmp::Ordering::Equal;
1102 for (i, incoming_record) in incoming.iter().enumerate().take(min_len) {
1103 match self.records[i].compare(incoming_record.as_ref()) {
1104 cmp::Ordering::Equal => continue,
1105 other => {
1106 cmp_result = other;
1107 break; }
1109 }
1110 }
1111
1112 if cmp_result == cmp::Ordering::Equal {
1113 cmp_result = self.records.len().cmp(&incoming.len());
1115 }
1116
1117 match cmp_result {
1118 cmp::Ordering::Less => {
1119 debug!("tiebreaking '{probe_name}': LOST, will wait for one second",);
1120 self.start_time = now + Duration::from_millis(1000); self.next_send = now + Duration::from_millis(1000);
1122 }
1123 ordering => {
1124 debug!("tiebreaking '{probe_name}': {:?}", ordering);
1125 }
1126 }
1127 }
1128
1129 pub(crate) fn update_next_send(&mut self, now: Instant) {
1130 self.next_send = now + Duration::from_millis(250);
1131 }
1132
1133 pub(crate) fn expired(&self, now: Instant) -> bool {
1135 now >= self.start_time + Duration::from_millis(750)
1138 }
1139}
1140
1141pub(crate) struct DnsRegistry {
1143 pub(crate) probing: HashMap<String, Probe>,
1154
1155 pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
1158
1159 pub(crate) new_timers: Vec<Instant>,
1161
1162 pub(crate) name_changes: HashMap<String, String>,
1164
1165 pub(crate) last_multicast_v4: HashMap<String, Instant>,
1175
1176 pub(crate) last_multicast_v6: HashMap<String, Instant>,
1178}
1179
1180impl DnsRegistry {
1181 pub(crate) fn new() -> Self {
1182 Self {
1183 probing: HashMap::new(),
1184 active: HashMap::new(),
1185 new_timers: Vec::new(),
1186 name_changes: HashMap::new(),
1187 last_multicast_v4: HashMap::new(),
1188 last_multicast_v6: HashMap::new(),
1189 }
1190 }
1191
1192 pub(crate) fn apply_multicast_rate_limit(
1207 &mut self,
1208 out: &mut DnsOutgoing,
1209 now: Instant,
1210 is_ipv4: bool,
1211 ) {
1212 let last_multicast = if is_ipv4 {
1213 &mut self.last_multicast_v4
1214 } else {
1215 &mut self.last_multicast_v6
1216 };
1217
1218 last_multicast.retain(|_, last| {
1221 now.saturating_duration_since(*last)
1222 < Duration::from_millis(MULTICAST_RATE_LIMIT_MILLIS)
1223 });
1224
1225 out.retain_answers(|record| keep_after_rate_limit(last_multicast, record, now));
1226
1227 if out.answers_count() > 0 {
1229 out.retain_additionals(|record| keep_after_rate_limit(last_multicast, record, now));
1230 }
1231 }
1232
1233 pub(crate) fn resolve_name<'a>(&'a self, name: &'a str) -> &'a str {
1235 match self.name_changes.get(name) {
1236 Some(new_name) => new_name,
1237 None => name,
1238 }
1239 }
1240
1241 pub(crate) fn is_probing_done<T>(
1242 &mut self,
1243 answer: &T,
1244 service_name: &str,
1245 start_time: Instant,
1246 ) -> bool
1247 where
1248 T: DnsRecordExt + Send + 'static,
1249 {
1250 if let Some(active_records) = self.active.get(answer.get_name()) {
1251 for record in active_records.iter() {
1252 if answer.matches(record.as_ref()) {
1253 debug!(
1254 "found active record {} {}",
1255 answer.get_type(),
1256 answer.get_name(),
1257 );
1258 return true;
1259 }
1260 }
1261 }
1262
1263 let probe = self
1264 .probing
1265 .entry(answer.get_name().to_string())
1266 .or_insert_with(|| {
1267 debug!("new probe of {}", answer.get_name());
1268 Probe::new(start_time)
1269 });
1270
1271 self.new_timers.push(probe.next_send);
1272
1273 for record in probe.records.iter() {
1274 if answer.matches(record.as_ref()) {
1275 debug!(
1276 "found existing record {} in probe of '{}'",
1277 answer.get_type(),
1278 answer.get_name(),
1279 );
1280 probe.waiting_services.insert(service_name.to_string());
1281 return false; }
1283 }
1284
1285 debug!(
1286 "insert record {} into probe of {}",
1287 answer.get_type(),
1288 answer.get_name(),
1289 );
1290 probe.insert_record(answer.clone_box());
1291 probe.waiting_services.insert(service_name.to_string());
1292
1293 false
1294 }
1295
1296 pub(crate) fn update_hostname(
1300 &mut self,
1301 original: &str,
1302 new_name: &str,
1303 probe_time: Instant,
1304 ) -> bool {
1305 let mut found_records = Vec::new();
1306 let mut new_timer_added = false;
1307
1308 for (_name, probe) in self.probing.iter_mut() {
1309 probe.records.retain(|record| {
1310 if record.get_type() == RRType::SRV {
1311 if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1312 if srv.host() == original {
1313 let mut new_record = srv.clone();
1314 new_record.set_host(new_name.to_string());
1315 found_records.push(new_record);
1316 return false;
1317 }
1318 }
1319 }
1320 true
1321 });
1322 }
1323
1324 for (_name, records) in self.active.iter_mut() {
1325 records.retain(|record| {
1326 if record.get_type() == RRType::SRV {
1327 if let Some(srv) = record.any().downcast_ref::<DnsSrv>() {
1328 if srv.host() == original {
1329 let mut new_record = srv.clone();
1330 new_record.set_host(new_name.to_string());
1331 found_records.push(new_record);
1332 return false;
1333 }
1334 }
1335 }
1336 true
1337 });
1338 }
1339
1340 for record in found_records {
1341 let probe = match self.probing.get_mut(record.get_name()) {
1342 Some(p) => {
1343 p.start_time = probe_time; p
1345 }
1346 None => {
1347 let new_probe = self
1348 .probing
1349 .entry(record.get_name().to_string())
1350 .or_insert_with(|| Probe::new(probe_time));
1351 new_timer_added = true;
1352 new_probe
1353 }
1354 };
1355
1356 debug!(
1357 "insert record {} with new hostname {new_name} into probe for: {}",
1358 record.get_type(),
1359 record.get_name()
1360 );
1361 probe.insert_record(record.boxed());
1362 }
1363
1364 new_timer_added
1365 }
1366}
1367
1368pub(crate) const MULTICAST_RATE_LIMIT_MILLIS: u64 = 1000;
1372
1373fn keep_after_rate_limit(
1376 last_multicast: &mut HashMap<String, Instant>,
1377 record: &DnsRecordBox,
1378 now: Instant,
1379) -> bool {
1380 let key = rate_limit_key(record);
1381 match last_multicast.get(&key) {
1382 Some(last)
1383 if now.saturating_duration_since(*last)
1384 < Duration::from_millis(MULTICAST_RATE_LIMIT_MILLIS) =>
1385 {
1386 false
1387 }
1388 _ => {
1389 last_multicast.insert(key, now);
1390 true
1391 }
1392 }
1393}
1394
1395fn rate_limit_key(record: &DnsRecordBox) -> String {
1400 format!(
1401 "{}-{}-{}",
1402 record.get_name().to_lowercase(),
1403 record.get_type(),
1404 record.rdata_print(),
1405 )
1406}
1407
1408pub(crate) fn split_sub_domain(domain: &str) -> (&str, Option<&str>) {
1410 if let Some((_, ty_domain)) = domain.rsplit_once("._sub.") {
1411 (ty_domain, Some(domain))
1412 } else {
1413 (domain, None)
1414 }
1415}
1416
1417pub(crate) fn is_unicast_link_local(addr: &Ipv6Addr) -> bool {
1423 (addr.segments()[0] & 0xffc0) == 0xfe80
1424}
1425
1426#[derive(Clone, Debug)]
1429#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1430#[non_exhaustive]
1431pub struct ResolvedService {
1432 pub ty_domain: String,
1434
1435 pub sub_ty_domain: Option<String>,
1441
1442 pub fullname: String,
1444
1445 pub host: String,
1447
1448 pub port: u16,
1450
1451 pub addresses: HashSet<ScopedIp>,
1453
1454 pub txt_properties: TxtProperties,
1456}
1457
1458impl ResolvedService {
1459 pub fn is_valid(&self) -> bool {
1461 let some_missing = self.ty_domain.is_empty()
1462 || self.fullname.is_empty()
1463 || self.host.is_empty()
1464 || self.addresses.is_empty();
1465 !some_missing
1466 }
1467
1468 #[inline]
1469 pub const fn get_subtype(&self) -> &Option<String> {
1470 &self.sub_ty_domain
1471 }
1472
1473 #[inline]
1474 pub fn get_fullname(&self) -> &str {
1475 &self.fullname
1476 }
1477
1478 #[inline]
1479 pub fn get_hostname(&self) -> &str {
1480 &self.host
1481 }
1482
1483 #[inline]
1484 pub fn get_port(&self) -> u16 {
1485 self.port
1486 }
1487
1488 #[inline]
1489 pub fn get_addresses(&self) -> &HashSet<ScopedIp> {
1490 &self.addresses
1491 }
1492
1493 pub fn get_addresses_v4(&self) -> HashSet<Ipv4Addr> {
1494 self.addresses
1495 .iter()
1496 .filter_map(|ip| match ip {
1497 ScopedIp::V4(ipv4) => Some(*ipv4.addr()),
1498 _ => None,
1499 })
1500 .collect()
1501 }
1502
1503 #[inline]
1504 pub fn get_properties(&self) -> &TxtProperties {
1505 &self.txt_properties
1506 }
1507
1508 #[inline]
1509 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
1510 self.txt_properties.get(key)
1511 }
1512
1513 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
1514 self.txt_properties.get_property_val(key)
1515 }
1516
1517 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
1518 self.txt_properties.get_property_val_str(key)
1519 }
1520}
1521
1522#[cfg(test)]
1523mod tests {
1524 use super::{
1525 decode_txt, encode_txt, u8_slice_to_hex, DnsRegistry, Instant, ServiceInfo, TxtProperty,
1526 };
1527 use crate::dns_parser::{DnsOutgoing, DnsPointer, RRType, CLASS_IN, FLAGS_QR_RESPONSE};
1528 use crate::{IfKind, IfPredicate};
1529 use if_addrs::{IfAddr, IfOperStatus, Ifv4Addr, Ifv6Addr, Interface};
1530 use std::net::{Ipv4Addr, Ipv6Addr};
1531 use std::time::Duration;
1532
1533 #[test]
1537 fn test_multicast_rate_limit() {
1538 let mut registry = DnsRegistry::new();
1539
1540 let build_out = || {
1541 let mut out = DnsOutgoing::new(FLAGS_QR_RESPONSE);
1542 out.add_answer_record(DnsPointer::new(
1543 "_test._tcp.local.",
1544 RRType::PTR,
1545 CLASS_IN,
1546 4500,
1547 "inst._test._tcp.local.".to_string(),
1548 ));
1549 out
1550 };
1551
1552 let now = Instant::now();
1553
1554 let mut out = build_out();
1556 registry.apply_multicast_rate_limit(&mut out, now, true);
1557 assert_eq!(out.answers_count(), 1);
1558
1559 let mut out = build_out();
1561 registry.apply_multicast_rate_limit(&mut out, now + Duration::from_millis(500), true);
1562 assert_eq!(out.answers_count(), 0);
1563
1564 let mut out = build_out();
1566 registry.apply_multicast_rate_limit(&mut out, now + Duration::from_millis(1000), true);
1567 assert_eq!(out.answers_count(), 1);
1568 }
1569
1570 #[test]
1576 fn test_multicast_rate_limit_per_family() {
1577 let mut registry = DnsRegistry::new();
1578
1579 let build_out = || {
1580 let mut out = DnsOutgoing::new(FLAGS_QR_RESPONSE);
1581 out.add_answer_record(DnsPointer::new(
1582 "_test._tcp.local.",
1583 RRType::PTR,
1584 CLASS_IN,
1585 4500,
1586 "inst._test._tcp.local.".to_string(),
1587 ));
1588 out
1589 };
1590
1591 let now = Instant::now();
1592
1593 let mut out = build_out();
1595 registry.apply_multicast_rate_limit(&mut out, now, true);
1596 assert_eq!(out.answers_count(), 1);
1597
1598 let mut out = build_out();
1601 registry.apply_multicast_rate_limit(&mut out, now, false);
1602 assert_eq!(out.answers_count(), 1);
1603
1604 let mut out = build_out();
1607 registry.apply_multicast_rate_limit(&mut out, now + Duration::from_millis(500), true);
1608 assert_eq!(out.answers_count(), 0);
1609
1610 let mut out = build_out();
1612 registry.apply_multicast_rate_limit(&mut out, now + Duration::from_millis(500), false);
1613 assert_eq!(out.answers_count(), 0);
1614 }
1615
1616 #[test]
1621 fn test_multicast_rate_limit_additionals_not_stamped_without_answer() {
1622 let mut registry = DnsRegistry::new();
1623
1624 let ptr_answer = || {
1625 DnsPointer::new(
1626 "_test._tcp.local.",
1627 RRType::PTR,
1628 CLASS_IN,
1629 4500,
1630 "inst._test._tcp.local.".to_string(),
1631 )
1632 };
1633 let extra = || {
1634 DnsPointer::new(
1635 "_other._tcp.local.",
1636 RRType::PTR,
1637 CLASS_IN,
1638 4500,
1639 "inst._other._tcp.local.".to_string(),
1640 )
1641 };
1642
1643 let now = Instant::now();
1644
1645 let mut out = DnsOutgoing::new(FLAGS_QR_RESPONSE);
1647 out.add_answer_record(ptr_answer());
1648 registry.apply_multicast_rate_limit(&mut out, now, true);
1649 assert_eq!(out.answers_count(), 1);
1650
1651 let mut out = DnsOutgoing::new(FLAGS_QR_RESPONSE);
1654 out.add_answer_record(ptr_answer());
1655 out.add_additional_answer(extra());
1656 registry.apply_multicast_rate_limit(&mut out, now + Duration::from_millis(100), true);
1657 assert_eq!(out.answers_count(), 0);
1658
1659 let mut out = DnsOutgoing::new(FLAGS_QR_RESPONSE);
1663 out.add_answer_record(extra());
1664 registry.apply_multicast_rate_limit(&mut out, now + Duration::from_millis(200), true);
1665 assert_eq!(out.answers_count(), 1);
1666 }
1667
1668 #[test]
1669 fn test_txt_encode_decode() {
1670 let properties = [
1671 TxtProperty::from(&("key1", "value1")),
1672 TxtProperty::from(&("key2", "value2")),
1673 ];
1674
1675 let property_count = properties.len();
1677 let encoded = encode_txt(properties.iter());
1678 assert_eq!(
1679 encoded.len(),
1680 "key1=value1".len() + "key2=value2".len() + property_count
1681 );
1682 assert_eq!(encoded[0] as usize, "key1=value1".len());
1683
1684 let decoded = decode_txt(&encoded);
1686 assert_eq!(properties, decoded[..]);
1687
1688 let properties = vec![TxtProperty::from(&("key3", ""))];
1690 let property_count = properties.len();
1691 let encoded = encode_txt(properties.iter());
1692 assert_eq!(encoded.len(), "key3=".len() + property_count);
1693
1694 let decoded = decode_txt(&encoded);
1695 assert_eq!(properties, decoded);
1696
1697 let binary_val: Vec<u8> = vec![123, 234, 0];
1699 let binary_len = binary_val.len();
1700 let properties = vec![TxtProperty::from(("key4", binary_val))];
1701 let property_count = properties.len();
1702 let encoded = encode_txt(properties.iter());
1703 assert_eq!(encoded.len(), "key4=".len() + binary_len + property_count);
1704
1705 let decoded = decode_txt(&encoded);
1706 assert_eq!(properties, decoded);
1707
1708 let properties = vec![TxtProperty::from(("key5", "val=5"))];
1710 let property_count = properties.len();
1711 let encoded = encode_txt(properties.iter());
1712 assert_eq!(
1713 encoded.len(),
1714 "key5=".len() + "val=5".len() + property_count
1715 );
1716
1717 let decoded = decode_txt(&encoded);
1718 assert_eq!(properties, decoded);
1719
1720 let properties = vec![TxtProperty::from("key6")];
1722 let property_count = properties.len();
1723 let encoded = encode_txt(properties.iter());
1724 assert_eq!(encoded.len(), "key6".len() + property_count);
1725 let decoded = decode_txt(&encoded);
1726 assert_eq!(properties, decoded);
1727
1728 let properties = [TxtProperty::from(
1730 String::from_utf8(vec![0x30; 255]).unwrap().as_str(),
1731 )];
1732 let property_count = properties.len();
1733 let encoded = encode_txt(properties.iter());
1734 assert_eq!(encoded.len(), 255 + property_count);
1736 let decoded = decode_txt(&encoded);
1737 assert_eq!(properties.to_vec(), decoded);
1738 }
1739
1740 #[test]
1741 fn test_txt_property_exceeds_255_bytes() {
1742 let long_key = String::from_utf8(vec![0x30; 256]).unwrap();
1743 let result = ServiceInfo::new(
1744 "_test._tcp.local.",
1745 "test",
1746 "host",
1747 "",
1748 1234,
1749 &[(long_key.as_str(), "")][..],
1750 );
1751 assert!(result.is_err());
1752 assert!(result
1753 .unwrap_err()
1754 .to_string()
1755 .contains("exceeding the 255-byte limit"));
1756
1757 let key_at_limit = String::from_utf8(vec![0x30; 250]).unwrap();
1760 let result = ServiceInfo::new(
1761 "_test._tcp.local.",
1762 "test",
1763 "host",
1764 "",
1765 1234,
1766 &[(key_at_limit.as_str(), "abcd")][..],
1767 );
1768 assert!(result.is_ok());
1769 }
1770
1771 #[test]
1772 fn test_set_properties_from_txt() {
1773 let properties = [
1775 TxtProperty::from(&("one", "1")),
1776 TxtProperty::from(&("ONE", "2")),
1777 TxtProperty::from(&("One", "3")),
1778 ];
1779 let encoded = encode_txt(properties.iter());
1780
1781 let decoded = decode_txt(&encoded);
1783 assert_eq!(decoded.len(), 3);
1784
1785 let mut service_info =
1787 ServiceInfo::new("_test._tcp", "prop_test", "localhost", "", 1234, None).unwrap();
1788 service_info._set_properties_from_txt(&encoded);
1789 assert_eq!(service_info.get_properties().len(), 1);
1790
1791 let prop = service_info.get_properties().iter().next().unwrap();
1793 assert_eq!(prop.key, "one");
1794 assert_eq!(prop.val_str(), "1");
1795 }
1796
1797 #[test]
1798 fn test_u8_slice_to_hex() {
1799 let bytes = [0x01u8, 0x02u8, 0x03u8];
1800 let hex = u8_slice_to_hex(&bytes);
1801 assert_eq!(hex.as_str(), "0x010203");
1802
1803 let slice = "abcdefghijklmnopqrstuvwxyz";
1804 let hex = u8_slice_to_hex(slice.as_bytes());
1805 assert_eq!(hex.len(), slice.len() * 2 + 2);
1806 assert_eq!(
1807 hex.as_str(),
1808 "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"
1809 );
1810 }
1811
1812 #[test]
1813 fn test_txt_property_debug() {
1814 let prop_1 = TxtProperty {
1816 key: "key1".to_string(),
1817 val: Some("val1".to_string().into()),
1818 };
1819 let prop_1_debug = format!("{:?}", &prop_1);
1820 assert_eq!(
1821 prop_1_debug,
1822 "TxtProperty {key: \"key1\", val: Some(\"val1\")}"
1823 );
1824
1825 let prop_2 = TxtProperty {
1827 key: "key2".to_string(),
1828 val: Some(vec![150u8, 151u8, 152u8]),
1829 };
1830 let prop_2_debug = format!("{:?}", &prop_2);
1831 assert_eq!(
1832 prop_2_debug,
1833 "TxtProperty {key: \"key2\", val: Some(0x969798)}"
1834 );
1835 }
1836
1837 #[test]
1838 fn test_txt_decode_property_size_out_of_bounds() {
1839 let encoded: Vec<u8> = vec![
1841 0x0b, b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1843 b'1', 0x10, b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1846 b'2', ];
1848 let decoded = decode_txt(&encoded);
1850 assert_eq!(decoded.len(), 1);
1853 assert_eq!(decoded[0].key, "key1");
1855 }
1856
1857 #[test]
1858 fn test_is_address_supported() {
1859 let mut service_info =
1860 ServiceInfo::new("_test._tcp", "prop_test", "testhost", "", 1234, None).unwrap();
1861
1862 let intf_v6 = Interface {
1863 name: "foo".to_string(),
1864 index: Some(1),
1865 addr: IfAddr::V6(Ifv6Addr {
1866 ip: Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1867 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1868 broadcast: None,
1869 prefixlen: 16,
1870 }),
1871 oper_status: IfOperStatus::Up,
1872 is_p2p: false,
1873 #[cfg(windows)]
1874 adapter_name: String::new(),
1875 };
1876
1877 let intf_v4 = Interface {
1878 name: "bar".to_string(),
1879 index: Some(1),
1880 addr: IfAddr::V4(Ifv4Addr {
1881 ip: Ipv4Addr::new(192, 1, 2, 3),
1882 netmask: Ipv4Addr::new(255, 255, 0, 0),
1883 broadcast: None,
1884 prefixlen: 16,
1885 }),
1886 oper_status: IfOperStatus::Up,
1887 is_p2p: false,
1888 #[cfg(windows)]
1889 adapter_name: String::new(),
1890 };
1891
1892 let intf_baz = Interface {
1893 name: "baz".to_string(),
1894 index: Some(1),
1895 addr: IfAddr::V6(Ifv6Addr {
1896 ip: Ipv6Addr::new(0x2003, 0xdb8, 0, 0, 0x1234, 0, 0, 1),
1897 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1898 broadcast: None,
1899 prefixlen: 16,
1900 }),
1901 oper_status: IfOperStatus::Up,
1902 is_p2p: false,
1903 #[cfg(windows)]
1904 adapter_name: String::new(),
1905 };
1906
1907 let intf_loopback_v4 = Interface {
1908 name: "foo".to_string(),
1909 index: Some(1),
1910 addr: IfAddr::V4(Ifv4Addr {
1911 ip: Ipv4Addr::new(127, 0, 0, 1),
1912 netmask: Ipv4Addr::new(255, 255, 255, 255),
1913 broadcast: None,
1914 prefixlen: 16,
1915 }),
1916 oper_status: IfOperStatus::Up,
1917 is_p2p: false,
1918 #[cfg(windows)]
1919 adapter_name: String::new(),
1920 };
1921
1922 let intf_loopback_v6 = Interface {
1923 name: "foo".to_string(),
1924 index: Some(1),
1925 addr: IfAddr::V6(Ifv6Addr {
1926 ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1),
1927 netmask: Ipv6Addr::new(
1928 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
1929 ),
1930 broadcast: None,
1931 prefixlen: 16,
1932 }),
1933 oper_status: IfOperStatus::Up,
1934 is_p2p: false,
1935 #[cfg(windows)]
1936 adapter_name: String::new(),
1937 };
1938
1939 let intf_link_local_v4 = Interface {
1940 name: "foo".to_string(),
1941 index: Some(1),
1942 addr: IfAddr::V4(Ifv4Addr {
1943 ip: Ipv4Addr::new(169, 254, 0, 1),
1944 netmask: Ipv4Addr::new(255, 255, 0, 0),
1945 broadcast: None,
1946 prefixlen: 16,
1947 }),
1948 oper_status: IfOperStatus::Up,
1949 is_p2p: false,
1950 #[cfg(windows)]
1951 adapter_name: String::new(),
1952 };
1953
1954 let intf_link_local_v6 = Interface {
1955 name: "foo".to_string(),
1956 index: Some(1),
1957 addr: IfAddr::V6(Ifv6Addr {
1958 ip: Ipv6Addr::new(0xfe80, 0, 0, 0, 0x1234, 0, 0, 1),
1959 netmask: Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0),
1960 broadcast: None,
1961 prefixlen: 16,
1962 }),
1963 oper_status: IfOperStatus::Up,
1964 is_p2p: false,
1965 #[cfg(windows)]
1966 adapter_name: String::new(),
1967 };
1968
1969 assert!(service_info.is_address_supported(&intf_v6));
1971
1972 service_info.set_interfaces(vec![
1974 IfKind::Name("foo".to_string()),
1975 IfKind::Name("bar".to_string()),
1976 ]);
1977 assert!(!service_info.is_address_supported(&intf_baz));
1978
1979 service_info.set_link_local_only(true);
1981 assert!(!service_info.is_address_supported(&intf_v4));
1982 assert!(!service_info.is_address_supported(&intf_v6));
1983 assert!(service_info.is_address_supported(&intf_link_local_v4));
1984 assert!(service_info.is_address_supported(&intf_link_local_v6));
1985 service_info.set_link_local_only(false);
1986
1987 service_info.set_interfaces(vec![IfKind::All]);
1989 assert!(service_info.is_address_supported(&intf_v6));
1990 assert!(service_info.is_address_supported(&intf_v4));
1991
1992 service_info.set_interfaces(vec![IfKind::IPv6]);
1994 assert!(service_info.is_address_supported(&intf_v6));
1995 assert!(!service_info.is_address_supported(&intf_v4));
1996
1997 service_info.set_interfaces(vec![IfKind::IPv4]);
1999 assert!(service_info.is_address_supported(&intf_v4));
2000 assert!(!service_info.is_address_supported(&intf_v6));
2001
2002 service_info.set_interfaces(vec![IfKind::Addr(intf_v6.ip())]);
2004 assert!(service_info.is_address_supported(&intf_v6));
2005 assert!(!service_info.is_address_supported(&intf_v4));
2006
2007 service_info.set_interfaces(vec![IfKind::LoopbackV4]);
2009 assert!(service_info.is_address_supported(&intf_loopback_v4));
2010 assert!(!service_info.is_address_supported(&intf_loopback_v6));
2011
2012 service_info.set_interfaces(vec![IfKind::LoopbackV6]);
2014 assert!(!service_info.is_address_supported(&intf_loopback_v4));
2015 assert!(service_info.is_address_supported(&intf_loopback_v6));
2016
2017 service_info.set_interfaces(vec![IfKind::Predicate(IfPredicate::new(|intf| {
2019 intf.ip().is_ipv4() && intf.name == "foo"
2020 }))]);
2021 assert!(service_info.is_address_supported(&intf_loopback_v4));
2022 assert!(!service_info.is_address_supported(&intf_v4));
2023 assert!(!service_info.is_address_supported(&intf_loopback_v6));
2024 }
2025
2026 #[test]
2027 fn test_scoped_ip_set_detects_interface_id_change() {
2028 use crate::{InterfaceId, ScopedIp, ScopedIpV4};
2029 use std::collections::HashSet;
2030
2031 let intf1 = InterfaceId {
2032 name: "en0".to_string(),
2033 index: 1,
2034 };
2035 let intf2 = InterfaceId {
2036 name: "en1".to_string(),
2037 index: 2,
2038 };
2039 let addr = Ipv4Addr::new(192, 168, 1, 100);
2040
2041 let scoped_v4_one_intf = ScopedIpV4::new(addr, intf1);
2042 let mut scoped_v4_two_intfs = scoped_v4_one_intf.clone();
2043 scoped_v4_two_intfs.add_interface_id(intf2);
2044
2045 assert_ne!(scoped_v4_one_intf, scoped_v4_two_intfs);
2046
2047 let set_old: HashSet<ScopedIp> = HashSet::from([ScopedIp::V4(scoped_v4_one_intf)]);
2048 let set_new: HashSet<ScopedIp> = HashSet::from([ScopedIp::V4(scoped_v4_two_intfs)]);
2049
2050 assert_ne!(set_old, set_new);
2051 }
2052
2053 #[cfg(test)]
2054 #[cfg(feature = "serde")]
2055 mod serde {
2056 use super::{Ipv4Addr, Ipv6Addr};
2057 use crate::{ResolvedService, ScopedIp, TxtProperties};
2058
2059 use std::collections::HashSet;
2060 use std::net::IpAddr;
2061
2062 #[test]
2063 fn test_deserialize_serialize() -> Result<(), Box<dyn std::error::Error>> {
2064 let addresses = HashSet::from([
2065 ScopedIp::from(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
2066 ScopedIp::from(IpAddr::V6(Ipv6Addr::new(
2067 0xfe80, 0x2001, 0x0db8, 0x85a3, 0x0000, 0x8a2e, 0x0370, 0x7334,
2068 ))),
2069 ]);
2070
2071 let service = ResolvedService {
2072 ty_domain: "_http._tcp.local.".to_owned(),
2073 sub_ty_domain: None,
2074 fullname: "example._http._tcp.local.".to_owned(),
2075 host: "example.local.".to_owned(),
2076 port: 1234,
2077 addresses,
2078 txt_properties: TxtProperties::new(),
2079 };
2080
2081 let json = serde_json::to_value(&service)?;
2082
2083 let parsed: ResolvedService = serde_json::from_value(json)?;
2084
2085 assert!(compare(&service, &parsed));
2086
2087 Ok(())
2088 }
2089
2090 fn compare(service: &ResolvedService, other: &ResolvedService) -> bool {
2091 service.ty_domain == other.ty_domain
2092 && service.sub_ty_domain == other.sub_ty_domain
2093 && service.fullname == other.fullname
2094 && service.host == other.host
2095 && service.port == other.port
2096 && service.addresses == other.addresses
2097 && service.txt_properties == other.txt_properties
2098 }
2099 }
2100}