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 crate::bedrock::{RAKNET_SPLIT, RakReliability};
use crate::bedrock::{MTU, RAKNET_SPLIT, RakReliability};
use crate::codec::u24;
use crate::serial::{PacketRead, PacketWrite};
@ -68,15 +68,30 @@ impl Frame {
pub fn read<R: Read>(reader: &mut R) -> Result<Vec<Self>, Error> {
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 reliability_id = (header & 0xE0) >> 5;
let Some(reliability) = RakReliability::from_id(reliability_id) else {
return Err(Error::other("Invalid reliability"));
};
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() {
frame.reliable_number = u24::read(reader)?.0;
@ -98,8 +113,9 @@ impl Frame {
}
frame.reliability = reliability;
frame.payload = vec![0; length as usize];
frame.payload = vec![0; byte_length as usize];
reader.read_exact(&mut frame.payload)?;
frames.push(frame);
}

View File

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

View File

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

View File

@ -18,7 +18,7 @@ use pumpkin_protocol::{
codec::var_uint::VarUInt,
};
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 std::sync::Arc;
use thiserror::Error;
@ -49,7 +49,14 @@ struct CertificateChainPayload {
}
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))
.await;
self.set_compression(CompressionInfo::default()).await;

View File

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

View File

@ -97,7 +97,7 @@ impl RCONClient {
}
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(());
};
let config = &server.advanced_config.networking.rcon;
@ -178,7 +178,7 @@ impl RCONClient {
Ok(())
}
async fn receive_packet(&mut self) -> Result<Option<Packet>, PacketError> {
Packet::deserialize(&mut self.incoming).await
fn receive_packet(&mut self) -> Result<Option<Packet>, PacketError> {
Packet::deserialize(&mut self.incoming)
}
}

View File

@ -1,11 +1,7 @@
use std::{
io::{BufRead, Cursor},
string::FromUtf8Error,
};
use std::string::FromUtf8Error;
use bytes::{BufMut, BytesMut};
use thiserror::Error;
use tokio::io::AsyncReadExt;
/// Client -> Server
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -57,10 +53,10 @@ impl ClientboundPacket {
pub enum PacketError {
#[error("Invalid length")]
InvalidLength,
#[error("Failed to read packet")]
FailedRead(std::io::Error),
#[error("Dailed to send packet")]
FailedSend(std::io::Error),
#[error("Missing packet lull terminator")]
MissingNullTerminator,
#[error("Invalid packet string body")]
InvalidBody(FromUtf8Error),
}
@ -74,36 +70,55 @@ pub struct 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 {
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,
ptype: ServerboundPacket::from_i32(ty),
body: String::from_utf8(payload).map_err(PacketError::InvalidBody)?,
};
Ok(Some(packet))
body,
}))
}
pub fn get_body(&self) -> &str {
&self.body
}