Switch to server status polling rather than pinging

This commit is contained in:
timvisee
2021-11-08 12:44:43 +01:00
parent 824c728e09
commit d4c0b4c146
4 changed files with 53 additions and 128 deletions

74
Cargo.lock generated
View File

@@ -181,17 +181,6 @@ dependencies = [
"pin-utils", "pin-utils",
] ]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@@ -227,7 +216,6 @@ dependencies = [
"log", "log",
"minecraft-protocol", "minecraft-protocol",
"pretty_env_logger", "pretty_env_logger",
"rand 0.8.4",
"tokio", "tokio",
] ]
@@ -261,7 +249,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]] [[package]]
name = "minecraft-protocol" name = "minecraft-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/timvisee/minecraft-protocol?rev=c578492#c57849246166add5ad45ef36b3bdebd8b744883d" source = "git+https://github.com/timvisee/minecraft-protocol?rev=c181117#c1811171c20a88bc7ecac1cda3257fbaa9f9c10c"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"minecraft-protocol-derive", "minecraft-protocol-derive",
@@ -274,7 +262,7 @@ dependencies = [
[[package]] [[package]]
name = "minecraft-protocol-derive" name = "minecraft-protocol-derive"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/timvisee/minecraft-protocol?rev=c578492#c57849246166add5ad45ef36b3bdebd8b744883d" source = "git+https://github.com/timvisee/minecraft-protocol?rev=c181117#c1811171c20a88bc7ecac1cda3257fbaa9f9c10c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -361,12 +349,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]] [[package]]
name = "pretty_env_logger" name = "pretty_env_logger"
version = "0.4.0" version = "0.4.0"
@@ -409,9 +391,9 @@ checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [ dependencies = [
"autocfg 0.1.7", "autocfg 0.1.7",
"libc", "libc",
"rand_chacha 0.1.1", "rand_chacha",
"rand_core 0.4.2", "rand_core 0.4.2",
"rand_hc 0.1.0", "rand_hc",
"rand_isaac", "rand_isaac",
"rand_jitter", "rand_jitter",
"rand_os", "rand_os",
@@ -420,18 +402,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.1.1" version = "0.1.1"
@@ -442,16 +412,6 @@ dependencies = [
"rand_core 0.3.1", "rand_core 0.3.1",
] ]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.3.1" version = "0.3.1"
@@ -467,15 +427,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "rand_hc" name = "rand_hc"
version = "0.1.0" version = "0.1.0"
@@ -485,15 +436,6 @@ dependencies = [
"rand_core 0.3.1", "rand_core 0.3.1",
] ]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]] [[package]]
name = "rand_isaac" name = "rand_isaac"
version = "0.1.1" version = "0.1.1"
@@ -681,16 +623,10 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
dependencies = [ dependencies = [
"rand 0.6.5", "rand",
"serde", "serde",
] ]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -22,8 +22,7 @@ edition = "2021"
bytes = "1.1" bytes = "1.1"
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
libc = "0.2" libc = "0.2"
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" } minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c181117" }
rand = "0.8"
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] } tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] }
dotenv = "0.15" dotenv = "0.15"

View File

