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 self.bind_secondary(SocketAddr::new(bind_ip, 53)).await
344 }
345
346 #[allow(clippy::unused_async)]
361 pub async fn bind_secondary(&self, listen_addr: SocketAddr) -> Result<DnsHandle, DnsError> {
362 let handle = self.handle();
363 let zone_origin = self.zone_origin.clone();
364 let authority = Arc::clone(&self.authority);
365
366 let udp_socket = UdpSocket::bind(listen_addr).await?;
370 let tcp_listener = TcpListener::bind(listen_addr).await?;
371
372 tokio::spawn(async move {
373 let mut catalog = Catalog::new();
374 catalog.upsert(zone_origin.into(), Box::new(authority));
375 let mut server = ServerFuture::new(catalog);
376 server.register_socket(udp_socket);
377 server.register_listener(tcp_listener, Duration::from_secs(30));
378 tracing::info!(
379 addr = %listen_addr,
380 "secondary DNS listener started",
381 );
382 if let Err(e) = server.block_until_done().await {
383 tracing::error!("secondary DNS listener error: {}", e);
384 }
385 });
386
387 Ok(handle)
388 }
389
390 async fn run_server(
392 listen_addr: SocketAddr,
393 zone_origin: Name,
394 authority: Arc<InMemoryAuthority>,
395 ) -> Result<(), DnsError> {
396 let mut catalog = Catalog::new();
398
399 catalog.upsert(zone_origin.into(), Box::new(authority));
401
402 let mut server = ServerFuture::new(catalog);
404
405 let udp_socket = UdpSocket::bind(listen_addr).await?;
407 server.register_socket(udp_socket);
408
409 let tcp_listener = TcpListener::bind(listen_addr).await?;
411 server.register_listener(tcp_listener, Duration::from_secs(30));
412
413 tracing::info!(addr = %listen_addr, "DNS server listening");
414
415 server
417 .block_until_done()
418 .await
419 .map_err(|e| DnsError::Server(e.to_string()))?;
420
421 Ok(())
422 }
423
424 #[must_use]
426 pub fn listen_addr(&self) -> SocketAddr {
427 self.listen_addr
428 }
429
430 #[must_use]
432 pub fn zone_origin(&self) -> &Name {
433 &self.zone_origin
434 }
435}
436
437pub struct DnsClient {
439 server_addr: SocketAddr,
440}
441
442impl DnsClient {
443 #[must_use]
445 pub fn new(server_addr: SocketAddr) -> Self {
446 Self { server_addr }
447 }
448
449 pub fn query_a(&self, hostname: &str) -> Result<Option<Ipv4Addr>, DnsError> {
455 let name = Name::from_str(hostname)
456 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?;
457
458 let conn = UdpClientConnection::new(self.server_addr)
459 .map_err(|e| DnsError::Client(e.to_string()))?;
460
461 let client = SyncClient::new(conn);
462
463 let response = client
464 .query(&name, DNSClass::IN, RecordType::A)
465 .map_err(|e| DnsError::Client(e.to_string()))?;
466
467 for answer in response.answers() {
469 if let Some(RData::A(a_record)) = answer.data() {
470 return Ok(Some((*a_record).into()));
471 }
472 }
473
474 Ok(None)
475 }
476
477 pub fn query_aaaa(&self, hostname: &str) -> Result<Option<Ipv6Addr>, DnsError> {
483 let name = Name::from_str(hostname)
484 .map_err(|e| DnsError::InvalidName(format!("{hostname}: {e}")))?;
485
486 let conn = UdpClientConnection::new(self.server_addr)
487 .map_err(|e| DnsError::Client(e.to_string()))?;
488
489 let client = SyncClient::new(conn);
490
491 let response = client
492 .query(&name, DNSClass::IN, RecordType::AAAA)
493 .map_err(|e| DnsError::Client(e.to_string()))?;
494
495 for answer in response.answers() {
497 if let Some(RData::AAAA(aaaa_record)) = answer.data() {
498 return Ok(Some((*aaaa_record).into()));
499 }
500 }
501
502 Ok(None)
503 }
504
505 pub fn query_addr(&self, hostname: &str) -> Result<Option<IpAddr>, DnsError> {
513 if let Ok(Some(v4)) = self.query_a(hostname) {
515 return Ok(Some(IpAddr::V4(v4)));
516 }
517
518 if let Ok(Some(v6)) = self.query_aaaa(hostname) {
520 return Ok(Some(IpAddr::V6(v6)));
521 }
522
523 Ok(None)
524 }
525}
526
527pub struct ServiceDiscovery {
529 dns_server: SocketAddr,
530 records: RwLock<HashMap<String, IpAddr>>,
531}
532
533impl ServiceDiscovery {
534 #[must_use]
536 pub fn new(dns_server_addr: SocketAddr) -> Self {
537 Self {
538 dns_server: dns_server_addr,
539 records: RwLock::new(HashMap::new()),
540 }
541 }
542
543 pub async fn register(&self, name: &str, ip: IpAddr) {
545 let mut records = self.records.write().await;
546 records.insert(name.to_string(), ip);
547 }
548
549 pub async fn resolve(&self, name: &str) -> Option<IpAddr> {
554 {
556 let records = self.records.read().await;
557 if let Some(ip) = records.get(name) {
558 return Some(*ip);
559 }
560 }
561
562 let client = DnsClient::new(self.dns_server);
564 if let Ok(Some(addr)) = client.query_addr(name) {
565 return Some(addr);
566 }
567
568 None
569 }
570
571 pub async fn unregister(&self, name: &str) {
573 let mut records = self.records.write().await;
574 records.remove(name);
575 }
576
577 pub async fn list_services(&self) -> Vec<String> {
579 let records = self.records.read().await;
580 records.keys().cloned().collect()
581 }
582
583 pub fn dns_server(&self) -> SocketAddr {
585 self.dns_server
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 #[test]
594 fn test_peer_hostname_v4() {
595 assert_eq!(
597 peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1))),
598 "node-0-1"
599 );
600 assert_eq!(
601 peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 200, 0, 5))),
602 "node-0-5"
603 );
604 assert_eq!(
605 peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 200, 1, 100))),
606 "node-1-100"
607 );
608 assert_eq!(
609 peer_hostname(IpAddr::V4(Ipv4Addr::new(192, 168, 255, 254))),
610 "node-255-254"
611 );
612 }
613
614 #[test]
615 fn test_peer_hostname_v6() {
616 assert_eq!(
618 peer_hostname(IpAddr::V6("fd00::1".parse().unwrap())),
619 "node-0001"
620 );
621 assert_eq!(
622 peer_hostname(IpAddr::V6("fd00::abcd".parse().unwrap())),
623 "node-abcd"
624 );
625 assert_eq!(
626 peer_hostname(IpAddr::V6("fd00:200::ffff".parse().unwrap())),
627 "node-ffff"
628 );
629 assert_eq!(
631 peer_hostname(IpAddr::V6("fd00::1:0".parse().unwrap())),
632 "node-0000"
633 );
634 }
635
636 #[test]
637 fn test_dns_config() {
638 let config = DnsConfig::new("overlay.local.", IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1)));
639 assert_eq!(config.zone, "overlay.local.");
640 assert_eq!(config.port, DEFAULT_DNS_PORT);
641 assert_eq!(config.bind_addr, IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1)));
642
643 let config = config.with_port(5353);
645 assert_eq!(config.port, 5353);
646 }
647
648 #[test]
649 fn test_dns_config_serialization() {
650 let config = DnsConfig::new("overlay.local.", IpAddr::V4(Ipv4Addr::new(10, 200, 0, 1)))
651 .with_port(15353);
652
653 let json = serde_json::to_string(&config).unwrap();
654 let deserialized: DnsConfig = serde_json::from_str(&json).unwrap();
655
656 assert_eq!(deserialized.zone, config.zone);
657 assert_eq!(deserialized.port, config.port);
658 assert_eq!(deserialized.bind_addr, config.bind_addr);
659 }
660
661 #[tokio::test]
662 async fn test_service_discovery_local_cache() {
663 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
665 let discovery = ServiceDiscovery::new(addr);
666
667 let ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2));
668 discovery.register("test-service", ip).await;
669
670 let resolved = discovery.resolve("test-service").await;
671 assert_eq!(resolved, Some(ip));
672
673 discovery.unregister("test-service").await;
675 let services = discovery.list_services().await;
676 assert!(services.is_empty());
677 }
678
679 #[test]
680 fn test_dns_server_creation() {
681 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
682 let server = DnsServer::new(addr, "overlay.local.");
683
684 assert!(server.is_ok());
685 let server = server.unwrap();
686 assert_eq!(server.listen_addr(), addr);
687 assert_eq!(server.zone_origin().to_string(), "overlay.local.");
688 }
689
690 #[test]
691 fn test_dns_server_from_config() {
692 let config =
693 DnsConfig::new("test.local.", IpAddr::V4(Ipv4Addr::LOCALHOST)).with_port(15353);
694 let server = DnsServer::from_config(&config);
695
696 assert!(server.is_ok());
697 let server = server.unwrap();
698 assert_eq!(server.listen_addr().port(), 15353);
699 assert_eq!(server.zone_origin().to_string(), "test.local.");
700 }
701
702 #[test]
703 fn test_dns_server_invalid_zone() {
704 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
705 let server = DnsServer::new(addr, "overlay.local.");
707 assert!(server.is_ok());
708 }
709
710 #[tokio::test]
711 async fn test_dns_server_add_record() {
712 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
713 let server = DnsServer::new(addr, "overlay.local.").unwrap();
714
715 let result = server
716 .add_record("myservice", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)))
717 .await;
718 assert!(result.is_ok());
719 }
720
721 #[tokio::test]
722 async fn test_dns_handle_add_record() {
723 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
724 let server = DnsServer::new(addr, "overlay.local.").unwrap();
725
726 let handle = server.handle();
728
729 let result = handle
730 .add_record("service1", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))
731 .await;
732 assert!(result.is_ok());
733
734 let result = handle
735 .add_record("service2", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)))
736 .await;
737 assert!(result.is_ok());
738
739 assert_eq!(handle.zone_origin().to_string(), "overlay.local.");
741 }
742
743 #[test]
744 fn test_dns_client_creation() {
745 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 53);
746 let client = DnsClient::new(addr);
747 assert_eq!(client.server_addr, addr);
748 }
749
750 #[tokio::test]
751 async fn test_dns_handle_add_aaaa_record() {
752 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
753 let server = DnsServer::new(addr, "overlay.local.").unwrap();
754 let handle = server.handle();
755
756 let ipv6: IpAddr = "fd00::1".parse().unwrap();
758 let result = handle.add_record("service-v6", ipv6).await;
759 assert!(result.is_ok());
760
761 let ipv6_2: IpAddr = "fd00::abcd".parse().unwrap();
763 let result = handle.add_record("service-v6-2", ipv6_2).await;
764 assert!(result.is_ok());
765 }
766
767 #[tokio::test]
768 async fn test_dns_server_add_aaaa_record() {
769 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
770 let server = DnsServer::new(addr, "overlay.local.").unwrap();
771
772 let ipv6: IpAddr = "fd00::42".parse().unwrap();
774 let result = server.add_record("myservice-v6", ipv6).await;
775 assert!(result.is_ok());
776 }
777
778 #[tokio::test]
779 async fn test_dns_handle_remove_record_covers_both_types() {
780 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
781 let server = DnsServer::new(addr, "overlay.local.").unwrap();
782 let handle = server.handle();
783
784 let ipv4 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
786 handle.add_record("dual-service", ipv4).await.unwrap();
787
788 let removed = handle.remove_record("dual-service").await.unwrap();
790 assert!(removed);
791
792 let ipv6: IpAddr = "fd00::1".parse().unwrap();
794 handle.add_record("v6-service", ipv6).await.unwrap();
795
796 let removed = handle.remove_record("v6-service").await.unwrap();
798 assert!(removed);
799 }
800
801 #[tokio::test]
802 async fn test_service_discovery_local_cache_ipv6() {
803 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
804 let discovery = ServiceDiscovery::new(addr);
805
806 let ipv6: IpAddr = "fd00::beef".parse().unwrap();
808 discovery.register("v6-service", ipv6).await;
809
810 let resolved = discovery.resolve("v6-service").await;
812 assert_eq!(resolved, Some(ipv6));
813
814 discovery.unregister("v6-service").await;
816 let services = discovery.list_services().await;
817 assert!(services.is_empty());
818 }
819
820 #[tokio::test]
821 async fn test_service_discovery_mixed_v4_v6_cache() {
822 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 15353);
823 let discovery = ServiceDiscovery::new(addr);
824
825 let ipv4 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
826 let ipv6: IpAddr = "fd00::1".parse().unwrap();
827
828 discovery.register("svc-v4", ipv4).await;
829 discovery.register("svc-v6", ipv6).await;
830
831 assert_eq!(discovery.resolve("svc-v4").await, Some(ipv4));
832 assert_eq!(discovery.resolve("svc-v6").await, Some(ipv6));
833
834 let mut services = discovery.list_services().await;
835 services.sort();
836 assert_eq!(services, vec!["svc-v4", "svc-v6"]);
837 }
838
839 #[test]
840 fn test_dns_config_with_ipv6_bind_addr() {
841 let ipv6_bind: IpAddr = "fd00::1".parse().unwrap();
842 let config = DnsConfig::new("overlay.local.", ipv6_bind);
843 assert_eq!(config.bind_addr, ipv6_bind);
844 assert_eq!(config.port, DEFAULT_DNS_PORT);
845
846 let json = serde_json::to_string(&config).unwrap();
848 let deserialized: DnsConfig = serde_json::from_str(&json).unwrap();
849 assert_eq!(deserialized.bind_addr, ipv6_bind);
850 }
851
852 #[test]
853 fn test_dns_server_creation_ipv6_bind() {
854 let ipv6_addr: IpAddr = "::1".parse().unwrap();
855 let addr = SocketAddr::new(ipv6_addr, 15353);
856 let server = DnsServer::new(addr, "overlay.local.");
857
858 assert!(server.is_ok());
859 let server = server.unwrap();
860 assert_eq!(server.listen_addr(), addr);
861 }
862
863 #[tokio::test]
870 async fn test_bind_windows_fallback_errors_or_shares_authority() {
871 let primary = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
872 let server = DnsServer::new(primary, "overlay.local.").unwrap();
873 let bind_ip: IpAddr = "127.0.0.2".parse().unwrap();
874
875 match server.bind_windows_fallback(bind_ip).await {
876 Ok(handle) => {
877 assert_eq!(handle.zone_origin().to_string(), "overlay.local.");
881 handle
882 .add_record("dual", IpAddr::V4(Ipv4Addr::new(10, 0, 0, 9)))
883 .await
884 .expect("add_record via fallback handle");
885 }
886 Err(DnsError::Io(_)) => {
887 }
891 Err(other) => panic!("unexpected error from bind_windows_fallback: {other}"),
892 }
893 }
894
895 #[test]
896 fn test_peer_hostname_uniqueness() {
897 let v4_a = peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)));
899 let v4_b = peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)));
900 assert_ne!(v4_a, v4_b);
901
902 let v6_a = peer_hostname(IpAddr::V6("fd00::1".parse().unwrap()));
903 let v6_b = peer_hostname(IpAddr::V6("fd00::2".parse().unwrap()));
904 assert_ne!(v6_a, v6_b);
905
906 let v4 = peer_hostname(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)));
908 let v6 = peer_hostname(IpAddr::V6("fd00::1".parse().unwrap()));
909 assert_ne!(v4, v6);
910 }
911}