azalea_client/
ping.rs

1//! Ping Minecraft servers.
2
3use std::io;
4
5use azalea_protocol::{
6    address::{ResolvableAddr, ServerAddr},
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    resolve,
20};
21use thiserror::Error;
22
23#[derive(Error, Debug)]
24pub enum PingError {
25    #[error("{0}")]
26    Resolve(#[from] resolve::ResolveError),
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 ResolvableAddr,
52) -> Result<ClientboundStatusResponse, PingError> {
53    let address = address.resolve().await?;
54    let conn = Connection::new(&address.socket).await?;
55    ping_server_with_connection(address.server, conn).await
56}
57
58/// Ping a Minecraft server through a SOCKS5 proxy.
59pub async fn ping_server_with_proxy(
60    address: impl ResolvableAddr,
61    proxy: Proxy,
62) -> Result<ClientboundStatusResponse, PingError> {
63    let address = address.resolve().await?;
64    let conn = Connection::new_with_proxy(&address.socket, proxy).await?;
65    ping_server_with_connection(address.server, conn).await
66}
67
68/// Ping a Minecraft server after we've already created a [`Connection`].
69///
70/// The `Connection` must still be in the handshake state (which is the state
71/// it's in immediately after it's created).
72pub async fn ping_server_with_connection(
73    address: ServerAddr,
74    mut conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket>,
75) -> Result<ClientboundStatusResponse, PingError> {
76    // send the client intention packet and switch to the status state
77    conn.write(ServerboundIntention {
78        protocol_version: PROTOCOL_VERSION,
79        hostname: address.host.clone(),
80        port: address.port,
81        intention: ClientIntention::Status,
82    })
83    .await?;
84    let mut conn = conn.status();
85
86    // send the empty status request packet
87    conn.write(ServerboundStatusRequest {}).await?;
88
89    let packet = conn.read().await?;
90
91    loop {
92        match packet {
93            ClientboundStatusPacket::StatusResponse(p) => return Ok(p),
94            ClientboundStatusPacket::PongResponse(_) => {
95                // we should never get this packet since we didn't send a ping
96            }
97        }
98    }
99}