1use std::{
7 net::{IpAddr, Ipv4Addr, SocketAddr},
8 sync::Arc,
9};
10
11use anyhow::Result;
12use hickory_server::{
21 Server,
22 net::runtime::TokioRuntimeProvider,
23 store::in_memory::InMemoryZoneHandler,
24 zone_handler::{AxfrPolicy, Catalog, ZoneType},
25};
26use tokio::net::UdpSocket;
27
28const DNS_TCP_RESPONSE_BUFFER_SIZE:usize = 65_535;
35
36pub fn BuildCatalog(_DNSPort:u16) -> Result<Catalog> {
41 let mut Catalog = Catalog::new();
42
43 let EditorLandOrigin = hickory_proto::rr::Name::from_ascii("land.playform.cloud.").unwrap();
44
45 let Authority = InMemoryZoneHandler::<TokioRuntimeProvider>::empty(
52 EditorLandOrigin.clone(),
53 ZoneType::Primary,
54 AxfrPolicy::Deny,
55 None,
56 );
57
58 let EditorLandLower = hickory_proto::rr::LowerName::from(&EditorLandOrigin);
59
60 let AuthorityArc = Arc::new(Authority);
61
62 Catalog.upsert(EditorLandLower, vec![AuthorityArc]);
63
64 Ok(Catalog)
65}
66
67pub async fn Serve(Catalog:Catalog, Port:u16) -> Result<()> {
72 let Address:SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), Port);
73
74 let BindingIP = Address.ip();
75
76 match BindingIP {
77 IpAddr::V4(IP) => {
78 if !IP.is_loopback() {
79 return Err(anyhow::anyhow!(
80 "SECURITY: DNS server attempted to bind to non-loopback address: {}. Only 127.x.x.x addresses are \
81 allowed.",
82 IP
83 ));
84 }
85 },
86
87 IpAddr::V6(IP) if IP.is_loopback() => {},
88
89 _ => {
90 return Err(anyhow::anyhow!(
91 "SECURITY: DNS server attempted to bind to invalid address: {}. Only loopback addresses are allowed.",
92 BindingIP
93 ));
94 },
95 }
96
97 tracing::info!("Binding DNS server to loopback address: {}", Address);
98
99 let UDPSocket = UdpSocket::bind(Address)
100 .await
101 .map_err(|E| anyhow::anyhow!("SECURITY: Failed to bind DNS server to {}: {}.", Address, E))?;
102
103 let BoundAddress = UDPSocket
104 .local_addr()
105 .map_err(|E| anyhow::anyhow!("SECURITY: Failed to retrieve bound socket address: {}", E))?;
106
107 if !BoundAddress.ip().is_loopback() {
108 return Err(anyhow::anyhow!(
109 "SECURITY: UDP socket bound to non-loopback address: {}.",
110 BoundAddress.ip()
111 ));
112 }
113
114 let mut Server = Server::new(Catalog);
118
119 Server.register_socket(UDPSocket);
120
121 let TCPListener = tokio::net::TcpListener::bind(Address)
122 .await
123 .map_err(|E| anyhow::anyhow!("SECURITY: Failed to bind TCP listener to {}: {}", Address, E))?;
124
125 let TCPBoundAddress = TCPListener
126 .local_addr()
127 .map_err(|E| anyhow::anyhow!("SECURITY: Failed to retrieve TCP listener bound address: {}", E))?;
128
129 if !TCPBoundAddress.ip().is_loopback() {
130 return Err(anyhow::anyhow!(
131 "SECURITY: TCP listener bound to non-loopback address: {}.",
132 TCPBoundAddress.ip()
133 ));
134 }
135
136 Server.register_listener(TCPListener, std::time::Duration::from_secs(5), DNS_TCP_RESPONSE_BUFFER_SIZE);
137
138 tracing::info!("DNS server bound to loopback: UDP={}, TCP={}", BoundAddress, TCPBoundAddress);
139
140 match Server.block_until_done().await {
141 Ok(_) => {
142 tracing::info!("DNS server shutdown gracefully");
143
144 Ok(())
145 },
146
147 Err(E) => {
148 let ErrorMessage = format!("DNS server error: {:?}", E);
149
150 tracing::error!("{}", ErrorMessage);
151
152 Err(anyhow::anyhow!(ErrorMessage))
153 },
154 }
155}
156
157pub fn ServeSync(Catalog:Catalog, Port:u16) -> Result<()> {
159 let Runtime = tokio::runtime::Runtime::new()?;
160
161 Runtime.block_on(Serve(Catalog, Port))?;
162
163 Ok(())
164}
165
166#[cfg(test)]
167mod tests {
168
169 use hickory_proto::rr::Name;
170
171 use super::*;
172
173 #[test]
174 fn TestBuildCatalog() { let _Catalog = BuildCatalog(5353).expect("Failed to build catalog"); }
175
176 #[test]
177 fn TestSocketAddressIsLoopback() {
178 let Address:SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5353);
179
180 assert!(Address.ip().is_loopback());
181 }
182}