1#[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
19const DNS_HOST_TTL: u32 = 120; const DNS_OTHER_TTL: u32 = 4500; #[derive(Debug)]
25pub(crate) struct MyIntf {
26 pub(crate) name: String,
28
29 pub(crate) index: u32,
31
32 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#[derive(Debug, Clone)]
60pub struct ServiceInfo {
61 ty_domain: String, sub_domain: Option<String>, fullname: String, server: String, addresses: HashSet<IpAddr>,
70 port: u16,
71 host_ttl: u32, other_ttl: u32, priority: u16,
74 weight: u16,
75 txt_properties: TxtProperties,
76 addr_auto: bool, status: HashMap<u32, ServiceStatus>, 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 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 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 pub const fn enable_addr_auto(mut self) -> Self {
186 self.addr_auto = true;
187 self
188 }
189
190 pub const fn is_addr_auto(&self) -> bool {
193 self.addr_auto
194 }
195
196 pub fn set_requires_probe(&mut self, enable: bool) {
201 self.requires_probe = enable;
202 }
203
204 pub const fn requires_probe(&self) -> bool {
208 self.requires_probe
209 }
210
211 #[inline]
215 pub fn get_type(&self) -> &str {
216 &self.ty_domain
217 }
218
219 #[inline]
224 pub const fn get_subtype(&self) -> &Option<String> {
225 &self.sub_domain
226 }
227
228 #[inline]
232 pub fn get_fullname(&self) -> &str {
233 &self.fullname
234 }
235
236 #[inline]
238 pub const fn get_properties(&self) -> &TxtProperties {
239 &self.txt_properties
240 }
241
242 pub fn get_property(&self, key: &str) -> Option<&TxtProperty> {
247 self.txt_properties.get(key)
248 }
249
250 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
255 self.txt_properties.get_property_val(key)
256 }
257
258 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
263 self.txt_properties.get_property_val_str(key)
264 }
265
266 #[inline]
268 pub fn get_hostname(&self) -> &str {
269 &self.server
270 }
271
272 #[inline]
274 pub const fn get_port(&self) -> u16 {
275 self.port
276 }
277
278 #[inline]
280 pub const fn get_addresses(&self) -> &HashSet<IpAddr> {
281 &self.addresses
282 }
283
284 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 #[inline]
299 pub const fn get_host_ttl(&self) -> u32 {
300 self.host_ttl
301 }
302
303 #[inline]
305 pub const fn get_other_ttl(&self) -> u32 {
306 self.other_ttl
307 }
308
309 #[inline]
311 pub const fn get_priority(&self) -> u16 {
312 self.priority
313 }
314
315 #[inline]
317 pub const fn get_weight(&self) -> u16 {
318 self.weight
319 }
320
321 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 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 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 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 pub(crate) fn _set_host_ttl(&mut self, ttl: u32) {
386 self.host_ttl = ttl;
387 }
388
389 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 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
427fn 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
436pub 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
447impl 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
473impl<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
486impl 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#[derive(Debug, Clone, PartialEq, Eq)]
517pub struct TxtProperties {
518 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 pub fn iter(&self) -> impl Iterator<Item = &TxtProperty> {
537 self.properties.iter()
538 }
539
540 pub fn len(&self) -> usize {
542 self.properties.len()
543 }
544
545 pub fn is_empty(&self) -> bool {
547 self.properties.is_empty()
548 }
549
550 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 pub fn get_property_val(&self, key: &str) -> Option<Option<&[u8]>> {
565 self.get(key).map(|x| x.val())
566 }
567
568 pub fn get_property_val_str(&self, key: &str) -> Option<&str> {
574 self.get(key).map(|x| x.val_str())
575 }
576
577 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#[derive(Clone, PartialEq, Eq)]
615pub struct TxtProperty {
616 key: String,
618
619 val: Option<Vec<u8>>,
623}
624
625impl TxtProperty {
626 pub fn key(&self) -> &str {
628 &self.key
629 }
630
631 pub fn val(&self) -> Option<&[u8]> {
635 self.val.as_deref()
636 }
637
638 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
646impl<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
673impl 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
689impl 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
716fn 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
729pub 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
747impl 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
759impl<'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 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
791fn 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 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 bytes.push(sz);
811 bytes.extend(s);
812 }
813 if bytes.is_empty() {
814 bytes.push(0);
815 }
816 bytes
817}
818
819pub(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; }
828 offset += 1; 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; }
835 let kv_bytes = &txt[offset..offset_end];
836
837 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 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 let mut keys = HashSet::new();
866 properties.retain(|p| {
867 let key = p.key().to_lowercase();
868 keys.insert(key) });
870 properties
871}
872
873pub(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#[derive(Debug)]
894pub(crate) struct Probe {
895 pub(crate) records: Vec<DnsRecordBox>,
897
898 pub(crate) waiting_services: HashSet<String>,
901
902 pub(crate) start_time: u64,
904
905 pub(crate) next_send: u64,
907}
908
909impl Probe {
910 pub(crate) fn new(start_time: u64) -> Self {
911 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 pub(crate) fn insert_record(&mut self, record: DnsRecordBox) {
929 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 pub(crate) fn tiebreaking(&mut self, incoming: &[&DnsRecordBox], now: u64, probe_name: &str) {
953 let min_len = self.records.len().min(incoming.len());
963
964 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; }
973 }
974 }
975
976 if cmp_result == cmp::Ordering::Equal {
977 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; 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 pub(crate) fn expired(&self, now: u64) -> bool {
999 now >= self.start_time + 750
1002 }
1003}
1004
1005pub(crate) struct DnsRegistry {
1007 pub(crate) probing: HashMap<String, Probe>,
1018
1019 pub(crate) active: HashMap<String, Vec<DnsRecordBox>>,
1021
1022 pub(crate) new_timers: Vec<u64>,
1024
1025 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; }
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 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; 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
1166pub(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#[derive(Clone, Debug)]
1178#[non_exhaustive]
1179pub struct ResolvedService {
1180 pub ty_domain: String,
1182
1183 pub sub_ty_domain: Option<String>,
1189
1190 pub fullname: String,
1192
1193 pub host: String,
1195
1196 pub port: u16,
1198
1199 pub addresses: HashSet<ScopedIp>,
1201
1202 pub txt_properties: TxtProperties,
1204}
1205
1206impl ResolvedService {
1207 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 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 let decoded = decode_txt(&encoded);
1292 assert!(properties[..] == decoded[..]);
1293
1294 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 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 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 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 let properties = vec![TxtProperty::from(
1336 String::from_utf8(vec![0x30; 1024]).unwrap().as_str(), )];
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 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 let decoded = decode_txt(&encoded);
1362 assert_eq!(decoded.len(), 3);
1363
1364 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 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 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 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 let encoded: Vec<u8> = vec![
1420 0x0b, b'k', b'e', b'y', b'1', b'=', b'v', b'a', b'l', b'u', b'e',
1422 b'1', 0x10, b'k', b'e', b'y', b'2', b'=', b'v', b'a', b'l', b'u', b'e',
1425 b'2', ];
1427 let decoded = decode_txt(&encoded);
1429 assert_eq!(decoded.len(), 1);
1432 assert_eq!(decoded[0].key, "key1");
1434 }
1435}