[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_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_legacy" => println!("{:#?}", mc::query_legacy(ip, port)?),
"mc_legacy_vb1_8" => {

View file

@ -1,9 +1,28 @@
use gamedig::games::mc;
use gamedig::protocols::minecraft::RequestSettings;
fn main() {
// or Some(<port>), None is the default protocol port (which is 25565 for java
// and 19132 for bedrock)
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 {
Err(error) => println!("Couldn't query, error: {}", error),

View file

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

View file

@ -159,7 +159,7 @@ pub fn query_with_timeout(
Protocol::Minecraft(version) => {
match version {
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) => {
protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?

View file

@ -11,33 +11,28 @@ use crate::{
use std::net::SocketAddr;
use crate::protocols::minecraft::{as_string, RequestSettings};
use byteorder::LittleEndian;
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 {
socket: TcpSocket,
request_settings: RequestSettings,
}
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)?;
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<()> {
@ -57,7 +52,24 @@ impl Java {
}
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(())
}
@ -143,7 +155,11 @@ impl Java {
})
}
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
Self::new(address, timeout_settings)?.get_info()
pub fn query(
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::{
protocols::minecraft::{
protocol::{
@ -26,7 +27,7 @@ mod legacy_v1_6;
/// Queries a Minecraft server with all the protocol variants one by one (Java
/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)).
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);
}
@ -42,8 +43,12 @@ pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) ->
}
/// Query a Java Server.
pub fn query_java(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
Java::query(address, timeout_settings)
pub fn query_java(
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).

View file

@ -8,7 +8,7 @@ use crate::{
types::{CommonPlayer, CommonResponse, GenericPlayer},
GenericResponse,
},
GDErrorKind::{PacketBad, UnknownEnumCast},
GDErrorKind::{InvalidInput, PacketBad, UnknownEnumCast},
GDResult,
};
@ -88,6 +88,28 @@ pub struct JavaResponse {
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 {
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))
}
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 receive(&mut self, size: Option<usize>) -> GDResult<Vec<u8>>;
fn port(&self) -> u16;
}
pub struct TcpSocket {
socket: net::TcpStream,
address: SocketAddr,
}
impl Socket for TcpSocket {
fn new(address: &SocketAddr) -> GDResult<Self> {
Ok(Self {
socket: net::TcpStream::connect(address).map_err(|e| SocketConnect.context(e))?,
address: *address,
})
}
@ -54,6 +58,8 @@ impl Socket for TcpSocket {
Ok(buf)
}
fn port(&self) -> u16 { self.address.port() }
}
pub struct UdpSocket {
@ -96,6 +102,8 @@ impl Socket for UdpSocket {
Ok(buf[.. number_of_bytes_received].to_vec())
}
fn port(&self) -> u16 { self.address.port() }
}
#[cfg(test)]