[Protocol] Fix Minecraft Java query not being able to specify a hostname (#91)

* Make initial fix

* Fix imports

* Rename query_address to hostname and add this to the mc example

* Fix master_querant example not compiling

* Add extra safety on converting strings to Minecraft Varint strings

* Add docs to RequestSettings

* Fix formatting
This commit is contained in:
CosminPerRam 2023-09-01 22:21:08 +03:00 committed by GitHub
parent 211cd5fd5f
commit a56ca45de6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 28 deletions

View file

@ -126,7 +126,7 @@ fn main() -> GDResult<()> {
) )
} }
"mc" => println!("{:#?}", mc::query(ip, port)?), "mc" => println!("{:#?}", mc::query(ip, port)?),
"mc_java" => println!("{:#?}", mc::query_java(ip, port)?), "mc_java" => println!("{:#?}", mc::query_java(ip, port, None)?),
"mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?), "mc_bedrock" => println!("{:#?}", mc::query_bedrock(ip, port)?),
"mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?), "mc_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?),
"mc_legacy_vb1_8" => { "mc_legacy_vb1_8" => {

View file

@ -1,9 +1,28 @@
use gamedig::games::mc; use gamedig::games::mc;
use gamedig::protocols::minecraft::RequestSettings;
fn main() { fn main() {
// or Some(<port>), None is the default protocol port (which is 25565 for java // or Some(<port>), None is the default protocol port (which is 25565 for java
// and 19132 for bedrock) // and 19132 for bedrock)
let response = mc::query(&"127.0.0.1".parse().unwrap(), None); let response = mc::query(&"127.0.0.1".parse().unwrap(), None);
// This will fail if no server is available locally!
match response {
Err(error) => println!("Couldn't query, error: {}", error),
Ok(r) => println!("{:#?}", r),
}
// This is an example to query a server with a hostname to be specified in the
// packet. Passing -1 on the protocol_version means anything, note that
// an invalid value here might result in server not responding.
let response = mc::query_java(
&"209.222.114.62".parse().unwrap(),
Some(25565),
Some(RequestSettings {
hostname: "mc.hypixel.net".to_string(),
protocol_version: -1,
}),
);
match response { match response {
Err(error) => println!("Couldn't query, error: {}", error), Err(error) => println!("Couldn't query, error: {}", error),

View file

@ -1,3 +1,4 @@
use crate::protocols::minecraft::RequestSettings;
use crate::{ use crate::{
protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup}, protocols::minecraft::{self, BedrockResponse, JavaResponse, LegacyGroup},
GDErrorKind, GDErrorKind,
@ -8,7 +9,7 @@ use std::net::{IpAddr, SocketAddr};
/// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy /// Query with all the protocol variants one by one (Java -> Bedrock -> Legacy
/// (1.6 -> 1.4 -> Beta 1.8)). /// (1.6 -> 1.4 -> Beta 1.8)).
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> { pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> {
if let Ok(response) = query_java(address, port) { if let Ok(response) = query_java(address, port, None) {
return Ok(response); return Ok(response);
} }
@ -24,8 +25,16 @@ pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> {
} }
/// Query a Java Server. /// Query a Java Server.
pub fn query_java(address: &IpAddr, port: Option<u16>) -> GDResult<JavaResponse> { pub fn query_java(
minecraft::query_java(&SocketAddr::new(*address, port_or_java_default(port)), None) address: &IpAddr,
port: Option<u16>,
request_settings: Option<RequestSettings>,
) -> GDResult<JavaResponse> {
minecraft::query_java(
&SocketAddr::new(*address, port_or_java_default(port)),
None,
request_settings,
)
} }
/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8).

View file

@ -159,7 +159,7 @@ pub fn query_with_timeout(
Protocol::Minecraft(version) => { Protocol::Minecraft(version) => {
match version { match version {
Some(protocols::minecraft::Server::Java) => { Some(protocols::minecraft::Server::Java) => {
protocols::minecraft::query_java(&socket_addr, timeout_settings).map(Box::new)? protocols::minecraft::query_java(&socket_addr, timeout_settings, None).map(Box::new)?
} }
Some(protocols::minecraft::Server::Bedrock) => { Some(protocols::minecraft::Server::Bedrock) => {
protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)? protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?

View file

@ -11,33 +11,28 @@ use crate::{
use std::net::SocketAddr; use std::net::SocketAddr;
use crate::protocols::minecraft::{as_string, RequestSettings};
use byteorder::LittleEndian; use byteorder::LittleEndian;
use serde_json::Value; use serde_json::Value;
#[rustfmt::skip]
const PAYLOAD: [u8; 17] = [
//Packet ID (0)
0x00,
//Protocol Version (-1 to determine version)
0xFF, 0xFF, 0xFF, 0xFF, 0x0F,
//Server address (can be anything)
0x07, 0x47, 0x61, 0x6D, 0x65, 0x44, 0x69, 0x67,
//Server port (can be anything)
0x00, 0x00,
//Next state (1 for status)
0x01
];
pub struct Java { pub struct Java {
socket: TcpSocket, socket: TcpSocket,
request_settings: RequestSettings,
} }
impl Java { impl Java {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(
address: &SocketAddr,
timeout_settings: Option<TimeoutSettings>,
request_settings: Option<RequestSettings>,
) -> GDResult<Self> {
let socket = TcpSocket::new(address)?; let socket = TcpSocket::new(address)?;
socket.apply_timeout(timeout_settings)?; socket.apply_timeout(timeout_settings)?;
Ok(Self { socket }) Ok(Self {
socket,
request_settings: request_settings.unwrap_or_default(),
})
} }
fn send(&mut self, data: Vec<u8>) -> GDResult<()> { fn send(&mut self, data: Vec<u8>) -> GDResult<()> {
@ -57,7 +52,24 @@ impl Java {
} }
fn send_handshake(&mut self) -> GDResult<()> { fn send_handshake(&mut self) -> GDResult<()> {
self.send(PAYLOAD.to_vec())?; let handshake_payload = [
&[
// Packet ID (0)
0x00,
], // Protocol Version (-1 to determine version)
as_varint(self.request_settings.protocol_version).as_slice(),
// Server address (can be anything)
as_string(&self.request_settings.hostname)?.as_slice(),
// Server port (can be anything)
&self.socket.port().to_le_bytes(),
&[
// Next state (1 for status)
0x01,
],
]
.concat();
self.send(handshake_payload)?;
Ok(()) Ok(())
} }
@ -143,7 +155,11 @@ impl Java {
}) })
} }
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> { pub fn query(
Self::new(address, timeout_settings)?.get_info() address: &SocketAddr,
timeout_settings: Option<TimeoutSettings>,
request_settings: Option<RequestSettings>,
) -> GDResult<JavaResponse> {
Self::new(address, timeout_settings, request_settings)?.get_info()
} }
} }

View file

@ -1,3 +1,4 @@
use crate::protocols::minecraft::types::RequestSettings;
use crate::{ use crate::{
protocols::minecraft::{ protocols::minecraft::{
protocol::{ protocol::{
@ -26,7 +27,7 @@ mod legacy_v1_6;
/// Queries a Minecraft server with all the protocol variants one by one (Java /// Queries a Minecraft server with all the protocol variants one by one (Java
/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)). /// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)).
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> { pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
if let Ok(response) = query_java(address, timeout_settings.clone()) { if let Ok(response) = query_java(address, timeout_settings.clone(), None) {
return Ok(response); return Ok(response);
} }
@ -42,8 +43,12 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
} }
/// Query a Java Server. /// Query a Java Server.
pub fn query_java(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> { pub fn query_java(
Java::query(address, timeout_settings) address: &SocketAddr,
timeout_settings: Option<TimeoutSettings>,
request_settings: Option<RequestSettings>,
) -> GDResult<JavaResponse> {
Java::query(address, timeout_settings, request_settings)
} }
/// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8). /// Query a (Java) Legacy Server (1.6 -> 1.4 -> Beta 1.8).

View file

@ -8,7 +8,7 @@ use crate::{
types::{CommonPlayer, CommonResponse, GenericPlayer}, types::{CommonPlayer, CommonResponse, GenericPlayer},
GenericResponse, GenericResponse,
}, },
GDErrorKind::{PacketBad, UnknownEnumCast}, GDErrorKind::{InvalidInput, PacketBad, UnknownEnumCast},
GDResult, GDResult,
}; };
@ -88,6 +88,28 @@ pub struct JavaResponse {
pub server_type: Server, pub server_type: Server,
} }
/// Java-only additional request settings.
pub struct RequestSettings {
/// Some Minecraft servers do not respond as expected if this
/// isn't a specific value, `mc.hypixel.net` is an example.
pub hostname: String,
/// Specifies the client [protocol version number](https://wiki.vg/Protocol_version_numbers),
/// `-1` means anything.
pub protocol_version: i32,
}
impl Default for RequestSettings {
/// `hostname`: "gamedig"
///
/// `protocol_version`: -1
fn default() -> Self {
Self {
hostname: "gamedig".to_string(),
protocol_version: -1,
}
}
}
impl CommonResponse for JavaResponse { impl CommonResponse for JavaResponse {
fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) } fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) }
@ -238,3 +260,14 @@ pub(crate) fn get_string<B: ByteOrder>(buffer: &mut Buffer<B>) -> GDResult<Strin
String::from_utf8(text).map_err(|e| PacketBad.context(e)) String::from_utf8(text).map_err(|e| PacketBad.context(e))
} }
pub(crate) fn as_string(value: &str) -> GDResult<Vec<u8>> {
let length = value
.len()
.try_into()
.map_err(|e| InvalidInput.context(e))?;
let mut buf = as_varint(length);
buf.extend(value.as_bytes());
Ok(buf)
}

View file

@ -20,16 +20,20 @@ pub trait Socket {
fn send(&mut self, data: &[u8]) -> GDResult<()>; fn send(&mut self, data: &[u8]) -> GDResult<()>;
fn receive(&mut self, size: Option<usize>) -> GDResult<Vec<u8>>; fn receive(&mut self, size: Option<usize>) -> GDResult<Vec<u8>>;
fn port(&self) -> u16;
} }
pub struct TcpSocket { pub struct TcpSocket {
socket: net::TcpStream, socket: net::TcpStream,
address: SocketAddr,
} }
impl Socket for TcpSocket { impl Socket for TcpSocket {
fn new(address: &SocketAddr) -> GDResult<Self> { fn new(address: &SocketAddr) -> GDResult<Self> {
Ok(Self { Ok(Self {
socket: net::TcpStream::connect(address).map_err(|e| SocketConnect.context(e))?, socket: net::TcpStream::connect(address).map_err(|e| SocketConnect.context(e))?,
address: *address,
}) })
} }
@ -54,6 +58,8 @@ impl Socket for TcpSocket {
Ok(buf) Ok(buf)
} }
fn port(&self) -> u16 { self.address.port() }
} }
pub struct UdpSocket { pub struct UdpSocket {
@ -96,6 +102,8 @@ impl Socket for UdpSocket {
Ok(buf[.. number_of_bytes_received].to_vec()) Ok(buf[.. number_of_bytes_received].to_vec())
} }
fn port(&self) -> u16 { self.address.port() }
} }
#[cfg(test)] #[cfg(test)]