Update to 1.21.8

This commit is contained in:
Alexander Medvedev 2025-08-03 23:31:23 +02:00
parent 1979d3310c
commit 8ae9635c80
5 changed files with 755 additions and 536 deletions

1028
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,12 @@ codegen-units = 1
log = "0.4" log = "0.4"
simple_logger = "5.0.0" simple_logger = "5.0.0"
# CLI # CLI
clap = { version = "4.5.31", features = ["derive"] } clap = { version = "4.5.42", features = ["derive"] }
# Networking # Networking
tokio = { version = "1.43.0", features = ["net", "io-util"] } tokio = { version = "1.47.1", features = ["net", "io-util"] }
pumpkin-protocol = { git = "https://github.com/Pumpkin-MC/Pumpkin", package = "pumpkin-protocol", default-features = false, features = ["packets"]} pumpkin-protocol = { git = "https://github.com/Pumpkin-MC/Pumpkin", package = "pumpkin-protocol", default-features = false, features = ["serverbound", "clientbound"]}
pumpkin-data = { git = "https://github.com/Pumpkin-MC/Pumpkin", package = "pumpkin-data"}
crossbeam = "0.8.4" crossbeam = "0.8.4"
uuid = "1.15.0" uuid = "1.17.0"
rand = "0.9" rand = "0.9"

View File

