From cda86fd59e3f044c7fd9aff2a76cc2d5397c2d0f Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Sep 2024 17:59:56 +0200 Subject: [PATCH] add keep alive packets --- .../src/client/play/c_keep_alive.rs | 8 ++++ pumpkin-protocol/src/client/play/mod.rs | 2 + pumpkin-protocol/src/server/play/mod.rs | 2 + .../src/server/play/s_keep_alive.rs | 8 ++++ pumpkin/src/client/mod.rs | 13 +++++- pumpkin/src/entity/player.rs | 19 ++++++-- pumpkin/src/main.rs | 46 +++++++++++++++++-- pumpkin/src/server/mod.rs | 9 ++-- pumpkin/src/world/mod.rs | 9 ++-- 9 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 pumpkin-protocol/src/client/play/c_keep_alive.rs create mode 100644 pumpkin-protocol/src/server/play/s_keep_alive.rs diff --git a/pumpkin-protocol/src/client/play/c_keep_alive.rs b/pumpkin-protocol/src/client/play/c_keep_alive.rs new file mode 100644 index 000000000..c1d95f094 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_keep_alive.rs @@ -0,0 +1,8 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +#[packet(0x26)] +#[derive(Serialize)] +pub struct CKeepAlive { + pub keep_alive_id: i64, +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 493ae921c..ff9dd2461 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -14,6 +14,7 @@ mod c_entity_velocity; mod c_game_event; mod c_head_rot; mod c_hurt_animation; +mod c_keep_alive; mod c_login; mod c_open_screen; mod c_particle; @@ -57,6 +58,7 @@ pub use c_entity_velocity::*; pub use c_game_event::*; pub use c_head_rot::*; pub use c_hurt_animation::*; +pub use c_keep_alive::*; pub use c_login::*; pub use c_open_screen::*; pub use c_particle::*; diff --git a/pumpkin-protocol/src/server/play/mod.rs b/pumpkin-protocol/src/server/play/mod.rs index 59a8fc3f7..4723a6b57 100644 --- a/pumpkin-protocol/src/server/play/mod.rs +++ b/pumpkin-protocol/src/server/play/mod.rs @@ -5,6 +5,7 @@ mod s_client_information; mod s_close_container; mod s_confirm_teleport; mod s_interact; +mod s_keep_alive; mod s_ping_request; mod s_player_action; mod s_player_command; @@ -25,6 +26,7 @@ pub use s_client_information::*; pub use s_close_container::*; pub use s_confirm_teleport::*; pub use s_interact::*; +pub use s_keep_alive::*; pub use s_ping_request::*; pub use s_player_action::*; pub use s_player_command::*; diff --git a/pumpkin-protocol/src/server/play/s_keep_alive.rs b/pumpkin-protocol/src/server/play/s_keep_alive.rs new file mode 100644 index 000000000..449f00b06 --- /dev/null +++ b/pumpkin-protocol/src/server/play/s_keep_alive.rs @@ -0,0 +1,8 @@ +use pumpkin_macros::packet; +use serde::Deserialize; + +#[packet(0x18)] +#[derive(Deserialize)] +pub struct SKeepAlive { + pub keep_alive_id: i64, +} diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 9dc174072..df40d5d80 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -113,10 +113,19 @@ pub struct Client { /// Indicates whether the client should be converted into a player. pub make_player: AtomicBool, + /// Sends each keep alive packet that the server receives for a player to here, which gets picked up in a tokio task + pub keep_alive_sender: Arc>, + /// Stores the last time it was confirmed that the client is alive + pub last_alive_received: AtomicCell, } impl Client { - pub fn new(token: Token, connection: TcpStream, address: SocketAddr) -> Self { + pub fn new( + token: Token, + connection: TcpStream, + address: SocketAddr, + keep_alive_sender: Arc>, + ) -> Self { Self { protocol_version: AtomicI32::new(0), gameprofile: Mutex::new(None), @@ -132,6 +141,8 @@ impl Client { closed: AtomicBool::new(false), client_packets_queue: Arc::new(Mutex::new(Vec::new())), make_player: AtomicBool::new(false), + keep_alive_sender, + last_alive_received: AtomicCell::new(std::time::Instant::now()), } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 252d4dcc4..848f4f51f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -29,7 +29,7 @@ use pumpkin_protocol::{ ConnectionState, RawPacket, ServerPacket, VarInt, }; -use pumpkin_protocol::server::play::SCloseContainer; +use pumpkin_protocol::server::play::{SCloseContainer, SKeepAlive}; use pumpkin_world::item::ItemStack; use crate::{ @@ -50,7 +50,7 @@ pub struct Player { /// The player's game profile information, including their username and UUID. pub gameprofile: GameProfile, /// The client connection associated with the player. - pub client: Client, + pub client: Arc, /// The player's configuration settings. Changes when the Player changes their settings. pub config: Mutex, /// The player's current gamemode (e.g., Survival, Creative, Adventure). @@ -90,7 +90,12 @@ pub struct Player { } impl Player { - pub fn new(client: Client, world: Arc, entity_id: EntityId, gamemode: GameMode) -> Self { + pub fn new( + client: Arc, + world: Arc, + entity_id: EntityId, + gamemode: GameMode, + ) -> Self { let gameprofile = match client.gameprofile.lock().clone() { Some(profile) => profile, None => { @@ -371,6 +376,14 @@ impl Player { self.handle_close_container(server, SCloseContainer::read(bytebuf)?); Ok(()) } + SKeepAlive::PACKET_ID => { + self.client + .keep_alive_sender + .send(SKeepAlive::read(bytebuf)?.keep_alive_id) + .await + .unwrap(); + Ok(()) + } _ => { log::error!("Failed to handle player packet id {:#04x}", packet.id.0); Ok(()) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 5f27a7607..bb709c22c 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -6,11 +6,13 @@ compile_error!("Compiling for WASI targets is not supported!"); use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; +use client::{interrupted, Client}; +use pumpkin_protocol::client::play::CKeepAlive; +use pumpkin_protocol::ConnectionState; +use server::Server; use std::collections::HashMap; use std::io::{self, Read}; - -use client::{interrupted, Client}; -use server::Server; +use std::time::Duration; // Setup some tokens to allow us to identify which event is for which socket. @@ -78,7 +80,7 @@ fn main() -> io::Result<()> { let use_console = ADVANCED_CONFIG.commands.use_console; let rcon = ADVANCED_CONFIG.rcon.clone(); - let mut clients: HashMap = HashMap::new(); + let mut clients: HashMap> = HashMap::new(); let mut players: HashMap> = HashMap::new(); let server = Arc::new(Server::new()); @@ -152,7 +154,41 @@ fn main() -> io::Result<()> { token, Interest::READABLE.add(Interest::WRITABLE), )?; - let client = Client::new(token, connection, addr); + let keep_alive = tokio::sync::mpsc::channel(1024); + let client = + Arc::new(Client::new(token, connection, addr, keep_alive.0.into())); + + { + let client = client.clone(); + let mut receiver = keep_alive.1; + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { + interval.tick().await; + let now = std::time::Instant::now(); + if client.connection_state.load() == ConnectionState::Play { + if now.duration_since(client.last_alive_received.load()) + >= Duration::from_secs(15) + { + dbg!("no keep alive"); + client.kick("No keep alive received"); + break; + } + let random = rand::random::(); + client.send_packet(&CKeepAlive { + keep_alive_id: random, + }); + if let Some(id) = receiver.recv().await { + if id == random { + client.last_alive_received.store(now); + } + } + } else { + client.last_alive_received.store(now); + } + } + }); + } clients.insert(token, client); }, diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 68dcd9501..b34d756c9 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -5,10 +5,13 @@ use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; +use pumpkin_inventory::drag_handler::DragHandler; +use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_plugin::PluginLoader; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::client::status::CStatusResponse; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; +use pumpkin_registry::Registry; use pumpkin_world::dimension::Dimension; use std::collections::HashMap; use std::{ @@ -19,10 +22,6 @@ use std::{ time::Duration, }; -use pumpkin_inventory::drag_handler::DragHandler; -use pumpkin_inventory::{Container, OpenContainer}; -use pumpkin_registry::Registry; - use crate::client::EncryptionError; use crate::{ client::Client, @@ -96,7 +95,7 @@ impl Server { } } - pub async fn add_player(&self, token: Token, client: Client) -> (Arc, Arc) { + pub async fn add_player(&self, token: Token, client: Arc) -> (Arc, Arc) { let entity_id = self.new_entity_id(); let gamemode = match BASIC_CONFIG.default_gamemode { GameMode::Undefined => GameMode::Survival, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b61ddc868..b3eb8a595 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -2,6 +2,10 @@ use std::{collections::HashMap, sync::Arc}; pub mod player_chunker; +use crate::{ + client::Client, + entity::{player::Player, Entity}, +}; use mio::Token; use num_traits::ToPrimitive; use parking_lot::Mutex; @@ -18,11 +22,6 @@ use pumpkin_protocol::{ use pumpkin_world::level::Level; use tokio::sync::mpsc; -use crate::{ - client::Client, - entity::{player::Player, Entity}, -}; - /// Represents a Minecraft world, containing entities, players, and the underlying level data. /// /// Each dimension (Overworld, Nether, End) typically has its own `World`.