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