Add more Arguments

- delay
- spam_message
- spam_message_delay
This commit is contained in:
Alexander Medvedev 2025-02-26 20:19:01 +01:00
parent 634f9cb00c
commit 0f348a89c3
5 changed files with 205 additions and 34 deletions

130
Cargo.lock generated
View File

@ -122,11 +122,18 @@ dependencies = [
"crossbeam", "crossbeam",
"log", "log",
"pumpkin-protocol", "pumpkin-protocol",
"rand 0.9.0",
"simple_logger", "simple_logger",
"tokio", "tokio",
"uuid", "uuid",
] ]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.0" version = "1.10.0"
@ -177,9 +184,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.30" version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -187,9 +194,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.30" version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -401,9 +408,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
[[package]] [[package]]
name = "enum_dispatch" name = "enum_dispatch"
@ -435,9 +442,9 @@ dependencies = [
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.35" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@ -643,7 +650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc" checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"rand", "rand 0.8.5",
"rand_xorshift", "rand_xorshift",
] ]
@ -773,6 +780,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy 0.7.35",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.93"
@ -785,7 +801,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-config" name = "pumpkin-config"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"log", "log",
"pumpkin-util", "pumpkin-util",
@ -797,7 +813,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-data" name = "pumpkin-data"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -811,7 +827,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-macros" name = "pumpkin-macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -825,7 +841,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-nbt" name = "pumpkin-nbt"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"bytes", "bytes",
"cesu8", "cesu8",
@ -836,7 +852,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-protocol" name = "pumpkin-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"aes", "aes",
"bytes", "bytes",
@ -857,7 +873,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-util" name = "pumpkin-util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"bytes", "bytes",
"colored 3.0.0", "colored 3.0.0",
@ -871,7 +887,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-world" name = "pumpkin-world"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Pumpkin-MC/Pumpkin#7c1d5eff4cfcd9fcffe762265b0ce77686675d64" source = "git+https://github.com/Pumpkin-MC/Pumpkin#01e571946439ad9b3b40e4e1b9d47aca78f75e6a"
dependencies = [ dependencies = [
"bytes", "bytes",
"dashmap", "dashmap",
@ -915,7 +931,28 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha",
"rand_core 0.9.2",
"zerocopy 0.8.20",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.2",
] ]
[[package]] [[package]]
@ -924,13 +961,23 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c"
dependencies = [
"getrandom",
"zerocopy 0.8.20",
]
[[package]] [[package]]
name = "rand_xorshift" name = "rand_xorshift"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [ dependencies = [
"rand_core", "rand_core 0.6.4",
] ]
[[package]] [[package]]
@ -1015,7 +1062,7 @@ dependencies = [
[[package]] [[package]]
name = "serde_json5" name = "serde_json5"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/kralverde/serde_json5.git#6d320e959a3f596dc4313c1036eae784891466d8" source = "git+https://github.com/kralverde/serde_json5.git#998a0b50c025fe9eac08b882593b175e8a8f43eb"
dependencies = [ dependencies = [
"pest", "pest",
"pest_derive", "pest_derive",
@ -1255,9 +1302,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.14.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1" checksum = "bd8dcafa1ca14750d8d7a05aa05988c17aab20886e1f3ae33a40223c58d92ef7"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"md-5", "md-5",
@ -1473,6 +1520,47 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c"
dependencies = [
"zerocopy-derive 0.8.20",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.13.3" version = "0.13.3"

View File

@ -12,10 +12,11 @@ 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.30", features = ["derive"] } clap = { version = "4.5.31", features = ["derive"] }
# Networking # Networking
tokio = { version = "1.43.0", features = ["net", "io-util"] } tokio = { version = "1.43.0", 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 = ["packets"]}
crossbeam = "0.8.4" crossbeam = "0.8.4"
uuid = "1.14.0" uuid = "1.15.0"
rand = "0.9"

View File

@ -5,12 +5,40 @@
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)
</div> </div>
### How to Run ### How to Run
1. **Disable Online Mode:** Ensure that `online-mode=false` in your server's `server.properties` file. This is essential for Botmark to connect.
2. **Run Botmark:** Use the following command in your terminal: 1. **Disable Online Mode & Encryption:** Ensure that `online-mode=false` in your server's `server.properties` file. This is crucial for Botmark to connect as it simulates offline-mode clients. **(Warning: only do this in a test environment)**.
`./botmark --ip <ip:port> --count <bot-count>` 2. **Run Botmark:** Use the following command in your terminal, adjusting the arguments as needed:
**Example**
`./botmark --ip 0.0.0.0:25565 --count 50` ```bash
./botmark --ip <ip:port> --count <bot-count> [OPTIONS]
```
### Command-Line Arguments
Here's a detailed breakdown of all available command-line arguments:
- **`--ip <ip:port>` (Required):**
- The IP address and port of the Minecraft server to stress test.
- Example: `127.0.0.1:25565` or `example.com:25565`
- **`-c, --count <bot-count>` (Optional, Default: `1`):**
- The number of bots to simulate connecting to the server.
- Example: `--count 50`
- **`-d, --delay <delay>` (Optional, Default: `200`):**
- The delay (in milliseconds) between each bot connection.
- Example: `--delay 100` (100 milliseconds delay)
- **`--spam_message "<message>"` (Optional):**
- Will send a Chat message with the specefied `spam_message_delay`.
- Example: `--spam_message "Hello, I'm a Robot"`
- **`--spam_message_delay <delay>` (Optional, Default: `210`):**
- The delay (in milliseconds) between spam message.
- Example: `--spam_message_delay 100` (100 milliseconds delay)

View File

@ -10,7 +10,7 @@ use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::server::config::{SAcknowledgeFinishConfig, SKnownPacks}; use pumpkin_protocol::server::config::{SAcknowledgeFinishConfig, SKnownPacks};
use pumpkin_protocol::server::handshake::SHandShake; use pumpkin_protocol::server::handshake::SHandShake;
use pumpkin_protocol::server::login::{SLoginAcknowledged, SLoginStart}; use pumpkin_protocol::server::login::{SLoginAcknowledged, SLoginStart};
use pumpkin_protocol::server::play::{SConfirmTeleport, SKeepAlive}; use pumpkin_protocol::server::play::{SChatMessage, SConfirmTeleport, SKeepAlive};
use pumpkin_protocol::{CURRENT_MC_PROTOCOL, RawPacket, ServerPacket}; use pumpkin_protocol::{CURRENT_MC_PROTOCOL, RawPacket, ServerPacket};
use pumpkin_protocol::{ use pumpkin_protocol::{
ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState, ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState,
@ -18,7 +18,9 @@ use pumpkin_protocol::{
}; };
use std::collections::VecDeque; use std::collections::VecDeque;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::AtomicU32;
use std::sync::{Arc, atomic::AtomicBool}; use std::sync::{Arc, atomic::AtomicBool};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::{ use tokio::{
net::{ net::{
@ -42,6 +44,8 @@ pub struct Client {
pub connection_reader: Mutex<OwnedReadHalf>, pub connection_reader: Mutex<OwnedReadHalf>,
pub connection_writer: Mutex<OwnedWriteHalf>, pub connection_writer: Mutex<OwnedWriteHalf>,
pub client_packets_queue: Arc<Mutex<VecDeque<RawPacket>>>, pub client_packets_queue: Arc<Mutex<VecDeque<RawPacket>>>,
message_spam_cooldown: AtomicU32,
} }
impl Client { impl Client {
@ -55,6 +59,7 @@ impl Client {
connection_reader: Mutex::new(connection_reader), connection_reader: Mutex::new(connection_reader),
connection_writer: Mutex::new(connection_writer), connection_writer: Mutex::new(connection_writer),
client_packets_queue: Arc::new(Mutex::new(VecDeque::new())), client_packets_queue: Arc::new(Mutex::new(VecDeque::new())),
message_spam_cooldown: AtomicU32::new(1),
} }
} }
@ -147,6 +152,40 @@ impl Client {
} }
} }
// TODO: make this less ugly ig
pub async fn tick(&self, spam_message: Arc<Option<String>>, spam_message_delay: u32) {
if self.connection_state.load() != ConnectionState::Play {
return;
}
if let Some(spam_message) = spam_message.as_ref() {
if self
.message_spam_cooldown
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed)
<= 0
{
self.message_spam_cooldown
.store(spam_message_delay, std::sync::atomic::Ordering::Relaxed);
self.send_message(spam_message.clone()).await
}
}
}
pub async fn send_message(&self, message: String) {
let start = SystemTime::now();
let since_the_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
self.send_packet(&SChatMessage::new(
message,
since_the_epoch.as_millis() as i64,
rand::random(),
None,
VarInt(1),
vec![0; 20],
))
.await;
}
/// Sends a clientbound packet to the connected client. /// Sends a clientbound packet to the connected client.
/// ///
/// # Arguments /// # Arguments
@ -171,6 +210,7 @@ impl Client {
} }
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());
self.send_packet(&SHandShake { self.send_packet(&SHandShake {
protocol_version: VarInt(CURRENT_MC_PROTOCOL.get() as i32), protocol_version: VarInt(CURRENT_MC_PROTOCOL.get() as i32),
server_address: address.ip().to_string(), server_address: address.ip().to_string(),

View File

@ -1,4 +1,4 @@
use std::net::SocketAddr; use std::{net::SocketAddr, sync::Arc, thread::sleep, time::Duration};
use clap::Parser; use clap::Parser;
use client::Client; use client::Client;
@ -8,11 +8,17 @@ mod client;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
struct Args { pub struct Args {
#[arg(short, long)] #[arg(long)]
ip: String, ip: String,
#[arg(short, long, default_value_t = 1)] #[arg(short, long, default_value_t = 1)]
count: u32, count: u32,
#[arg(short, long, default_value_t = 200)]
delay: u64,
#[arg(long)]
pub spam_message: Option<String>,
#[arg(long, default_value_t = 210)]
pub spam_message_delay: u32,
} }
#[tokio::main] #[tokio::main]
@ -20,20 +26,28 @@ async fn main() {
simple_logger::init_with_level(log::Level::Info).unwrap(); simple_logger::init_with_level(log::Level::Info).unwrap();
let args = Args::parse(); let args = Args::parse();
let address = args.ip.parse::<SocketAddr>().unwrap(); let address = args.ip.parse::<SocketAddr>().unwrap();
log::info!("{} Bots will Join {}", args.count, address); log::info!(
"{} Bots will Join {} with a delay of {}ms",
args.count,
address,
args.delay
);
let mut join_handles = Vec::with_capacity(args.count as usize); let mut join_handles = Vec::with_capacity(args.count as usize);
for i in 0..args.count { for i in 0..args.count {
sleep(Duration::from_millis(args.delay));
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 = Client::new(stream);
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.poll().await {
break; break;
} }
client.tick(message.clone(), args.spam_message_delay).await;
client.process_packets().await; client.process_packets().await;
} }
}); });