Skip to main content

azalea_protocol/
resolve.rs

1//! Resolve a Minecraft server address into an IP address and port.
2
3use std::{
4    net::{IpAddr, SocketAddr},
5    sync::LazyLock,
6};
7
8pub use hickory_resolver::ResolveError;
9use hickory_resolver::{
10    Name, TokioResolver, config::ResolverConfig, name_server::TokioConnectionProvider,
11};
12use tracing::warn;
13
14use crate::address::ServerAddr;
15
16#[doc(hidden)]
17#[deprecated(note = "Renamed to ResolveError")]
18pub type ResolverError = ResolveError;
19
20static RESOLVER: LazyLock<TokioResolver> = LazyLock::new(|| {
21    TokioResolver::builder(TokioConnectionProvider::default())
22        .unwrap_or_else(|_| {
23            warn!("System DNS resolver unavailable; falling back to Google DNS.");
24            TokioResolver::builder_with_config(
25                ResolverConfig::google(),
26                TokioConnectionProvider::default(),
27            )
28        })
29        .build()
30});
31
32/// Resolve a Minecraft server address into an IP address and port.
33///
34/// If it's already an IP address, it's returned as-is.
35pub async fn resolve_address(mut address: &ServerAddr) -> Result<SocketAddr, ResolveError> {
36    let redirect = resolve_srv_redirect(address).await;
37    if let Ok(redirect_target) = &redirect {
38        address = redirect_target;
39    }
40
41    resolve_ip_without_redirects(address).await
42}
43
44async fn resolve_ip_without_redirects(address: &ServerAddr) -> Result<SocketAddr, ResolveError> {
45    if let Ok(ip) = address.host.parse::<IpAddr>() {
46        // no need to do a lookup
47        return Ok(SocketAddr::new(ip, address.port));
48    }
49
50    let name = Name::from_ascii(&address.host)?;
51    let lookup_ip = RESOLVER.lookup_ip(name).await?;
52
53    let ip = lookup_ip
54        .iter()
55        .next()
56        .ok_or(hickory_resolver::ResolveError::from(
57            "No A/AAAA record found",
58        ))?;
59
60    Ok(SocketAddr::new(ip, address.port))
61}
62
63async fn resolve_srv_redirect(address: &ServerAddr) -> Result<ServerAddr, ResolveError> {
64    if address.port != 25565 {
65        return Err(ResolveError::from("Port must be 25565 to do a SRV lookup"));
66    }
67
68    let query = format!("_minecraft._tcp.{}", address.host);
69    let res = RESOLVER.srv_lookup(query).await?;
70
71    let srv = res
72        .iter()
73        .next()
74        .ok_or(ResolveError::from("No SRV record found"))?;
75    Ok(ServerAddr {
76        host: srv.target().to_ascii(),
77        port: srv.port(),
78    })
79}