azalea_protocol/
resolver.rs

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