1use hickory_client::client::{Client, SyncClient};
4use hickory_client::udp::UdpClientConnection;
5use hickory_server::authority::{Catalog, ZoneType};
6use hickory_server::proto::rr::rdata::{A, AAAA};
7use hickory_server::proto::rr::{DNSClass, Name, RData, Record, RecordType};
8use hickory_server::server::ServerFuture;
9use hickory_server::store::in_memory::InMemoryAuthority;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
13use std::str::FromStr;
14use std::sync::Arc;
15use std::time::Duration;
16use tokio::net::{TcpListener, UdpSocket};
17use tokio::sync::RwLock;
18
19pub const DEFAULT_DNS_PORT: u16 = 15353;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct DnsConfig {
25 pub zone: String,
27 pub port: u16,
29 pub bind_addr: IpAddr,
31}
32
33impl DnsConfig {
34 #[must_use]
36 pub fn new(zone: &str, bind_addr: IpAddr) -> Self {
37 Self {
38 zone: zone.to_string(),
39 port: DEFAULT_DNS_PORT,
40 bind_addr,
41 }
42 }
43
44 #[must_use]
46 pub fn with_port(mut self, port: u16) -> Self {
47 self.port = port;
48 self
49 }
50}
51
52#[must_use]
57pub fn peer_hostname(ip: IpAddr) -> String {
58 match ip {
59 IpAddr::V4(v4) => {
60 let octets = v4.octets();
61 format!("node-{}-{}", octets[2], octets[3])
62 }
63 IpAddr::V6(v6) => {
64 let segments = v6.segments();
65 let last_segment = segments[7];
66 format!("node-{last_segment:04x}")
67 }
68 }
69}
70
71#[derive(Debug, thiserror::Error)]
73pub enum DnsError {
74 #[error("Invalid domain name: {0}")]
75 InvalidName(String),
76
77 #[error("DNS server error: {0}")]
78 Server(String),
79
80 #[error("DNS client error: {0}")]
81 Client(String),
82
83 #[error("IO error: {0}")]
84 Io(#[from] std::io::Error),
85
86 #[error("Record not found: {0}")]
87 NotFound(String),
88}
89
90#[derive(Clone)]
94pub struct DnsHandle {
95 authority: Arc<InMemoryAuthority>,
96 zone_origin: Name,
97 serial: Arc<RwLock<u32>>,
98}
99
100impl DnsHandle {
101 pub async fn add_record(&self, hostname: &str, ip: IpAddr) -> Result<(), DnsError> {
109 let fqdn = if hostname.ends_with('.') {
111 Name::from_str(hostname)
112 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?
113 } else {
114 let name = Name::from_str(hostname)
116 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?;
117 name.append_domain(&self.zone_origin)
118 .map_err(|e| DnsError::InvalidName(format!("Failed to append zone: {e}")))?
119 };
120
121 let rdata = match ip {
123 IpAddr::V4(v4) => RData::A(A::from(v4)),
124 IpAddr::V6(v6) => RData::AAAA(AAAA::from(v6)),
125 };
126 let record = Record::from_rdata(fqdn, 300, rdata); let serial = {
130 let mut s = self.serial.write().await;
131 let current = *s;
132 *s = s.wrapping_add(1);
133 current
134 };
135
136 self.authority.upsert(record, serial).await;
138
139 Ok(())
140 }
141
142 pub async fn remove_record(&self, hostname: &str) -> Result<bool, DnsError> {
150 let fqdn = if hostname.ends_with('.') {
151 Name::from_str(hostname)
152 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?
153 } else {
154 let name = Name::from_str(hostname)
155 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?;
156 name.append_domain(&self.zone_origin)
157 .map_err(|e| DnsError::InvalidName(format!("Failed to append zone: {e}")))?
158 };
159
160 let serial = {
161 let mut s = self.serial.write().await;
162 let current = *s;
163 *s = s.wrapping_add(1);
164 current
165 };
166
167 let a_record = Record::with(fqdn.clone(), RecordType::A, 0);
171 self.authority.upsert(a_record, serial).await;
172
173 let aaaa_record = Record::with(fqdn.clone(), RecordType::AAAA, 0);
174 self.authority.upsert(aaaa_record, serial).await;
175
176 Ok(true)
177 }
178
179 #[must_use]
181 pub fn zone_origin(&self) -> &Name {
182 &self.zone_origin
183 }
184}
185
186pub struct DnsServer {
188 listen_addr: SocketAddr,
189 authority: Arc<InMemoryAuthority>,
190 zone_origin: Name,
191 serial: Arc<RwLock<u32>>,
192}
193
194impl DnsServer {
195 pub fn new(listen_addr: SocketAddr, zone: &str) -> Result<Self, DnsError> {
201 let zone_origin =
202 Name::from_str(zone).map_err(|e| DnsError::InvalidName(format!("{zone}: {e}")))?;
203
204 let authority = Arc::new(InMemoryAuthority::empty(
207 zone_origin.clone(),
208 ZoneType::Primary,
209 false,
210 ));
211
212 Ok(Self {
213 listen_addr,
214 authority,
215 zone_origin,
216 serial: Arc::new(RwLock::new(1)),
217 })
218 }
219
220 pub fn from_config(config: &DnsConfig) -> Result<Self, DnsError> {
226 let listen_addr = SocketAddr::new(config.bind_addr, config.port);
227 Self::new(listen_addr, &config.zone)
228 }
229
230 #[must_use]
235 pub fn handle(&self) -> DnsHandle {
236 DnsHandle {
237 authority: Arc::clone(&self.authority),
238 zone_origin: self.zone_origin.clone(),
239 serial: Arc::clone(&self.serial),
240 }
241 }
242
243 pub async fn add_record(&self, hostname: &str, ip: IpAddr) -> Result<(), DnsError> {
251 self.handle().add_record(hostname, ip).await
252 }
253
254 pub async fn remove_record(&self, hostname: &str) -> Result<bool, DnsError> {
260 self.handle().remove_record(hostname).await
261 }
262
263 #[allow(clippy::unused_async)]
272 pub async fn start(self) -> Result<DnsHandle, DnsError> {
273 let handle = self.handle();
274 let listen_addr = self.listen_addr;
275 let zone_origin = self.zone_origin.clone();
276 let authority = Arc::clone(&self.authority);
277
278 tokio::spawn(async move {
280 if let Err(e) = Self::run_server(listen_addr, zone_origin, authority).await {
281 tracing::error!("DNS server error: {}", e);
282 }
283 });
284
285 Ok(handle)
286 }
287
288 #[allow(clippy::unused_async)]
298 pub async fn start_background(&self) -> Result<DnsHandle, DnsError> {
299 let handle = self.handle();
300 let listen_addr = self.listen_addr;
301 let zone_origin = self.zone_origin.clone();
302 let authority = Arc::clone(&self.authority);
303
304 tokio::spawn(async move {
305 if let Err(e) = Self::run_server(listen_addr, zone_origin, authority).await {
306 tracing::error!("DNS server error: {}", e);
307 }
308 });
309
310 Ok(handle)
311 }
312
313 #[allow(clippy::unused_async)]
342 pub async fn bind_windows_fallback(&self, bind_ip: IpAddr) -> Result<DnsHandle, DnsError> {
343 let handle = self.handle();
344 let listen_addr = SocketAddr::new(bind_ip, 53);
345 let zone_origin = self.zone_origin.clone();
346 let authority = Arc::clone(&self.authority);
347
348 let udp_socket = UdpSocket::bind(listen_addr).await?;
352 let tcp_listener = TcpListener::bind(listen_addr).await?;
353
354 tokio::spawn(async move {
355 let mut catalog = Catalog::new();
356 catalog.upsert(zone_origin.into(), Box::new(authority));
357 let mut server = ServerFuture::new(catalog);
358 server.register_socket(udp_socket);
359 server.register_listener(tcp_listener, Duration::from_secs(30));
360 tracing::info!(
361 addr = %listen_addr,
362 "Windows fallback DNS listener started on port 53",
363 );
364 if let Err(e) = server.block_until_done().await {
365 tracing::error!("Windows fallback DNS listener error: {}", e);
366 }
367 });
368
369 Ok(handle)
370 }
371
372 async fn run_server(
374 listen_addr: SocketAddr,
375 zone_origin: Name,
376 authority: Arc<InMemoryAuthority>,
377 ) -> Result<(), DnsError> {
378 let mut catalog = Catalog::new();
380
381 catalog.upsert(zone_origin.into(), Box::new(authority));
383
384 let mut server = ServerFuture::new(catalog);
386
387 let udp_socket = UdpSocket::bind(listen_addr).await?;
389 server.register_socket(udp_socket);
390
391 let tcp_listener = TcpListener::bind(listen_addr).await?;
393 server.register_listener(tcp_listener, Duration::from_secs(30));
394
395 tracing::info!(addr = %listen_addr, "DNS server listening");
396
397 server
399 .block_until_done()
400 .await
401 .map_err(|e| DnsError::Server(e.to_string()))?;
402
403 Ok(())
404 }
405
406 #[must_use]
408 pub fn listen_addr(&self) -> SocketAddr {
409 self.listen_addr
410 }
411
412 #[must_use]
414 pub fn zone_origin(&self) -> &Name {
415 &self.zone_origin
416 }
417}
418
419pub struct DnsClient {
421 server_addr: SocketAddr,
422}
423
424impl DnsClient {
425 #[must_use]
427 pub fn new(server_addr: SocketAddr) -> Self {
428 Self { server_addr }
429 }
430
431 pub fn query_a(&self, hostname: &str) -> Result<Option<Ipv4Addr>, DnsError> {
437 let name = Name::from_str(hostname)
438 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?;
439
440 let conn = UdpClientConnection::new(self.server_addr)
441 .map_err(|e| DnsError::Client(e.to_string()))?;
442
443 let client = SyncClient::new(conn);
444
445 let response = client
446 .query(&name, DNSClass::IN, RecordType::A)
447 .map_err(|e| DnsError::Client(e.to_string()))?;
448
449 for answer in response.answers() {
451 if let Some(RData::A(a_record)) = answer.data() {
452 return Ok(Some((*a_record).into()));
453 }
454 }
455
456 Ok(None)
457 }
458
459 pub fn query_aaaa(&self, hostname: &str) -> Result<Option<Ipv6Addr>, DnsError> {
465 let name = Name::from_str(hostname)
466 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?;
467
468 let conn = UdpClientConnection::new(self.server_addr)
469 .map_err(|e| DnsError::Client(e.to_string()))?;
470
471 let client = SyncClient::new(conn);
472
473 let response = client
474 .query(&name, DNSClass::IN, RecordType::AAAA)
475 .map_err(|e| DnsError::Client(e.to_string()))?;
476
477 for answer in response.answers() {
479 if let Some(RData::AAAA(aaaa_record)) = answer.data() {
480 return Ok(Some((*aaaa_record).into()));
481 }
482 }
483
484 Ok(None)
485 }
486
487 pub fn query_addr(&self, hostname: &str) -> Result<Option<IpAddr>, DnsError> {
495 if let Ok(Some(v4)) = self.query_a(hostname) {
497 return Ok(Some(IpAddr::V4(v4)));
498 }
499
500 if let Ok(Some(v6)) = self.query_aaaa(hostname) {
502 return Ok(Some(IpAddr::V6(v6)));
503 }
504
505 Ok(None)
506 }
507}
508
509pub struct ServiceDiscovery {
511 dns_server: SocketAddr,
512 records: RwLock<HashMap<String, IpAddr>>,
513}
514
515impl ServiceDiscovery {
516 #[must_use]
518 pub fn new(dns_server_addr: SocketAddr) -> Self {
519 Self {
520 dns_server: dns_server_addr,
521 records: RwLock::new(HashMap::new()),
522 }
523 }
524
525 pub async fn register(&self, name: &str, ip: IpAddr) {
527 let mut records = self.records.write().await;
528 records.insert(name.to_string(), ip);
529 }
530
531 pub async fn resolve(&self, name: &str) -> Option<IpAddr> {
536 {
538 let records = self.records.read().await;
539 if let Some(ip) = records.get(name) {
540 return Some(*ip);
541 }
542 }
543
544 let client = DnsClient::new(self.dns_server);
546 if let Ok(Some(addr)) = client.query_addr(name) {
547 return Some(addr);
548 }
549
550 None
551 }
552
553 pub async fn unregister(&self, name: &str) {
555 let mut records = self.records.write().await;
556 records.remove(name);
557 }
558
559 pub async fn list_services(&self) -> Vec<String> {
561 let records = self.records.read().await;
562 records.keys().cloned().collect()
563 }
564
565 pub fn dns_server(&self) -> SocketAddr {
567 self.dns_server
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn test_peer_hostname_v4() {
577 assert_eq!(
579 peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1))),
580 "node-0-1"
581 );
582 assert_eq!(
583 peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 200, 0, 5))),
584 "node-0-5"
585 );
586 assert_eq!(
587 peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 200, 1, 100))),
588 "node-1-100"
589 );
590 assert_eq!(
591 peer_hostname(IpAddr::V4(Ipv4Addr::new(192, 168, 255, 254))),
592 "node-255-254"
593 );
594 }
595
596 #[test]
597 fn test_peer_hostname_v6() {
598 assert_eq!(
600 peer_hostname(IpAddr::V6("fd00::1".parse().unwrap())),
601 "node-0001"
602 );
603 assert_eq!(
604 peer_hostname(IpAddr::V6("fd00::abcd".parse().unwrap())),
605 "node-abcd"
606 );
607 assert_eq!(
608 peer_hostname(IpAddr::V6("fd00:200::ffff".parse().unwrap())),
609 "node-ffff"
610 );
611 assert_eq!(
613 peer_hostname(IpAddr::V6("fd00::1:0".parse().unwrap())),
614 "node-0000"
615 );
616 }
617
618 #[test]
619 fn test_dns_config() {
620 let config = DnsConfig::new("overlay.local.", IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1)));
621 assert_eq!(config.zone, "overlay.local.");
622 assert_eq!(config.port, DEFAULT_DNS_PORT);
623 assert_eq!(config.bind_addr, IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1)));
624
625 let config = config.with_port(5353);
627 assert_eq!(config.port, 5353);
628 }
629
630 #[test]
631 fn test_dns_config_serialization() {
632 let config = DnsConfig::new("overlay.local.", IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1)))
633 .with_port(15353);
634
635 let json = serde_json::to_string(&config).unwrap();
636 let deserialized: DnsConfig = serde_json::from_str(&json).unwrap();
637
638 assert_eq!(deserialized.zone, config.zone);
639 assert_eq!(deserialized.port, config.port);
640 assert_eq!(deserialized.bind_addr, config.bind_addr);
641 }
642
643 #[tokio::test]
644 async fn test_service_discovery_local_cache() {
645 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
647 let discovery = ServiceDiscovery::new(addr);
648
649 let ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2));
650 discovery.register("test-service", ip).await;
651
652 let resolved = discovery.resolve("test-service").await;
653 assert_eq!(resolved, Some(ip));
654
655 discovery.unregister("test-service").await;
657 let services = discovery.list_services().await;
658 assert!(services.is_empty());
659 }
660
661 #[test]
662 fn test_dns_server_creation() {
663 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
664 let server = DnsServer::new(addr, "overlay.local.");
665
666 assert!(server.is_ok());
667 let server = server.unwrap();
668 assert_eq!(server.listen_addr(), addr);
669 assert_eq!(server.zone_origin().to_string(), "overlay.local.");
670 }
671
672 #[test]
673 fn test_dns_server_from_config() {
674 let config =
675 DnsConfig::new("test.local.", IpAddr::V4(Ipv4Addr::LOCALHOST)).with_port(15353);
676 let server = DnsServer::from_config(&config);
677
678 assert!(server.is_ok());
679 let server = server.unwrap();
680 assert_eq!(server.listen_addr().port(), 15353);
681 assert_eq!(server.zone_origin().to_string(), "test.local.");
682 }
683
684 #[test]
685 fn test_dns_server_invalid_zone() {
686 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
687 let server = DnsServer::new(addr, "overlay.local.");
689 assert!(server.is_ok());
690 }
691
692 #[tokio::test]
693 async fn test_dns_server_add_record() {
694 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
695 let server = DnsServer::new(addr, "overlay.local.").unwrap();
696
697 let result = server
698 .add_record("myservice", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)))
699 .await;
700 assert!(result.is_ok());
701 }
702
703 #[tokio::test]
704 async fn test_dns_handle_add_record() {
705 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
706 let server = DnsServer::new(addr, "overlay.local.").unwrap();
707
708 let handle = server.handle();
710
711 let result = handle
712 .add_record("service1", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))
713 .await;
714 assert!(result.is_ok());
715
716 let result = handle
717 .add_record("service2", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)))
718 .await;
719 assert!(result.is_ok());
720
721 assert_eq!(handle.zone_origin().to_string(), "overlay.local.");
723 }
724
725 #[test]
726 fn test_dns_client_creation() {
727 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 53);
728 let client = DnsClient::new(addr);
729 assert_eq!(client.server_addr, addr);
730 }
731
732 #[tokio::test]
733 async fn test_dns_handle_add_aaaa_record() {
734 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
735 let server = DnsServer::new(addr, "overlay.local.").unwrap();
736 let handle = server.handle();
737
738 let ipv6: IpAddr = "fd00::1".parse().unwrap();
740 let result = handle.add_record("service-v6", ipv6).await;
741 assert!(result.is_ok());
742
743 let ipv6_2: IpAddr = "fd00::abcd".parse().unwrap();
745 let result = handle.add_record("service-v6-2", ipv6_2).await;
746 assert!(result.is_ok());
747 }
748
749 #[tokio::test]
750 async fn test_dns_server_add_aaaa_record() {
751 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
752 let server = DnsServer::new(addr, "overlay.local.").unwrap();
753
754 let ipv6: IpAddr = "fd00::42".parse().unwrap();
756 let result = server.add_record("myservice-v6", ipv6).await;
757 assert!(result.is_ok());
758 }
759
760 #[tokio::test]
761 async fn test_dns_handle_remove_record_covers_both_types() {
762 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
763 let server = DnsServer::new(addr, "overlay.local.").unwrap();
764 let handle = server.handle();
765
766 let ipv4 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
768 handle.add_record("dual-service", ipv4).await.unwrap();
769
770 let removed = handle.remove_record("dual-service").await.unwrap();
772 assert!(removed);
773
774 let ipv6: IpAddr = "fd00::1".parse().unwrap();
776 handle.add_record("v6-service", ipv6).await.unwrap();
777
778 let removed = handle.remove_record("v6-service").await.unwrap();
780 assert!(removed);
781 }
782
783 #[tokio::test]
784 async fn test_service_discovery_local_cache_ipv6() {
785 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
786 let discovery = ServiceDiscovery::new(addr);
787
788 let ipv6: IpAddr = "fd00::beef".parse().unwrap();
790 discovery.register("v6-service", ipv6).await;
791
792 let resolved = discovery.resolve("v6-service").await;
794 assert_eq!(resolved, Some(ipv6));
795
796 discovery.unregister("v6-service").await;
798 let services = discovery.list_services().await;
799 assert!(services.is_empty());
800 }
801
802 #[tokio::test]
803 async fn test_service_discovery_mixed_v4_v6_cache() {
804 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
805 let discovery = ServiceDiscovery::new(addr);
806
807 let ipv4 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
808 let ipv6: IpAddr = "fd00::1".parse().unwrap();
809
810 discovery.register("svc-v4", ipv4).await;
811 discovery.register("svc-v6", ipv6).await;
812
813 assert_eq!(discovery.resolve("svc-v4").await, Some(ipv4));
814 assert_eq!(discovery.resolve("svc-v6").await, Some(ipv6));
815
816 let mut services = discovery.list_services().await;
817 services.sort();
818 assert_eq!(services, vec!["svc-v4", "svc-v6"]);
819 }
820
821 #[test]
822 fn test_dns_config_with_ipv6_bind_addr() {
823 let ipv6_bind: IpAddr = "fd00::1".parse().unwrap();
824 let config = DnsConfig::new("overlay.local.", ipv6_bind);
825 assert_eq!(config.bind_addr, ipv6_bind);
826 assert_eq!(config.port, DEFAULT_DNS_PORT);
827
828 let json = serde_json::to_string(&config).unwrap();
830 let deserialized: DnsConfig = serde_json::from_str(&json).unwrap();
831 assert_eq!(deserialized.bind_addr, ipv6_bind);
832 }
833
834 #[test]
835 fn test_dns_server_creation_ipv6_bind() {
836 let ipv6_addr: IpAddr = "::1".parse().unwrap();
837 let addr = SocketAddr::new(ipv6_addr, 15353);
838 let server = DnsServer::new(addr, "overlay.local.");
839
840 assert!(server.is_ok());
841 let server = server.unwrap();
842 assert_eq!(server.listen_addr(), addr);
843 }
844
845 #[tokio::test]
852 async fn test_bind_windows_fallback_errors_or_shares_authority() {
853 let primary = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
854 let server = DnsServer::new(primary, "overlay.local.").unwrap();
855 let bind_ip: IpAddr = "127.0.0.2".parse().unwrap();
856
857 match server.bind_windows_fallback(bind_ip).await {
858 Ok(handle) => {
859 assert_eq!(handle.zone_origin().to_string(), "overlay.local.");
863 handle
864 .add_record("dual", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 9)))
865 .await
866 .expect("add_record via fallback handle");
867 }
868 Err(DnsError::Io(_)) => {
869 }
873 Err(other) => panic!("unexpected error from bind_windows_fallback: {other}"),
874 }
875 }
876
877 #[test]
878 fn test_peer_hostname_uniqueness() {
879 let v4_a = peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)));
881 let v4_b = peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)));
882 assert_ne!(v4_a, v4_b);
883
884 let v6_a = peer_hostname(IpAddr::V6("fd00::1".parse().unwrap()));
885 let v6_b = peer_hostname(IpAddr::V6("fd00::2".parse().unwrap()));
886 assert_ne!(v6_a, v6_b);
887
888 let v4 = peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)));
890 let v6 = peer_hostname(IpAddr::V6("fd00::1".parse().unwrap()));
891 assert_ne!(v4, v6);
892 }
893}