diff --git a/crates/pumpkin-protocol/src/bedrock/status.rs b/crates/pumpkin-protocol/src/bedrock/status.rs index 36325c0e7..73c28428a 100644 --- a/crates/pumpkin-protocol/src/bedrock/status.rs +++ b/crates/pumpkin-protocol/src/bedrock/status.rs @@ -61,6 +61,26 @@ impl PacketWrite for CUnconnectedPong { } } +#[derive(PacketWrite)] +#[packet(0x19)] +pub struct CIncompatibleProtocolVersion { + protocol_version: u8, + magic: [u8; 16], + #[serial(big_endian)] + server_guid: u64, +} + +impl CIncompatibleProtocolVersion { + #[must_use] + pub const fn new(protocol_version: u8, server_guid: u64) -> Self { + Self { + protocol_version, + magic: OFFLINE_MESSAGE_MAGIC, + server_guid, + } + } +} + pub struct ServerInfo<'a> { pub motd: &'a str, pub protocol: u32, @@ -120,4 +140,16 @@ mod tests { "MCPE;Pumpkin;2168;1.26.40;2;20;42;world;Creative;1;19132;19133;0;" ); } + + #[test] + fn encodes_raknet_incompatible_protocol_response() { + let mut response = Vec::new(); + CIncompatibleProtocolVersion::new(12, 42) + .write(&mut response) + .unwrap(); + + assert_eq!(response[0], 12); + assert_eq!(response[1..17], OFFLINE_MESSAGE_MAGIC); + assert_eq!(response[17..], 42u64.to_be_bytes()); + } } diff --git a/crates/pumpkin/src/net/bedrock/login/request_network_settings.rs b/crates/pumpkin/src/net/bedrock/login/request_network_settings.rs index 76fcd45e2..875d9a1ee 100644 --- a/crates/pumpkin/src/net/bedrock/login/request_network_settings.rs +++ b/crates/pumpkin/src/net/bedrock/login/request_network_settings.rs @@ -6,13 +6,19 @@ impl BedrockClient { &self, packet: SRequestNetworkSettings, server: &Server, - ) { - if packet.protocol_version < CURRENT_BEDROCK_MC_PROTOCOL as i32 { - self.send_packet(&CPlayStatus::OutdatedClient).await; - return; - } else if packet.protocol_version > CURRENT_BEDROCK_MC_PROTOCOL as i32 { - self.send_packet(&CPlayStatus::OutdatedServer).await; - return; + ) -> bool { + let status = match packet + .protocol_version + .cmp(&(CURRENT_BEDROCK_MC_PROTOCOL as i32)) + { + std::cmp::Ordering::Less => Some(CPlayStatus::OutdatedClient), + std::cmp::Ordering::Greater => Some(CPlayStatus::OutdatedServer), + std::cmp::Ordering::Equal => None, + }; + if let Some(status) = status { + self.send_packet(&status).await; + self.close().await; + return false; } self.version.store(BedrockMinecraftVersion::from_protocol( @@ -35,5 +41,6 @@ impl BedrockClient { )) .await; self.set_compression(compression).await; + true } } diff --git a/crates/pumpkin/src/net/bedrock/mod.rs b/crates/pumpkin/src/net/bedrock/mod.rs index 07d57b21b..d39973b82 100644 --- a/crates/pumpkin/src/net/bedrock/mod.rs +++ b/crates/pumpkin/src/net/bedrock/mod.rs @@ -596,7 +596,9 @@ impl BedrockClient { continue; } }; - self.handle_request_network_settings(packet, server).await; + if !self.handle_request_network_settings(packet, server).await { + return PacketHandlerResult::Stop; + } } SLogin::PACKET_ID => { let packet = match SLogin::read(payload) { diff --git a/crates/pumpkin/src/net/bedrock/status.rs b/crates/pumpkin/src/net/bedrock/status.rs index ad3d1cb09..2fc44c023 100644 --- a/crates/pumpkin/src/net/bedrock/status.rs +++ b/crates/pumpkin/src/net/bedrock/status.rs @@ -9,8 +9,8 @@ use bytes::Bytes; use pumpkin_protocol::{ BClientPacket, bedrock::status::{ - CUnconnectedPong, OFFLINE_MESSAGE_MAGIC, SUnconnectedPing, SUnconnectedPingOpenConnections, - ServerInfo, + CIncompatibleProtocolVersion, CUnconnectedPong, OFFLINE_MESSAGE_MAGIC, SUnconnectedPing, + SUnconnectedPingOpenConnections, ServerInfo, }, packet::Packet, serial::PacketRead, @@ -75,6 +75,8 @@ impl StatusResponder { if is_status_packet(packet) { trace!(%client, length, "Received Bedrock server-list status ping"); self.respond(server, &self.ipv4, packet, client).await + } else if let Some(client_protocol) = raknet_protocol_version(packet) { + self.reject_legacy_raknet(server, &self.ipv4, client, client_protocol).await } else { trace!( %client, @@ -91,11 +93,32 @@ impl StatusResponder { result = self.ipv6.recv_from(&mut ipv6_buffer) => { let (length, client) = result?; trace!(%client, length, "Received Bedrock IPv6 server-list status packet"); - self.respond(server, &self.ipv6, &ipv6_buffer[..length], client).await + let packet = &ipv6_buffer[..length]; + if let Some(client_protocol) = raknet_protocol_version(packet) { + self.reject_legacy_raknet(server, &self.ipv6, client, client_protocol).await + } else { + self.respond(server, &self.ipv6, packet, client).await + } } } } + async fn reject_legacy_raknet( + &self, + server: &Server, + socket: &UdpSocket, + client: SocketAddr, + client_protocol: u8, + ) -> Result<(), Error> { + let server_protocol = client_protocol.saturating_add(1); + let packet = CIncompatibleProtocolVersion::new(server_protocol, server.server_guid); + let mut response = vec![CIncompatibleProtocolVersion::PACKET_ID as u8]; + packet.write_packet(&mut response)?; + socket.send_to(&response, client).await?; + trace!(%client, client_protocol, server_protocol, "Rejected unsupported Bedrock RakNet connection"); + Ok(()) + } + async fn respond( &self, server: &Server, @@ -125,6 +148,12 @@ fn is_status_packet(packet: &[u8]) -> bool { && packet.get(9..25) == Some(OFFLINE_MESSAGE_MAGIC.as_slice()) } +fn raknet_protocol_version(packet: &[u8]) -> Option { + (packet.first() == Some(&0x05) && packet.get(1..17) == Some(OFFLINE_MESSAGE_MAGIC.as_slice())) + .then(|| packet.get(17).copied()) + .flatten() +} + fn ice_packet_kind(packet: &[u8]) -> &'static str { if packet.len() >= 20 && packet.get(4..8) == Some(&[0x21, 0x12, 0xa4, 0x42]) { "STUN" @@ -231,6 +260,12 @@ mod tests { stun_success[4..8].copy_from_slice(&[0x21, 0x12, 0xa4, 0x42]); assert!(!is_status_packet(&stun_success)); assert_eq!(ice_packet_kind(&stun_success), "STUN"); + + let mut open_connection = [0; 18]; + open_connection[0] = 0x05; + open_connection[1..17].copy_from_slice(&OFFLINE_MESSAGE_MAGIC); + open_connection[17] = 11; + assert_eq!(raknet_protocol_version(&open_connection), Some(11)); } #[tokio::test]