@ -1,7 +1,7 @@
<div align="center"> <div align="center">
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![Current version)](https://img.shields.io/badge/current_version-1.21.4-blue) ![Current version)](https://img.shields.io/badge/current_version-1.21.8-blue)
A tool to stress test Minecraft servers by simulating numerous bot connections, helping identify performance bottlenecks and stability issues. A tool to stress test Minecraft servers by simulating numerous bot connections, helping identify performance bottlenecks and stability issues.
![Screenshot_20250223_154536](https://github.com/user-attachments/assets/b1379060-e375-44dc-95a3-91f1f2fb52dd) ![Screenshot_20250223_154536](https://github.com/user-attachments/assets/b1379060-e375-44dc-95a3-91f1f2fb52dd)

View File

@ -1,27 +1,31 @@
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use pumpkin_protocol::bytebuf::ReadingError; use pumpkin_data::packet::CURRENT_MC_PROTOCOL;
use pumpkin_protocol::bytebuf::packet::Packet; use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::client::config::{CConfigDisconnect, CFinishConfig}; use pumpkin_protocol::java::client::config::{CConfigDisconnect, CFinishConfig};
use pumpkin_protocol::client::login::{ use pumpkin_protocol::java::client::login::{
CEncryptionRequest, CLoginDisconnect, CLoginSuccess, CSetCompression, CEncryptionRequest, CLoginDisconnect, CLoginSuccess, CSetCompression,
}; };
use pumpkin_protocol::client::play::{CKeepAlive, CPlayDisconnect, CPlayerPosition}; use pumpkin_protocol::java::client::play::{CKeepAlive, CPlayDisconnect, CPlayerPosition};
use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::packet_decoder::TCPNetworkDecoder;
use pumpkin_protocol::server::config::{SAcknowledgeFinishConfig, SKnownPacks}; use pumpkin_protocol::java::packet_encoder::TCPNetworkEncoder;
use pumpkin_protocol::server::handshake::SHandShake; use pumpkin_protocol::java::server::config::{SAcknowledgeFinishConfig, SKnownPacks};
use pumpkin_protocol::server::login::{SLoginAcknowledged, SLoginStart}; use pumpkin_protocol::java::server::handshake::SHandShake;
use pumpkin_protocol::server::play::{SChatMessage, SConfirmTeleport, SKeepAlive}; use pumpkin_protocol::java::server::login::{SLoginAcknowledged, SLoginStart};
use pumpkin_protocol::{CURRENT_MC_PROTOCOL, RawPacket, ServerPacket}; use pumpkin_protocol::java::server::play::{SChatMessage, SConfirmTeleport, SKeepAlive};
use pumpkin_protocol::packet::Packet;
use pumpkin_protocol::ser::NetworkWriteExt;
use pumpkin_protocol::ser::{ReadingError, WritingError};
use pumpkin_protocol::{ use pumpkin_protocol::{
ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState, ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState, PacketDecodeError,
packet_decoder::PacketDecoder, packet_encoder::PacketEncoder, RawPacket, ServerPacket,
}; };
use std::collections::VecDeque; use std::io::Write;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::AtomicU32; use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, atomic::AtomicBool}; use std::sync::{Arc, atomic::AtomicBool};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{BufReader, BufWriter};
use tokio::sync::Notify;
use tokio::{ use tokio::{
net::{ net::{
TcpStream, TcpStream,
@ -38,12 +42,10 @@ pub struct Client {
/// Indicates if the client connection is closed. /// Indicates if the client connection is closed.
pub closed: AtomicBool, pub closed: AtomicBool,
/// The packet encoder for outgoing packets. /// The packet encoder for outgoing packets.
pub enc: Arc<Mutex<PacketEncoder>>, pub network_writer: Arc<Mutex<TCPNetworkEncoder<BufWriter<OwnedWriteHalf>>>>,
/// The packet decoder for incoming packets. /// The packet decoder for incoming packets.
pub dec: Arc<Mutex<PacketDecoder>>, pub network_reader: Arc<Mutex<TCPNetworkDecoder<BufReader<OwnedReadHalf>>>>,
pub connection_reader: Mutex<OwnedReadHalf>, close_interrupt: Arc<Notify>,
pub connection_writer: Mutex<OwnedWriteHalf>,
pub client_packets_queue: Arc<Mutex<VecDeque<RawPacket>>>,
message_spam_cooldown: AtomicU32, message_spam_cooldown: AtomicU32,
} }
@ -53,12 +55,14 @@ impl Client {
let (connection_reader, connection_writer) = stream.into_split(); let (connection_reader, connection_writer) = stream.into_split();
Self { Self {
connection_state: AtomicCell::new(ConnectionState::HandShake), connection_state: AtomicCell::new(ConnectionState::HandShake),
enc: Arc::new(Mutex::new(PacketEncoder::default())), network_writer: Arc::new(Mutex::new(TCPNetworkEncoder::new(BufWriter::new(
dec: Arc::new(Mutex::new(PacketDecoder::default())), connection_writer,
)))),
network_reader: Arc::new(Mutex::new(TCPNetworkDecoder::new(BufReader::new(
connection_reader,
)))),
closed: AtomicBool::new(false), closed: AtomicBool::new(false),
connection_reader: Mutex::new(connection_reader), close_interrupt: Arc::new(Notify::new()),
connection_writer: Mutex::new(connection_writer),
client_packets_queue: Arc::new(Mutex::new(VecDeque::new())),
message_spam_cooldown: AtomicU32::new(1), message_spam_cooldown: AtomicU32::new(1),
} }
} }
@ -75,83 +79,61 @@ impl Client {
&self, &self,
compression: Option<(CompressionThreshold, CompressionLevel)>, compression: Option<(CompressionThreshold, CompressionLevel)>,
) { ) {
self.dec.lock().await.set_compression(compression.is_some()); if let Some(compression) = compression {
self.enc self.network_reader
.lock() .lock()
.await .await
.set_compression(compression.map(|s| (s.0, s.1))) .set_compression(compression.0);
.unwrap_or_else(|_| log::warn!("invalid compression level")); self.network_writer
} .lock()
.await
pub async fn process_packets(&self) { .set_compression(compression);
let mut packet_queue = self.client_packets_queue.lock().await;
while let Some(mut packet) = packet_queue.pop_front() {
if self.closed.load(std::sync::atomic::Ordering::Relaxed) {
log::debug!("Canceling client packet processing (pre)");
return;
}
if let Err(error) = self.handle_packet(&mut packet).await {
log::error!(
"Failed to read incoming packet with id {}: {}",
i32::from(packet.id),
error
);
self.close().await;
};
} }
} }
pub async fn add_packet(&self, packet: RawPacket) {
let mut client_packets_queue = self.client_packets_queue.lock().await; pub async fn await_close_interrupt(&self) {
client_packets_queue.push_back(packet); self.close_interrupt.notified().await;
} }
pub async fn poll(&self) -> bool { pub async fn get_packet(&self) -> Option<RawPacket> {
loop { let mut network_reader = self.network_reader.lock().await;
if self.closed.load(std::sync::atomic::Ordering::Relaxed) { tokio::select! {
// If we manually close (like a kick) we dont want to keep reading bytes () = self.await_close_interrupt() => {
return false; log::debug!("Canceling player packet processing");
} None
},
let mut dec = self.dec.lock().await; packet_result = network_reader.get_raw_packet() => {
match packet_result {
match dec.decode() { Ok(packet) => Some(packet),
Ok(Some(packet)) => { Err(err) => {
self.add_packet(packet).await; if !matches!(err, PacketDecodeError::ConnectionClosed) {
return true; log::warn!("Failed to decode packet from client: {err}");
} self.close().await;
Ok(None) => (), //log::debug!("Waiting for more data to complete packet..."), }
Err(err) => { None
log::warn!("Failed to decode packet for: {}", err.to_string());
self.close().await;
return false; // return to avoid reserving additional bytes
}
}
dec.reserve(4096);
let mut buf = dec.take_capacity();
let bytes_read = self.connection_reader.lock().await.read_buf(&mut buf).await;
match bytes_read {
Ok(cnt) => {
//log::debug!("Read {} bytes", cnt);
if cnt == 0 {
self.close().await;
return false;
} }
} }
Err(error) => { }
log::error!("Error while reading incoming packet {}", error);
self.close().await;
return false;
}
};
// This should always be an O(1) unsplit because we reserved space earlier and
// the call to `read_buf` shouldn't have grown the allocation.
dec.queue_bytes(buf);
} }
} }
pub async fn process_packets(self: &Arc<Self>) -> bool {
let packet = self.get_packet().await;
let Some(mut packet) = packet else {
return false;
};
if let Err(error) = self.handle_packet(&mut packet).await {
log::error!(
"Failed to read incoming packet with id {}: {}",
packet.id,
error
);
self.close().await;
}
true
}
// TODO: make this less ugly ig // TODO: make this less ugly ig
pub async fn tick(&self, spam_message: Arc<Option<String>>, spam_message_delay: u32) { pub async fn tick(&self, spam_message: Arc<Option<String>>, spam_message_delay: u32) {
if self.connection_state.load() != ConnectionState::Play { if self.connection_state.load() != ConnectionState::Play {
@ -175,44 +157,57 @@ impl Client {
let since_the_epoch = start let since_the_epoch = start
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("Time went backwards"); .expect("Time went backwards");
self.send_packet(&SChatMessage::new( self.send_packet(&SChatMessage {
message, message,
since_the_epoch.as_millis() as i64, timestamp: since_the_epoch.as_millis() as i64,
rand::random(), salt: rand::random(),
None, signature: None,
VarInt(1), message_count: VarInt(1),
vec![0; 20], acknowledged: vec![0; 20].into_boxed_slice(),
)) checksum: 0,
})
.await; .await;
} }
pub fn write_packet<P: ClientPacket>(
packet: &P,
write: impl Write,
) -> Result<(), WritingError> {
let mut write = write;
write.write_var_int(&VarInt(P::PACKET_ID))?;
packet.write_packet_data(write)
}
/// Sends a clientbound packet to the connected client. /// Sends a clientbound packet to the connected client.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `packet`: A reference to a packet object implementing the `ClientPacket` trait. /// * `packet`: A reference to a packet object implementing the `ClientPacket` trait.
pub async fn send_packet<P: ClientPacket>(&self, packet: &P) { pub async fn send_packet<P: ClientPacket>(&self, packet: &P) {
//log::debug!("Sending packet with id {} to {}", P::PACKET_ID, self.id); let mut packet_buf = Vec::new();
// assert!(!self.closed); let writer = &mut packet_buf;
if self.closed.load(std::sync::atomic::Ordering::Relaxed) { Self::write_packet(packet, writer).unwrap();
return; if let Err(err) = self
} .network_writer
.lock()
let mut enc = self.enc.lock().await; .await
if let Err(_error) = enc.append_packet(packet) { .write_packet(packet_buf.into())
return; .await
} {
// It is expected that the packet will fail if we are closed
let mut writer = self.connection_writer.lock().await; if !self.closed.load(Ordering::Relaxed) {
if let Err(error) = writer.write_all(&enc.take()).await { log::warn!("Failed to send packet to client: {err}");
log::debug!("Unable to write to connection: {}", error.to_string()); // We now need to close the connection to the client since the stream is in an
// unknown state
self.close().await;
}
} }
} }
pub async fn join_server(&self, address: SocketAddr, name: String) { pub async fn join_server(&self, address: SocketAddr, name: String) {
dbg!(address.ip().to_string()); dbg!(address.ip().to_string());
self.send_packet(&SHandShake { self.send_packet(&SHandShake {
protocol_version: VarInt(CURRENT_MC_PROTOCOL.get() as i32), protocol_version: VarInt(CURRENT_MC_PROTOCOL as i32),
server_address: address.ip().to_string(), server_address: address.ip().to_string(),
server_port: address.port(), server_port: address.port(),
next_state: pumpkin_protocol::ConnectionState::Login, next_state: pumpkin_protocol::ConnectionState::Login,
@ -239,19 +234,16 @@ impl Client {
} }
async fn handle_login_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> { async fn handle_login_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> {
let bytebuf = &mut packet.bytebuf; let bytebuf = &packet.payload[..];
match packet.id.0 { match packet.id {
CEncryptionRequest::PACKET_ID => { CEncryptionRequest::PACKET_ID => {
log::debug!("Got Encryption Request") log::debug!("Got Encryption Request")
} }
CSetCompression::PACKET_ID => { CSetCompression::PACKET_ID => {
log::trace!("Set Compression"); log::trace!("Set Compression");
let packet = CSetCompression::read(bytebuf)?; let packet = CSetCompression::read(bytebuf)?;
self.set_compression(Some(( self.set_compression(Some((packet.threshold.0 as usize, 6)))
CompressionThreshold(packet.threshold.0 as u32), .await
CompressionLevel(6),
)))
.await
} }
CLoginDisconnect::PACKET_ID => { CLoginDisconnect::PACKET_ID => {
log::error!("Kicking in Login State"); log::error!("Kicking in Login State");
@ -273,7 +265,7 @@ impl Client {
} }
async fn handle_config_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> { async fn handle_config_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> {
match packet.id.0 { match packet.id {
CConfigDisconnect::PACKET_ID => { CConfigDisconnect::PACKET_ID => {
log::error!("Kicking in Config State"); log::error!("Kicking in Config State");
self.close().await; self.close().await;
@ -289,8 +281,8 @@ impl Client {
} }
async fn handle_play_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> { async fn handle_play_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> {
let bytebuf = &mut packet.bytebuf; let bytebuf = &packet.payload[..];
match packet.id.0 { match packet.id {
CKeepAlive::PACKET_ID => { CKeepAlive::PACKET_ID => {
let packet = CKeepAlive::read(bytebuf)?; let packet = CKeepAlive::read(bytebuf)?;
self.send_packet(&SKeepAlive { self.send_packet(&SKeepAlive {
@ -315,6 +307,7 @@ impl Client {
} }
pub async fn close(&self) { pub async fn close(&self) {
self.close_interrupt.notify_waiters();
self.closed self.closed
.store(true, std::sync::atomic::Ordering::Relaxed); .store(true, std::sync::atomic::Ordering::Relaxed);
} }

View File

@ -38,17 +38,16 @@ async fn main() {
let stream = TcpStream::connect(address) let stream = TcpStream::connect(address)
.await .await
.expect("Failed to connect to Ip"); .expect("Failed to connect to Ip");
let client = Client::new(stream); let client = Arc::new(Client::new(stream));
let message = Arc::new(args.spam_message.clone()); let message = Arc::new(args.spam_message.clone());
client.join_server(address, format!("BOT_{}", i)).await; client.join_server(address, format!("BOT_{i}")).await;
let join_handle = tokio::spawn(async move { let join_handle = tokio::spawn(async move {
loop { loop {
if !client.poll().await { if !client.process_packets().await {
break; break;
} }
client.tick(message.clone(), args.spam_message_delay).await; client.tick(message.clone(), args.spam_message_delay).await;
client.process_packets().await;
} }
}); });
join_handles.push(join_handle); join_handles.push(join_handle);