Skip to main content

Mist/
Server.rs

1#![allow(non_camel_case_types, non_upper_case_globals)]
2//! # DNS Server
3//!
4//! Builds and serves the private DNS catalog for CodeEditorLand.
5//! Binds exclusively to loopback (`127.0.0.1`) to prevent LAN exposure.
6
7use std::{
8	net::{IpAddr, Ipv4Addr, SocketAddr},
9	sync::Arc,
10};
11
12use anyhow::Result;
13// hickory-server 0.26 reorganisation:
14//   authority::*                       → zone_handler::*
15//   authority::Authority (trait)       → zone_handler::ZoneHandler
16//   authority::Catalog / ZoneType      → zone_handler::Catalog / ZoneType
17//   store::in_memory::InMemoryAuthority → store::in_memory::InMemoryZoneHandler
18//   server::ServerFuture               → Server (re-exported at crate root)
19//   InMemoryAuthority::empty(_,_,bool,_) → InMemoryZoneHandler::empty(_,_,AxfrPolicy,_)
20// The behaviour is unchanged; only names moved.
21use 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
29/// Buffer capacity for outgoing DNS TCP responses per connection. 65 535 is
30/// the upper bound a single DNS message can reach over TCP (the 16-bit
31/// length prefix cap from RFC 1035 §4.2.2). Picking the cap avoids any
32/// truncation for zone-transfer or large TXT responses while staying well
33/// within memory for the dozen-or-so concurrent connections a local
34/// `editor.land` catalog ever sees.
35const DNS_TCP_RESPONSE_BUFFER_SIZE:usize = 65_535;
36
37/// Builds a DNS catalog for the CodeEditorLand private network.
38///
39/// Creates a catalog with an authoritative zone for `editor.land` that
40/// resolves all queries locally to loopback addresses.
41pub 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	// `AxfrPolicy::Deny` replaces the old `false` bool that disabled AXFR.
47	// The trailing `None` is `Option<NxProofKind>` and remains dnssec-ring-gated.
48	// Turbofish pins the runtime provider so inference has a concrete type
49	// (the handler is generic over `P: RuntimeProvider`; there's no
50	// inference anchor without either an `.await`-driven callsite or an
51	// explicit parameter here).
52	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
68/// Serves DNS queries on the specified loopback port (async).
69///
70/// Binds to `127.0.0.1:{Port}` for both UDP and TCP. Validates that the
71/// socket is bound to a loopback address before accepting connections.
72pub 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	// `Server` supersedes `ServerFuture`; constructor + register_* +
116	// block_until_done signatures are the same so the rest of this body is
117	// unchanged.
118	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
158/// Serves DNS queries synchronously (blocking convenience wrapper).
159pub 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}