azalea_client/
ping.rs

1//! Ping Minecraft servers.
2
3use std::io;
4
5use azalea_protocol::{
6    ServerAddress,
7    connect::{Connection, ConnectionError, Proxy},
8    packets::{
9        ClientIntention, PROTOCOL_VERSION,
10        handshake::{
11            ClientboundHandshakePacket, ServerboundHandshakePacket,
12            s_intention::ServerboundIntention,
13        },
14        status::{
15            ClientboundStatusPacket, c_status_response::ClientboundStatusResponse,
16            s_status_request::ServerboundStatusRequest,
17        },
18    },
19    resolver,
20};
21use thiserror::Error;
22
23#[derive(Error, Debug)]
24pub enum PingError {
25    #[error("{0}")]
26    Resolver(#[from] resolver::ResolverError),
27    #[error("{0}")]
28    Connection(#[from] ConnectionError),
29    #[error("{0}")]
30    ReadPacket(#[from] Box<azalea_protocol::read::ReadPacketError>),
31    #[error("{0}")]
32    WritePacket(#[from] io::Error),
33    #[error("The given address could not be parsed into a ServerAddress")]
34    InvalidAddress,
35}
36
37/// Ping a Minecraft server.
38///
39/// # Examples
40///
41/// ```rust,no_run
42/// use azalea_client::ping;
43///
44/// #[tokio::main]
45/// async fn main() {
46///     let response = ping::ping_server("play.hypixel.net").await.unwrap();
47///     println!("{}", response.description.to_ansi());
48/// }
49/// ```
50pub async fn ping_server(
51    address: impl TryInto<ServerAddress>,
52) -> Result<ClientboundStatusResponse, PingError> {
53    let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?;
54    let resolved_address = resolver::resolve_address(&address).await?;
55    let conn = Connection::new(&resolved_address).await?;
56    ping_server_with_connection(address, conn).await
57}
58
59/// Ping a Minecraft server through a Socks5 proxy.
60pub async fn ping_server_with_proxy(
61    address: impl TryInto<ServerAddress>,
62    proxy: Proxy,
63) -> Result<ClientboundStatusResponse, PingError> {
64    let address: ServerAddress = address.try_into().map_err(|_| PingError::InvalidAddress)?;
65    let resolved_address = resolver::resolve_address(&address).await?;
66    let conn = Connection::new_with_proxy(&resolved_address, proxy).await?;
67    ping_server_with_connection(address, conn).await
68}
69
70/// Ping a Minecraft server after we've already created a [`Connection`]. The
71/// `Connection` must still be in the handshake state (which is the state it's
72/// in immediately after it's created).
73pub async fn ping_server_with_connection(
74    address: ServerAddress,
75    mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
76) -> Result<ClientboundStatusResponse, PingError> {
77    // send the client intention packet and switch to the status state
78    conn.write(ServerboundIntention {
79        protocol_version: PROTOCOL_VERSION,
80        hostname: address.host.clone(),
81        port: address.port,
82        intention: ClientIntention::Status,
83    })
84    .await?;
85    let mut conn = conn.status();
86
87    // send the empty status request packet
88    conn.write(ServerboundStatusRequest {}).await?;
89
90    let packet = conn.read().await?;
91
92    loop {
93        match packet {
94            ClientboundStatusPacket::StatusResponse(p) => return Ok(p),
95            ClientboundStatusPacket::PongResponse(_) => {
96                // we should never get this packet since we didn't send a ping
97            }
98        }
99    }
100}