mirror of
https://github.com/timvisee/lazymc.git
synced 2025-07-31 04:02:02 -07:00
Add ping as fall back method for server status monitor
This commit is contained in:
@@ -20,7 +20,7 @@ edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["rcon"]
|
||||
rcon = ["rust_rcon", "rand"]
|
||||
rcon = ["rust_rcon"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
@@ -34,6 +34,7 @@ libc = "0.2"
|
||||
log = "0.4"
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "31041b8" }
|
||||
pretty_env_logger = "0.4"
|
||||
rand = "0.8"
|
||||
serde = "1.0"
|
||||
shlex = "1.1"
|
||||
thiserror = "1.0"
|
||||
@@ -41,5 +42,4 @@ tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-t
|
||||
toml = "0.5"
|
||||
|
||||
# Feature: rcon
|
||||
rand = { version = "0.8", optional = true }
|
||||
rust_rcon = { package = "rcon", version = "0.5", optional = true }
|
||||
|
122
src/monitor.rs
122
src/monitor.rs
@@ -9,49 +9,82 @@ use minecraft_protocol::data::server_status::ServerStatus;
|
||||
use minecraft_protocol::decoder::Decoder;
|
||||
use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||
use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
||||
use minecraft_protocol::version::v1_14_4::status::{PingRequest, PingResponse, StatusResponse};
|
||||
use rand::Rng;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::proto::{self, ClientState, RawPacket};
|
||||
use crate::server::Server;
|
||||
use crate::server::{Server, State};
|
||||
|
||||
/// Monitor ping inverval in seconds.
|
||||
const MONITOR_PING_INTERVAL: u64 = 2;
|
||||
const MONITOR_POLL_INTERVAL: Duration = Duration::from_secs(2);
|
||||
|
||||
/// Status request timeout in seconds.
|
||||
const STATUS_TIMEOUT: u64 = 8;
|
||||
|
||||
/// Ping request timeout in seconds.
|
||||
const PING_TIMEOUT: u64 = 10;
|
||||
|
||||
/// Monitor server.
|
||||
pub async fn monitor_server(config: Arc<Config>, state: Arc<Server>) {
|
||||
pub async fn monitor_server(config: Arc<Config>, server: Arc<Server>) {
|
||||
// Server address
|
||||
let addr = config.server.address;
|
||||
|
||||
let mut poll_interval = time::interval(MONITOR_POLL_INTERVAL);
|
||||
|
||||
loop {
|
||||
// Poll server state and update internal status
|
||||
trace!(target: "lazymc::monitor", "Fetching status for {} ... ", addr);
|
||||
let status = poll_server(&config, addr).await;
|
||||
state.update_status(&config, status);
|
||||
let status = poll_server(&config, &server, addr).await;
|
||||
|
||||
match status {
|
||||
// Got status, update
|
||||
Ok(Some(status)) => server.update_status(&config, Some(status)),
|
||||
|
||||
// Error, reset status
|
||||
Err(_) => server.update_status(&config, None),
|
||||
|
||||
// Didn't get status, but ping fallback worked, leave as-is, show warning
|
||||
Ok(None) => {
|
||||
warn!(target: "lazymc::monitor", "Failed to poll server status, ping fallback succeeded");
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep server when it's bedtime
|
||||
if state.should_sleep(&config) {
|
||||
if server.should_sleep(&config) {
|
||||
info!(target: "lazymc::montior", "Server has been idle, sleeping...");
|
||||
if !state.stop(&config).await {
|
||||
if !server.stop(&config).await {
|
||||
warn!(target: "lazymc", "Failed to stop server");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use interval instead, for a more reliable polling interval?
|
||||
tokio::time::sleep(Duration::from_secs(MONITOR_PING_INTERVAL)).await;
|
||||
poll_interval.tick().await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll server state.
|
||||
///
|
||||
/// Returns server status if connection succeeded.
|
||||
pub async fn poll_server(config: &Config, addr: SocketAddr) -> Option<ServerStatus> {
|
||||
fetch_status(config, addr).await.ok()
|
||||
/// Returns `Ok` if status/ping succeeded, includes server status most of the time.
|
||||
/// Returns `Err` if no connection could be established or if an error occurred.
|
||||
pub async fn poll_server(
|
||||
config: &Config,
|
||||
server: &Server,
|
||||
addr: SocketAddr,
|
||||
) -> Result<Option<ServerStatus>, ()> {
|
||||
// Fetch status
|
||||
if let Ok(status) = fetch_status(config, addr).await {
|
||||
return Ok(Some(status));
|
||||
}
|
||||
|
||||
// Try ping fallback if server is currently started
|
||||
if server.state() == State::Started {
|
||||
do_ping(config, addr).await?;
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
/// Attemp to fetch status from server.
|
||||
@@ -63,6 +96,15 @@ async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus,
|
||||
wait_for_status_timeout(&mut stream).await
|
||||
}
|
||||
|
||||
/// Attemp to ping server.
|
||||
async fn do_ping(config: &Config, addr: SocketAddr) -> Result<(), ()> {
|
||||
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
|
||||
|
||||
send_handshake(&mut stream, &config, addr).await?;
|
||||
let token = send_ping(&mut stream).await?;
|
||||
wait_for_ping_timeout(&mut stream, token).await
|
||||
}
|
||||
|
||||
/// Send handshake.
|
||||
async fn send_handshake(
|
||||
stream: &mut TcpStream,
|
||||
@@ -96,6 +138,21 @@ async fn request_status(stream: &mut TcpStream) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send status request.
|
||||
async fn send_ping(stream: &mut TcpStream) -> Result<u64, ()> {
|
||||
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()
|
||||
.map_err(|_| ())?;
|
||||
stream.write_all(&raw).await.map_err(|_| ())?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
/// Wait for a status response.
|
||||
async fn wait_for_status(stream: &mut TcpStream) -> Result<ServerStatus, ()> {
|
||||
// Get stream reader, set up buffer
|
||||
@@ -128,3 +185,42 @@ async fn wait_for_status_timeout(stream: &mut TcpStream) -> Result<ServerStatus,
|
||||
.await
|
||||
.map_err(|_| ())?
|
||||
}
|
||||
|
||||
/// Wait for a status response.
|
||||
async fn wait_for_ping(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||
// Get stream reader, set up buffer
|
||||
let (mut reader, mut _writer) = stream.split();
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
loop {
|
||||
// Read packet from stream
|
||||
let (packet, _raw) = match proto::read_packet(&mut buf, &mut reader).await {
|
||||
Ok(Some(packet)) => packet,
|
||||
Ok(None) => break,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// Catch ping response
|
||||
if packet.id == proto::STATUS_PACKET_ID_PING {
|
||||
let ping = PingResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||
|
||||
// Ping token must match
|
||||
if ping.time == token {
|
||||
return Ok(());
|
||||
} else {
|
||||
debug!(target: "lazymc", "Got unmatched ping response when polling server status by ping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some error occurred
|
||||
Err(())
|
||||
}
|
||||
|
||||
/// Wait for a status response.
|
||||
async fn wait_for_ping_timeout(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||
let status = wait_for_ping(stream, token);
|
||||
tokio::time::timeout(Duration::from_secs(PING_TIMEOUT), status)
|
||||
.await
|
||||
.map_err(|_| ())?
|
||||
}
|
||||
|
Reference in New Issue
Block a user