mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 12:50:23 -07:00
Implement very basic server monitor
This commit is contained in:
parent
622310c685
commit
cd3b06b634
70
Cargo.lock
generated
70
Cargo.lock
generated
@ -142,6 +142,17 @@ 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"
|
||||||
@ -164,6 +175,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures",
|
"futures",
|
||||||
"minecraft-protocol",
|
"minecraft-protocol",
|
||||||
|
"rand 0.8.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -291,6 +303,12 @@ 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 = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.32"
|
version = "1.0.32"
|
||||||
@ -317,9 +335,9 @@ checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 0.1.7",
|
"autocfg 0.1.7",
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.1.1",
|
||||||
"rand_core 0.4.2",
|
"rand_core 0.4.2",
|
||||||
"rand_hc",
|
"rand_hc 0.1.0",
|
||||||
"rand_isaac",
|
"rand_isaac",
|
||||||
"rand_jitter",
|
"rand_jitter",
|
||||||
"rand_os",
|
"rand_os",
|
||||||
@ -328,6 +346,18 @@ 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"
|
||||||
@ -338,6 +368,16 @@ 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"
|
||||||
@ -353,6 +393,15 @@ 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"
|
||||||
@ -362,6 +411,15 @@ 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"
|
||||||
@ -512,10 +570,16 @@ 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",
|
"rand 0.6.5",
|
||||||
"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"
|
||||||
|
@ -22,4 +22,5 @@ edition = "2021"
|
|||||||
bytes = "1.1"
|
bytes = "1.1"
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" }
|
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" }
|
||||||
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "sync"] }
|
rand = "0.8"
|
||||||
|
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "sync", "time"] }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# lazymc
|
# lazymc
|
||||||
|
|
||||||
`lazymc` puts your Minecraft server at rest when idle, and wakes it up when
|
`lazymc` puts your Minecraft server to rest when idle, and wakes it up when
|
||||||
players connect.
|
players connect.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
13
src/main.rs
13
src/main.rs
@ -1,4 +1,5 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod monitor;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
@ -25,13 +26,19 @@ use protocol::{Client, ClientState, RawPacket};
|
|||||||
async fn main() -> Result<(), ()> {
|
async fn main() -> Result<(), ()> {
|
||||||
println!(
|
println!(
|
||||||
"Proxying public {} to internal {}",
|
"Proxying public {} to internal {}",
|
||||||
ADDRESS_PUBLIC, ADDRESS_PROXY
|
ADDRESS_PUBLIC, ADDRESS_PROXY,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let server_state = monitor::ServerState::default().shared();
|
||||||
|
|
||||||
// Listen for new connections
|
// Listen for new connections
|
||||||
// TODO: do not drop error here
|
// TODO: do not drop error here
|
||||||
let listener = TcpListener::bind(ADDRESS_PUBLIC).await.map_err(|_| ())?;
|
let listener = TcpListener::bind(ADDRESS_PUBLIC).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
// Spawn server monitor
|
||||||
|
let addr = ADDRESS_PROXY.parse().expect("invalid server IP");
|
||||||
|
tokio::spawn(monitor::monitor_server(addr, server_state));
|
||||||
|
|
||||||
// Proxy all incomming connections
|
// Proxy all incomming connections
|
||||||
while let Ok((inbound, _)) = listener.accept().await {
|
while let Ok((inbound, _)) = listener.accept().await {
|
||||||
let client = Client::default();
|
let client = Client::default();
|
||||||
@ -53,7 +60,7 @@ async fn main() -> Result<(), ()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read raw packet from stream.
|
/// Read raw packet from stream.
|
||||||
async fn read_packet<'a>(
|
pub async fn read_packet<'a>(
|
||||||
buf: &mut BytesMut,
|
buf: &mut BytesMut,
|
||||||
stream: &mut ReadHalf<'a>,
|
stream: &mut ReadHalf<'a>,
|
||||||
) -> Result<Option<(RawPacket, Vec<u8>)>, ()> {
|
) -> Result<Option<(RawPacket, Vec<u8>)>, ()> {
|
||||||
@ -113,7 +120,7 @@ async fn proxy(client: Client, mut inbound: TcpStream, addr_target: String) -> R
|
|||||||
|
|
||||||
let (client_send_queue, mut client_to_send) = unbounded_channel::<Vec<u8>>();
|
let (client_send_queue, mut client_to_send) = unbounded_channel::<Vec<u8>>();
|
||||||
|
|
||||||
let server_available = false;
|
let server_available = true;
|
||||||
|
|
||||||
let client_to_server = async {
|
let client_to_server = async {
|
||||||
// Incoming buffer
|
// Incoming buffer
|
||||||
|
179
src/monitor.rs
Normal file
179
src/monitor.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// TODO: remove all unwraps/expects here!
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
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::{PingRequest, PingResponse};
|
||||||
|
use rand::Rng;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::protocol::{self, ClientState, RawPacket};
|
||||||
|
|
||||||
|
/// Minecraft protocol version used when polling server status.
|
||||||
|
const PROTOCOL_VERSION: i32 = 754;
|
||||||
|
|
||||||
|
/// Monitor ping inverval in seconds.
|
||||||
|
const MONITOR_PING_INTERVAL: u64 = 2;
|
||||||
|
|
||||||
|
/// Ping timeout in seconds.
|
||||||
|
const PING_TIMEOUT: u64 = 8;
|
||||||
|
|
||||||
|
/// Shared server state.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ServerState {
|
||||||
|
/// Whether the server is online.
|
||||||
|
online: AtomicBool,
|
||||||
|
|
||||||
|
/// Whether the server is starting.
|
||||||
|
starting: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerState {
|
||||||
|
/// Transform into shared instance.
|
||||||
|
pub fn shared(self) -> Arc<Self> {
|
||||||
|
Arc::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the server is online.
|
||||||
|
pub fn online(&self) -> bool {
|
||||||
|
self.online.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the server is online.
|
||||||
|
pub fn set_online(&self, online: bool) {
|
||||||
|
self.online.store(online, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the server is starting.
|
||||||
|
pub fn starting(&self) -> bool {
|
||||||
|
self.starting.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the server is starting.
|
||||||
|
pub fn set_starting(&self, starting: bool) {
|
||||||
|
self.starting.store(starting, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub async fn monitor_server(addr: SocketAddr, state: Arc<ServerState>) {
|
||||||
|
loop {
|
||||||
|
eprint!("Polling {}: ", addr);
|
||||||
|
let online = poll_server(addr).await;
|
||||||
|
|
||||||
|
state.set_online(online);
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(MONITOR_PING_INTERVAL)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attemp to connect to the given server.
|
||||||
|
async fn attempt_connect(addr: SocketAddr) -> Result<(), ()> {
|
||||||
|
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
// Send handshake
|
||||||
|
send_handshake(&mut stream, addr).await?;
|
||||||
|
|
||||||
|
// Send ping request
|
||||||
|
let token = send_ping(&mut stream).await?;
|
||||||
|
|
||||||
|
// Wait for ping with timeout
|
||||||
|
wait_for_ping_timeout(&mut stream, token).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send handshake.
|
||||||
|
async fn send_handshake(stream: &mut TcpStream, addr: SocketAddr) -> Result<(), ()> {
|
||||||
|
let handshake = Handshake {
|
||||||
|
protocol_version: PROTOCOL_VERSION,
|
||||||
|
server_addr: addr.ip().to_string(),
|
||||||
|
server_port: addr.port(),
|
||||||
|
next_state: ClientState::Status.to_id(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut packet = Vec::new();
|
||||||
|
handshake.encode(&mut packet).map_err(|_| ())?;
|
||||||
|
|
||||||
|
let raw = RawPacket::new(protocol::HANDSHAKE_PACKET_ID_HANDSHAKE, packet)
|
||||||
|
.encode()
|
||||||
|
.map_err(|_| ())?;
|
||||||
|
|
||||||
|
stream.write_all(&raw).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send ping requets.
|
||||||
|
///
|
||||||
|
/// Returns sent ping time token on success.
|
||||||
|
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(protocol::STATUS_PACKET_ID_PING, packet)
|
||||||
|
.encode()
|
||||||
|
.map_err(|_| ())?;
|
||||||
|
|
||||||
|
stream.write_all(&raw).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for a ping 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 crate::read_packet(&mut buf, &mut reader).await {
|
||||||
|
Ok(Some(packet)) => packet,
|
||||||
|
Ok(None) => break,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Catch ping response
|
||||||
|
if packet.id == protocol::STATUS_PACKET_ID_PING {
|
||||||
|
let ping = PingResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||||
|
|
||||||
|
// Ensure ping token is correct
|
||||||
|
if ping.time != token {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some error occurred
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for a ping response with timeout.
|
||||||
|
async fn wait_for_ping_timeout(stream: &mut TcpStream, token: u64) -> Result<(), ()> {
|
||||||
|
let ping = wait_for_ping(stream, token);
|
||||||
|
tokio::time::timeout(Duration::from_secs(PING_TIMEOUT), ping)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ())?
|
||||||
|
}
|
@ -2,6 +2,7 @@ use std::sync::Mutex;
|
|||||||
|
|
||||||
use crate::types;
|
use crate::types;
|
||||||
|
|
||||||
|
pub const HANDSHAKE_PACKET_ID_HANDSHAKE: i32 = 0;
|
||||||
pub const STATUS_PACKET_ID_STATUS: i32 = 0;
|
pub const STATUS_PACKET_ID_STATUS: i32 = 0;
|
||||||
pub const STATUS_PACKET_ID_PING: i32 = 1;
|
pub const STATUS_PACKET_ID_PING: i32 = 1;
|
||||||
pub const LOGIN_PACKET_ID_LOGIN_START: i32 = 0;
|
pub const LOGIN_PACKET_ID_LOGIN_START: i32 = 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user