azalea_protocol/
resolver.rs

1//! Resolve IPs from hostnames.
2
3use std::net::{IpAddr, SocketAddr};
4
5use async_recursion::async_recursion;
6use hickory_resolver::{
7    config::{ResolverConfig, ResolverOpts},
8    Name, TokioAsyncResolver,
9};
10use thiserror::Error;
11
12use crate::ServerAddress;
13
14#[derive(Error, Debug)]
15pub enum ResolverError {
16    #[error("No SRV record found")]
17    NoSrvRecord,
18    #[error("No IP found")]
19    NoIp,
20}
21
22/// Resolve a Minecraft server address into an IP address and port.
23/// If it's already an IP address, it's returned as-is.
24#[must_use]
25#[async_recursion]
26pub async fn resolve_address(address: &ServerAddress) -> Result<SocketAddr, ResolverError> {
27    // If the address.host is already in the format of an ip address, return it.
28    if let Ok(ip) = address.host.parse::<IpAddr>() {
29        return Ok(SocketAddr::new(ip, address.port));
30    }
31
32    // we specify Cloudflare instead of the default resolver because
33    // hickory_resolver has an issue on Windows where it's really slow using the
34    // default resolver
35    let resolver = TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), ResolverOpts::default());
36
37    // first, we do a srv lookup for _minecraft._tcp.<host>
38    let srv_redirect_result = resolver
39        .srv_lookup(format!("_minecraft._tcp.{}", address.host).as_str())
40        .await;
41
42    // if it resolves that means it's a redirect so we call resolve_address again
43    // with the new host
44    if let Ok(redirect_result) = srv_redirect_result {
45        let redirect_srv = redirect_result
46            .iter()
47            .next()
48            .ok_or(ResolverError::NoSrvRecord)?;
49        let redirect_address = ServerAddress {
50            host: redirect_srv.target().to_ascii(),
51            port: redirect_srv.port(),
52        };
53
54        if redirect_address.host == address.host {
55            let lookup_ip_result = resolver.lookup_ip(redirect_address.host).await;
56            let lookup_ip = lookup_ip_result.map_err(|_| ResolverError::NoIp)?;
57            return Ok(SocketAddr::new(
58                lookup_ip.iter().next().unwrap(),
59                redirect_address.port,
60            ));
61        }
62
63        return resolve_address(&redirect_address).await;
64    }
65
66    // there's no redirect, try to resolve this as an ip address
67    let name = Name::from_ascii(&address.host).map_err(|_| ResolverError::NoIp)?;
68    let lookup_ip_result = resolver.lookup_ip(name).await;
69    let lookup_ip = lookup_ip_result.map_err(|_| ResolverError::NoIp)?;
70
71    Ok(SocketAddr::new(
72        lookup_ip.iter().next().unwrap(),
73        address.port,
74    ))
75}