@@ -5,11 +5,11 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use bytes::BytesMut; use bytes::BytesMut;
use minecraft_protocol::data::server_status::ServerStatus;
use minecraft_protocol::decoder::Decoder; use minecraft_protocol::decoder::Decoder;
use minecraft_protocol::encoder::Encoder; use minecraft_protocol::encoder::Encoder;
use minecraft_protocol::version::v1_14_4::handshake::Handshake; use minecraft_protocol::version::v1_14_4::handshake::Handshake;
use minecraft_protocol::version::v1_14_4::status::{PingRequest, PingResponse}; use minecraft_protocol::version::v1_14_4::status::StatusResponse;
use rand::Rng;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream; use tokio::net::TcpStream;
@@ -22,42 +22,39 @@ const PROTOCOL_VERSION: i32 = 754;
/// Monitor ping inverval in seconds. /// Monitor ping inverval in seconds.
const MONITOR_PING_INTERVAL: u64 = 2; const MONITOR_PING_INTERVAL: u64 = 2;
/// Ping timeout in seconds. /// Status request timeout in seconds.
const PING_TIMEOUT: u64 = 8; const STATUS_TIMEOUT: u64 = 8;
/// Poll server state.
///
/// Returns `true` if a ping succeeded.
pub async fn poll_server(addr: SocketAddr) -> bool {
attempt_connect(addr).await.is_ok()
}
/// Monitor server. /// Monitor server.
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) { pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
loop { loop {
trace!("Polling {} ... ", addr); trace!("Fetching status for {} ... ", addr);
let online = poll_server(addr).await; let status = poll_server(addr).await;
state.set_online(online); // Update server state
state.set_online(status.is_some());
if let Some(status) = status {
state.set_status(status);
}
tokio::time::sleep(Duration::from_secs(MONITOR_PING_INTERVAL)).await; tokio::time::sleep(Duration::from_secs(MONITOR_PING_INTERVAL)).await;
} }
} }
/// Attemp to connect to the given server. /// Poll server state.
async fn attempt_connect(addr: SocketAddr) -> Result<(), ()> { ///
/// Returns server status if connection succeeded.
pub async fn poll_server(addr: SocketAddr) -> Option<ServerStatus> {
fetch_status(addr).await.ok()
}
/// Attemp to fetch status from server.
async fn fetch_status(addr: SocketAddr) -> Result<ServerStatus, ()> {
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?; let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
// Send handshake
send_handshake(&mut stream, addr).await?; send_handshake(&mut stream, addr).await?;
request_status(&mut stream).await?;
// Send ping request wait_for_status_timeout(&mut stream).await
let token = send_ping(&mut stream).await?;
// Wait for ping with timeout
wait_for_ping_timeout(&mut stream, token).await?;
Ok(())
} }
/// Send handshake. /// Send handshake.
@@ -81,29 +78,17 @@ async fn send_handshake(stream: &mut TcpStream, addr: SocketAddr) -> Result<(),
Ok(()) Ok(())
} }
/// Send ping requets. /// Send status request.
/// async fn request_status(stream: &mut TcpStream) -> Result<(), ()> {
/// Returns sent ping time token on success. let raw = RawPacket::new(proto::STATUS_PACKET_ID_STATUS, vec![])
async fn send_ping(stream: &mut TcpStream) -> Result<u64, ()> {
// Generate a random ping token
let token = rand::thread_rng().gen();
let ping = PingRequest { time: token };
let mut packet = Vec::new();
ping.encode(&mut packet).map_err(|_| ())?;
let raw = RawPacket::new(proto::STATUS_PACKET_ID_PING, packet)
.encode() .encode()
.map_err(|_| ())?; .map_err(|_| ())?;
stream.write_all(&raw).await.map_err(|_| ())?; stream.write_all(&raw).await.map_err(|_| ())?;
Ok(())
Ok(token)
} }
/// Wait for a ping response. /// Wait for a status response.
async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> { async fn wait_for_status(stream: &mut TcpStream) -> Result<ServerStatus, ()> {
// Get stream reader, set up buffer // Get stream reader, set up buffer
let (mut reader, mut _writer) = stream.split(); let (mut reader, mut _writer) = stream.split();
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
@@ -116,16 +101,10 @@ async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
Err(_) => continue, Err(_) => continue,
}; };
// Catch ping response // Catch status response
if packet.id == proto::STATUS_PACKET_ID_PING { if packet.id == proto::STATUS_PACKET_ID_STATUS {
let ping = PingResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?; let status = StatusResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
return Ok(status.server_status);
// Ensure ping token is correct
if ping.time != token {
break;
}
return Ok(());
} }
} }
@@ -133,10 +112,10 @@ async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
Err(()) Err(())
} }
/// Wait for a ping response with timeout. /// Wait for a status response.
async fn wait_for_ping_timeout(stream: &mut TcpStream, token: u64) -> Result<(), ()> { async fn wait_for_status_timeout(stream: &mut TcpStream) -> Result<ServerStatus, ()> {
let ping = wait_for_ping(stream, token); let status = wait_for_status(stream);
tokio::time::timeout(Duration::from_secs(PING_TIMEOUT), ping) tokio::time::timeout(Duration::from_secs(STATUS_TIMEOUT), status)
.await .await
.map_err(|_| ())? .map_err(|_| ())?
} }

View File

@@ -1,6 +1,7 @@
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use minecraft_protocol::data::server_status::ServerStatus;
use tokio::process::Command; use tokio::process::Command;
use crate::config::SERVER_CMD; use crate::config::SERVER_CMD;
@@ -16,6 +17,11 @@ pub struct ServerState {
/// Server PID. /// Server PID.
pid: Mutex<Option<u32>>, pid: Mutex<Option<u32>>,
/// Last known server status.
///
/// Once set, this will remain set, and isn't cleared when the server goes offline.
status: Mutex<Option<ServerStatus>>,
} }
impl ServerState { impl ServerState {
@@ -54,6 +60,11 @@ impl ServerState {
pub fn set_pid(&self, pid: Option<u32>) { pub fn set_pid(&self, pid: Option<u32>) {
*self.pid.lock().unwrap() = pid; *self.pid.lock().unwrap() = pid;
} }
/// Update the server status.
pub fn set_status(&self, status: ServerStatus) {
self.status.lock().unwrap().replace(status);
}
} }
/// Start Minecraft server. /// Start Minecraft server.