GameSpy4 Query Protocol
Pumpkin includes native support for the GameSpy4 Query protocol over UDP. External monitoring systems, Discord bots, and server listing platforms use Query to poll real-time player counts, MOTD, server versions, and active player lists with minimal network overhead.
Query wire encoding is implemented in crates/pumpkin-protocol/src/query.rs, and the asynchronous UDP listener is implemented in crates/pumpkin/src/net/query.rs.
Why Query? (UDP vs. Status Ping)
While standard Minecraft clients ping servers via the TCP Status state (SLP), Query operates over stateless UDP:
- No TCP Handshake: Avoids TCP connection overhead and connection state tables.
- Detailed Server State: Returns comprehensive key-value metadata and a full list of connected player usernames (not available via basic TCP ping).
- Amplification Attack Protection: Enforces cryptographic challenge tokens so malicious actors cannot abuse the UDP port for denial-of-service reflection attacks.
The Handshake & Challenge Token Flow
To prevent UDP spoofing and reflection DDoS attacks, Query requires a two-step handshake:
- Handshake Challenge: The client transmits
SHandshake(0xFEFD, type0x09, and a 32-bit session ID). Pumpkin generates a cryptographic 32-bit integer challenge token tied to the client'sSocketAddrand responds withCHandshake. - Status Query: The client sends
SStatusRequestcontaining the received challenge token:- Basic Status (no padding): Returns MOTD, game type (
SMP), map name, online player count, and max player capacity. - Full Status (4-byte padding): Returns detailed server metadata key-value pairs (
hostname,gametype,game_id,version,plugins,map,numplayers,maxplayers,hostport,hostip) followed by an array of online player usernames.
- Basic Status (no padding): Returns MOTD, game type (
Challenge Token Invalidation
In crates/pumpkin/src/net/query.rs, challenge tokens are stored in a thread-safe map (Arc<RwLock<HashMap<SocketAddr, i32>>>). A background Tokio timer purges all challenge tokens every 30 seconds to prevent replay abuse:
// Tokens are strictly bound to the client's IP and port
let valid_challenge_tokens = Arc::new(RwLock::new(HashMap::new()));
// Background expiration loop
tokio::spawn(async move {
let mut interval = time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
valid_challenge_tokens.write().await.clear();
}
});Packet Formats
Every incoming Query packet starts with the 2-byte magic header 0xFEFD followed by a 1-byte packet type:
| Type Byte | Name | Direction | Description |
|---|---|---|---|
0x09 | Handshake Request | Client → Server | Client asks for a challenge token. |
0x09 | Handshake Response | Server → Client | Returns the numeric token encoded as an ASCII string. |
0x00 | Stat Request | Client → Server | Requests basic or full status with challenge token. |
0x00 | Basic Stat Response | Server → Client | 11-byte short summary. |
0x00 | Full Stat Response | Server → Client | Key-value dictionary + null-separated player list. |
Response Payloads
1. Basic Status (CBasicStatus)
Contains essential server fields:
motd: Server message of the day (stripped of color codes).gametype: Fixed string"SMP".map: Active world/level name (e.g.,"world").numplayers: Online player count.maxplayers: Maximum configured player capacity.hostport: Port number (u16Little Endian).hostip: Server IP string.
2. Full Status (CFullStatus)
Returns two null-terminated sections:
- Key-Value Metadata Map:
hostname: Server description / MOTD.gametype:"SMP".game_id:"MINECRAFT".version: Server protocol / Minecraft version string.plugins: Installed server software and plugin names.map: Level name.numplayers/maxplayers: Capacity stats.hostport/hostip: Network bindings.
- Player List:
- An array of null-terminated UTF-8 strings containing the usernames of all currently connected players.
- Concludes with a terminal null byte (
\0).
Configuration in pumpkin.toml
[networking.query]
enabled = true
address = "0.0.0.0:25565"