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