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