fix: bedrock protocol version check

This commit is contained in:
Alexander Medvedev 2026-04-26 19:45:22 +02:00
parent 5847c90a44
commit 3c6f15d5eb
7 changed files with 91 additions and 50 deletions

View File

@ -1,6 +1,6 @@
use std::io::{Error, Read, Write}; use std::io::{Error, Read, Write};
use crate::bedrock::{RAKNET_SPLIT, RakReliability}; use crate::bedrock::{MTU, RAKNET_SPLIT, RakReliability};
use crate::codec::u24; use crate::codec::u24;
use crate::serial::{PacketRead, PacketWrite}; use crate::serial::{PacketRead, PacketWrite};
@ -68,15 +68,30 @@ impl Frame {
pub fn read<R: Read>(reader: &mut R) -> Result<Vec<Self>, Error> { pub fn read<R: Read>(reader: &mut R) -> Result<Vec<Self>, Error> {
let mut frames = Vec::new(); let mut frames = Vec::new();
let mut header_buf = [0u8; 1];
while let Ok(header) = u8::read(reader) { while match reader.read(&mut header_buf) {
Ok(0) => false,
Ok(1) => true,
Err(e) => return Err(e),
_ => unreachable!(),
} {
let header = header_buf[0];
let mut frame = Self::default(); let mut frame = Self::default();
let reliability_id = (header & 0xE0) >> 5; let reliability_id = (header & 0xE0) >> 5;
let Some(reliability) = RakReliability::from_id(reliability_id) else { let Some(reliability) = RakReliability::from_id(reliability_id) else {
return Err(Error::other("Invalid reliability")); return Err(Error::other("Invalid reliability"));
}; };
let split = (header & RAKNET_SPLIT) != 0; let split = (header & RAKNET_SPLIT) != 0;
let length = u16::read_be(reader)? >> 3;
let bit_length = u16::read_be(reader)?;
let byte_length = (bit_length + 7) >> 3;
if byte_length > MTU as u16 {
return Err(Error::other("Frame payload length exceeds RakNet MTU"));
}
if reliability.is_reliable() { if reliability.is_reliable() {
frame.reliable_number = u24::read(reader)?.0; frame.reliable_number = u24::read(reader)?.0;
@ -98,8 +113,9 @@ impl Frame {
} }
frame.reliability = reliability; frame.reliability = reliability;
frame.payload = vec![0; length as usize]; frame.payload = vec![0; byte_length as usize];
reader.read_exact(&mut frame.payload)?; reader.read_exact(&mut frame.payload)?;
frames.push(frame); frames.push(frame);
} }

View File

@ -6,6 +6,7 @@ pub mod packet_decoder;
pub mod packet_encoder; pub mod packet_encoder;
pub mod server; pub mod server;
pub const RAKNET_PROTOCOL_VERSION: u8 = 11;
pub const UDP_HEADER_SIZE: u16 = 28; pub const UDP_HEADER_SIZE: u16 = 28;
pub const MTU: usize = 1400; pub const MTU: usize = 1400;

View File

@ -129,11 +129,16 @@ impl SStatusRequest {
.await .await
.map_err(|_| DecodeError::UnexpectedEof)?; .map_err(|_| DecodeError::UnexpectedEof)?;
let mut buf = [0u8; 4]; let position = packet.reader.position();
let is_full_request = match packet.reader.read(&mut buf).await { let total_len = packet.reader.get_ref().len() as u64;
Ok(0) => false, let remaining = total_len.saturating_sub(position);
Ok(4) => true,
// Any other byte count is malformed. let is_full_request = match remaining {
0 => false,
4 => {
packet.reader.set_position(position + 4);
true
}
_ => return Err(DecodeError::MalformedPayload), _ => return Err(DecodeError::MalformedPayload),
}; };

View File

@ -18,7 +18,7 @@ use pumpkin_protocol::{
codec::var_uint::VarUInt, codec::var_uint::VarUInt,
}; };
use pumpkin_util::jwt::{AuthError, verify_chain}; use pumpkin_util::jwt::{AuthError, verify_chain};
use pumpkin_world::CURRENT_BEDROCK_MC_VERSION; use pumpkin_world::{CURRENT_BEDROCK_MC_PROTOCOL, CURRENT_BEDROCK_MC_VERSION};
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
@ -49,7 +49,14 @@ struct CertificateChainPayload {
} }
impl BedrockClient { impl BedrockClient {
pub async fn handle_request_network_settings(&self, _packet: SRequestNetworkSettings) { pub async fn handle_request_network_settings(&self, packet: SRequestNetworkSettings) {
if packet.protocol_version < CURRENT_BEDROCK_MC_PROTOCOL as i32 {
self.send_game_packet(&CPlayStatus::OutdatedClient).await;
return;
} else if packet.protocol_version > CURRENT_BEDROCK_MC_PROTOCOL as i32 {
self.send_game_packet(&CPlayStatus::OutdatedServer).await;
return;
}
self.send_game_packet(&CNetworkSettings::new(0, 0, false, 0, 0.0)) self.send_game_packet(&CNetworkSettings::new(0, 0, false, 0, 0.0))
.await; .await;
self.set_compression(CompressionInfo::default()).await; self.set_compression(CompressionInfo::default()).await;

View File

@ -1,13 +1,13 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use pumpkin_protocol::bedrock::{ use pumpkin_protocol::bedrock::{
RAKNET_PROTOCOL_VERSION,
client::raknet::{ client::raknet::{
incompatible_protocol::CIncompatibleProtocolVersion, incompatible_protocol::CIncompatibleProtocolVersion,
open_connection::{COpenConnectionReply1, COpenConnectionReply2}, open_connection::{COpenConnectionReply1, COpenConnectionReply2},
}, },
server::raknet::open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2}, server::raknet::open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
}; };
use pumpkin_world::CURRENT_BEDROCK_MC_PROTOCOL;
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use crate::{net::bedrock::BedrockClient, server::Server}; use crate::{net::bedrock::BedrockClient, server::Server};
@ -19,12 +19,9 @@ impl BedrockClient {
addr: SocketAddr, addr: SocketAddr,
socket: &UdpSocket, socket: &UdpSocket,
) { ) {
if packet.protocol_version != CURRENT_BEDROCK_MC_PROTOCOL as u8 { if packet.protocol_version != RAKNET_PROTOCOL_VERSION {
Self::send_offline_packet( Self::send_offline_packet(
&CIncompatibleProtocolVersion::new( &CIncompatibleProtocolVersion::new(RAKNET_PROTOCOL_VERSION, server.server_guid),
CURRENT_BEDROCK_MC_PROTOCOL as u8,
server.server_guid,
),
addr, addr,
socket, socket,
) )

View File

@ -97,7 +97,7 @@ impl RCONClient {
} }
async fn poll(&mut self, server: &Arc<Server>, password: &str) -> Result<(), PacketError> { async fn poll(&mut self, server: &Arc<Server>, password: &str) -> Result<(), PacketError> {
let Some(packet) = self.receive_packet().await? else { let Some(packet) = self.receive_packet()? else {
return Ok(()); return Ok(());
}; };
let config = &server.advanced_config.networking.rcon; let config = &server.advanced_config.networking.rcon;
@ -178,7 +178,7 @@ impl RCONClient {
Ok(()) Ok(())
} }
async fn receive_packet(&mut self) -> Result<Option<Packet>, PacketError> { fn receive_packet(&mut self) -> Result<Option<Packet>, PacketError> {
Packet::deserialize(&mut self.incoming).await Packet::deserialize(&mut self.incoming)
} }
} }

View File

@ -1,11 +1,7 @@
use std::{ use std::string::FromUtf8Error;
io::{BufRead, Cursor},
string::FromUtf8Error,
};
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use thiserror::Error; use thiserror::Error;
use tokio::io::AsyncReadExt;
/// Client -> Server /// Client -> Server
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -57,10 +53,10 @@ impl ClientboundPacket {
pub enum PacketError { pub enum PacketError {
#[error("Invalid length")] #[error("Invalid length")]
InvalidLength, InvalidLength,
#[error("Failed to read packet")]
FailedRead(std::io::Error),
#[error("Dailed to send packet")] #[error("Dailed to send packet")]
FailedSend(std::io::Error), FailedSend(std::io::Error),
#[error("Missing packet lull terminator")]
MissingNullTerminator,
#[error("Invalid packet string body")] #[error("Invalid packet string body")]
InvalidBody(FromUtf8Error), InvalidBody(FromUtf8Error),
} }
@ -74,36 +70,55 @@ pub struct Packet {
} }
impl Packet { impl Packet {
pub async fn deserialize(incoming: &mut Vec<u8>) -> Result<Option<Self>, PacketError> { pub fn deserialize(incoming: &mut Vec<u8>) -> Result<Option<Self>, PacketError> {
// We need at least 4 bytes to read the packet length header
if incoming.len() < 4 { if incoming.len() < 4 {
return Ok(None); return Ok(None);
} }
let mut buf = Cursor::new(&incoming);
let len = buf.read_i32_le().await.map_err(PacketError::FailedRead)? + 4;
if !(0..=1460).contains(&len) {
return Err(PacketError::InvalidLength);
}
let id = buf.read_i32_le().await.map_err(PacketError::FailedRead)?;
let ty = buf.read_i32_le().await.map_err(PacketError::FailedRead)?;
let mut payload = vec![];
let _ = buf
.read_until(b'\0', &mut payload)
.map_err(PacketError::FailedRead)?;
payload.pop();
buf.read_u8().await.map_err(PacketError::FailedRead)?;
if buf.position() != len as u64 {
return Err(PacketError::InvalidLength);
}
incoming.drain(0..len as usize);
let packet = Self { // Read the 4-byte length header (little-endian)
let len_bytes: [u8; 4] = incoming[0..4].try_into().unwrap();
let size = i32::from_le_bytes(len_bytes);
// An RCON packet size includes: ID(4) + Type(4) + Body(n) + Null(1) + EmptyStringNull(1).
// Minimum size for an empty body is 10. Maximum MTU size is typically 1460.
if !(10..=1460).contains(&size) {
return Err(PacketError::InvalidLength);
}
// Total bytes we need in the buffer (length header + the payload size)
let total_packet_len = (size as usize) + 4;
// If the network hasn't delivered the full packet yet, return Ok(None)
if incoming.len() < total_packet_len {
return Ok(None);
}
// We have the full packet. Parse it synchronously using fixed slices.
let id = i32::from_le_bytes(incoming[4..8].try_into().unwrap());
let ty = i32::from_le_bytes(incoming[8..12].try_into().unwrap());
// Calculate body boundaries (starts after len, id, and ty -> 4+4+4 = 12)
// Ends 2 bytes before the total length (excluding the two trailing null bytes)
let body_start = 12;
let body_end = total_packet_len - 2;
if incoming[body_end] != 0 || incoming[body_end + 1] != 0 {
return Err(PacketError::MissingNullTerminator);
}
let payload = &incoming[body_start..body_end];
let body = String::from_utf8(payload.to_vec()).map_err(PacketError::InvalidBody)?;
incoming.drain(0..total_packet_len);
Ok(Some(Self {
id, id,
ptype: ServerboundPacket::from_i32(ty), ptype: ServerboundPacket::from_i32(ty),
body: String::from_utf8(payload).map_err(PacketError::InvalidBody)?, body,
}; }))
Ok(Some(packet))
} }
pub fn get_body(&self) -> &str { pub fn get_body(&self) -> &str {
&self.body &self.body
} }