Skip to main content

Mist/
Server.rs

1#![allow(non_snake_case, 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;
13use hickory_server::{
14	authority::{Catalog, ZoneType},
15	server::ServerFuture,
16	store::in_memory::InMemoryAuthority,
17};
18use tokio::net::UdpSocket;
19
20/// Builds a DNS catalog for the CodeEditorLand private network.
21///
22/// Creates a catalog with an authoritative zone for `editor.land` that
23/// resolves all queries locally to loopback addresses.
24pub fn BuildCatalog(_DNSPort:u16) -> Result<Catalog> {
25	let mut Catalog = Catalog::new();
26
27	let EditorLandOrigin = hickory_proto::rr::Name::from_ascii("editor.land.").unwrap();
28
29	let Authority = InMemoryAuthority::empty(EditorLandOrigin.clone(), ZoneType::Primary, false, None);
30
31	let EditorLandLower = hickory_proto::rr::LowerName::from(&EditorLandOrigin);
32	let AuthorityArc = Arc::new(Authority);
33	Catalog.upsert(EditorLandLower, vec![AuthorityArc]);
34
35	Ok(Catalog)
36}
37
38/// Serves DNS queries on the specified loopback port (async).
39///
40/// Binds to `127.0.0.1:{Port}` for both UDP and TCP. Validates that the
41/// socket is bound to a loopback address before accepting connections.
42pub async fn Serve(Catalog:Catalog, Port:u16) -> Result<()> {
43	let Address:SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), Port);
44
45	let BindingIP = Address.ip();
46	match BindingIP {
47		IpAddr::V4(IP) => {
48			if !IP.is_loopback() {
49				return Err(anyhow::anyhow!(
50					"SECURITY: DNS server attempted to bind to non-loopback address: {}. Only 127.x.x.x addresses are \
51					 allowed.",
52					IP
53				));
54			}
55		},
56		IpAddr::V6(IP) if IP.is_loopback() => {},
57		_ => {
58			return Err(anyhow::anyhow!(
59				"SECURITY: DNS server attempted to bind to invalid address: {}. Only loopback addresses are allowed.",
60				BindingIP
61			));
62		},
63	}
64
65	tracing::info!("Binding DNS server to loopback address: {}", Address);
66
67	let UDPSocket = UdpSocket::bind(Address)
68		.await
69		.map_err(|E| anyhow::anyhow!("SECURITY: Failed to bind DNS server to {}: {}.", Address, E))?;
70
71	let BoundAddress = UDPSocket
72		.local_addr()
73		.map_err(|E| anyhow::anyhow!("SECURITY: Failed to retrieve bound socket address: {}", E))?;
74
75	if !BoundAddress.ip().is_loopback() {
76		return Err(anyhow::anyhow!(
77			"SECURITY: UDP socket bound to non-loopback address: {}.",
78			BoundAddress.ip()
79		));
80	}
81
82	let mut Server = ServerFuture::new(Catalog);
83	Server.register_socket(UDPSocket);
84
85	let TCPListener = tokio::net::TcpListener::bind(Address)
86		.await
87		.map_err(|E| anyhow::anyhow!("SECURITY: Failed to bind TCP listener to {}: {}", Address, E))?;
88
89	let TCPBoundAddress = TCPListener
90		.local_addr()
91		.map_err(|E| anyhow::anyhow!("SECURITY: Failed to retrieve TCP listener bound address: {}", E))?;
92
93	if !TCPBoundAddress.ip().is_loopback() {
94		return Err(anyhow::anyhow!(
95			"SECURITY: TCP listener bound to non-loopback address: {}.",
96			TCPBoundAddress.ip()
97		));
98	}
99
100	Server.register_listener(TCPListener, std::time::Duration::from_secs(5));
101
102	tracing::info!("DNS server bound to loopback: UDP={}, TCP={}", BoundAddress, TCPBoundAddress);
103
104	match Server.block_until_done().await {
105		Ok(_) => {
106			tracing::info!("DNS server shutdown gracefully");
107			Ok(())
108		},
109		Err(E) => {
110			let ErrorMessage = format!("DNS server error: {:?}", E);
111			tracing::error!("{}", ErrorMessage);
112			Err(anyhow::anyhow!(ErrorMessage))
113		},
114	}
115}
116
117/// Serves DNS queries synchronously (blocking convenience wrapper).
118pub fn ServeSync(Catalog:Catalog, Port:u16) -> Result<()> {
119	let Runtime = tokio::runtime::Runtime::new()?;
120	Runtime.block_on(Serve(Catalog, Port))?;
121	Ok(())
122}
123
124#[cfg(test)]
125mod tests {
126	use hickory_proto::rr::Name;
127
128	use super::*;
129
130	#[test]
131	fn TestBuildCatalog() { let _Catalog = BuildCatalog(5353).expect("Failed to build catalog"); }
132
133	#[test]
134	fn TestSocketAddressIsLoopback() {
135		let Address:SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5353);
136		assert!(Address.ip().is_loopback());
137	}
138}