chore: some code cleanup
- remove one unwrap - move `mc_server` module to folder and create `::helpers` submodule
This commit is contained in:
parent
eac1c5d8fa
commit
241504ac3b
5 changed files with 87 additions and 80 deletions
273
src/mc_server.rs
273
src/mc_server.rs
|
|
@ -1,273 +0,0 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::{
|
||||
packets::{
|
||||
clientbound::status::{StatusStructNew, StatusTrait},
|
||||
serverbound::handshake::Handshake,
|
||||
Packet, SendPacket,
|
||||
},
|
||||
OpaqueError,
|
||||
};
|
||||
|
||||
#[tracing::instrument(skip(client_stream))]
|
||||
pub async fn handle_ping(client_stream: &mut TcpStream) -> Result<(), OpaqueError> {
|
||||
// --- Respond to ping packet ---
|
||||
let ping_packet = Packet::parse(client_stream).await?;
|
||||
match ping_packet.id.get_int() {
|
||||
1 => Ok(ping_packet
|
||||
.send_packet(client_stream)
|
||||
.await
|
||||
.map_err(|_| "Failed to send ping")?),
|
||||
_ => Err(OpaqueError::create(&format!(
|
||||
"Expected ping packet, got: {}",
|
||||
ping_packet.id.get_int()
|
||||
))),
|
||||
}
|
||||
}
|
||||
/// small helper function for sending `status request` to client
|
||||
pub async fn complete_status_request(
|
||||
client_stream: &mut TcpStream,
|
||||
status_struct: StatusStructNew,
|
||||
) -> Result<(), OpaqueError> {
|
||||
let status_res =
|
||||
crate::packets::clientbound::status::StatusResponse::set_json(Box::new(status_struct))
|
||||
.await;
|
||||
status_res
|
||||
.send_packet(client_stream)
|
||||
.await
|
||||
.map_err(|_| "Failed to send status packet")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disconnects the client.
|
||||
///
|
||||
/// It works if the client is in the login state, and it
|
||||
/// has *already* and *only* sent the **handshake** and **login_start** packet.
|
||||
#[tracing::instrument(skip(client_stream))]
|
||||
pub async fn send_disconnect(
|
||||
client_stream: &mut TcpStream,
|
||||
reason: &str,
|
||||
) -> Result<(), OpaqueError> {
|
||||
let disconnect_packet =
|
||||
crate::packets::clientbound::login::Disconnect::set_reason(reason.to_owned())
|
||||
.await
|
||||
.ok_or_else(|| "failed to *create* disconnect packet")?;
|
||||
disconnect_packet
|
||||
.send_packet(client_stream)
|
||||
.await
|
||||
.map_err(|_| "failed to *send* disconnect packet")?;
|
||||
client_stream.flush().await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait MinecraftServerHandle: Send + Sync + 'static + Clone {
|
||||
async fn start(&self) -> Result<(), OpaqueError>;
|
||||
async fn stop(&self) -> Result<(), OpaqueError>;
|
||||
async fn query_status(&self) -> Result<ServerDeploymentStatus, OpaqueError>;
|
||||
fn get_internal_address(&self) -> &str;
|
||||
fn get_internal_id(&self) -> &str;
|
||||
fn get_join_addr(&self) -> &str;
|
||||
fn get_join_port(&self) -> &str;
|
||||
fn get_motd(&self) -> Option<String>;
|
||||
|
||||
async fn query_server_connectable(&self) -> Result<TcpStream, OpaqueError> {
|
||||
let address = self.get_internal_address();
|
||||
let server_stream = TcpStream::connect(address)
|
||||
.await
|
||||
.map_err(|_| "failed to connect to minecraft server")?;
|
||||
|
||||
tracing::trace!(
|
||||
"successfully connected to backend server; (connectibility check) {:?}",
|
||||
server_stream.peer_addr()
|
||||
);
|
||||
Ok(server_stream)
|
||||
}
|
||||
|
||||
/// Takes the already received packets, sends them to the server,
|
||||
/// and proceeds to proxy the rest of the connection transparently.
|
||||
async fn proxy_status(
|
||||
&self,
|
||||
handshake: &Handshake,
|
||||
status_request: &Packet,
|
||||
client_stream: &mut TcpStream,
|
||||
server_stream: &mut TcpStream,
|
||||
) -> Result<(), OpaqueError> {
|
||||
handshake
|
||||
.send_packet(server_stream)
|
||||
.await
|
||||
.map_err(|_| "failed to forward handshake packet to minecraft server")?;
|
||||
status_request
|
||||
.send_packet(server_stream)
|
||||
.await
|
||||
.map_err(|_| "failed to forward status request packet to minecraft server")?;
|
||||
|
||||
let data_amount = tokio::io::copy_bidirectional(client_stream, server_stream)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"error during bidirectional copy between server and client; err={:?}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
tracing::trace!("data exchanged while proxying status: {:?}", data_amount);
|
||||
Ok(())
|
||||
}
|
||||
async fn query_description(&self) -> Result<Box<dyn StatusTrait>, OpaqueError> {
|
||||
let status = self.query_status().await?;
|
||||
match status {
|
||||
ServerDeploymentStatus::Connectable(mut tcp_stream) => {
|
||||
let handshake = crate::packets::serverbound::handshake::Handshake::create(
|
||||
crate::types::VarInt::from(746).ok_or("could not create VarInt WTF?")?,
|
||||
crate::types::VarString::from(self.get_join_addr().to_string()),
|
||||
crate::types::UShort::from(1234),
|
||||
crate::types::VarInt::from(1).ok_or("could not create VarInt WTF?")?,
|
||||
)
|
||||
.ok_or("failed to create handshake packet from scratch... WTF?")?;
|
||||
handshake
|
||||
.send_packet(&mut tcp_stream)
|
||||
.await
|
||||
.map_err(|_e| "failed to send handshake packet to server")?;
|
||||
let status_rq = crate::packets::Packet::from_bytes(0, Vec::new())
|
||||
.ok_or("Failed to create status request packet from scratch")?;
|
||||
status_rq
|
||||
.send_packet(&mut tcp_stream)
|
||||
.await
|
||||
.map_err(|_e| "failed to send status request packet to server")?;
|
||||
let return_packet = crate::packets::Packet::parse(&mut tcp_stream).await?;
|
||||
let status_response =
|
||||
crate::packets::clientbound::status::StatusResponse::parse(return_packet)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
return status_response.get_json().ok_or(OpaqueError::create(
|
||||
"failed to parse status response from server",
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(OpaqueError::create(&format!(
|
||||
"server is not running; status={:?}",
|
||||
status
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait MinecraftAPI<T>: Send + Sync + 'static {
|
||||
async fn query_server(&self, addr: &str, port: &str) -> Result<T, OpaqueError>;
|
||||
fn get_map(&self) -> Arc<tokio::sync::Mutex<HashMap<String, tokio::task::JoinHandle<()>>>>;
|
||||
|
||||
/// This should be callable even if there is already a watcher,
|
||||
/// and it should handle the collision itself while returning OK().
|
||||
async fn start_watch(
|
||||
self,
|
||||
server: impl MinecraftServerHandle + Send + Sync + 'static,
|
||||
frequency: std::time::Duration,
|
||||
) -> Result<(), OpaqueError>
|
||||
where
|
||||
Self: Clone,
|
||||
{
|
||||
let internal_id = server.get_internal_id().to_string();
|
||||
|
||||
if let Some(handle) = self.get_map().lock().await.get(&internal_id) {
|
||||
if !handle.is_finished() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let span = tracing::span!(parent: None,tracing::Level::INFO, "server_watcher", internal_id, join_addr = server.get_join_addr(), join_port = server.get_join_port());
|
||||
|
||||
let internal_id_clone = internal_id.clone();
|
||||
let api = self.clone();
|
||||
let handle = tokio::spawn(
|
||||
async move {
|
||||
tracing::info!("starting watcher");
|
||||
loop {
|
||||
tokio::time::sleep(frequency).await;
|
||||
let status_json = match server.query_description().await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
err = format!("{}", e.context),
|
||||
"could not query description"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if status_json.get_players_online() == 0 {
|
||||
// With this I don't need to specify that StatusTrait
|
||||
// should be send as well.
|
||||
|
||||
// Otherwise I would need to have it be defined as:
|
||||
// trait StatusTrait: Send { ... }
|
||||
drop(status_json);
|
||||
let map = api.get_map();
|
||||
let mut guard = map.lock().await;
|
||||
guard.remove(&internal_id_clone);
|
||||
drop(guard);
|
||||
drop(map);
|
||||
if let Err(err) = server.stop().await {
|
||||
tracing::error!(
|
||||
trace = %err.print_span_trace(),
|
||||
err = err.context,
|
||||
msg = "failed to stop server"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
.instrument(span),
|
||||
);
|
||||
let map = self.get_map();
|
||||
let mut guard = map.lock().await;
|
||||
guard.insert(internal_id, handle);
|
||||
drop(guard);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ServerDeploymentStatus {
|
||||
Connectable(TcpStream),
|
||||
Starting,
|
||||
PodOk,
|
||||
Offline,
|
||||
Unavailable(String),
|
||||
}
|
||||
|
||||
pub fn sanitize_addr(addr: &str) -> &str {
|
||||
// Thanks to a buggy minecraft, when the client sends a join
|
||||
// from a SRV DNS record, it will not use the address typed
|
||||
// in the game, but use the address redicted *to* by the
|
||||
// DNS record as the address for joining, plus a trailing "."
|
||||
//
|
||||
// For example:
|
||||
// server.example.com (_minecraft._tcp.server.example.com)
|
||||
// (the typed address) I (the DNS SRV record which gets read)
|
||||
// V
|
||||
// 5 25565 server.example.com
|
||||
// I (the response for the DNS SRV query)
|
||||
// V
|
||||
// server.example.com.
|
||||
// (the address used in the protocol)
|
||||
let addr = addr.trim_end_matches(".");
|
||||
|
||||
// Modded minecraft clients send null terminated strings,
|
||||
// after which they have extra data. This just removes them
|
||||
// from the addr lookup
|
||||
let addr = terminate_at_null(addr);
|
||||
addr
|
||||
}
|
||||
|
||||
fn terminate_at_null(str: &str) -> &str {
|
||||
match str.split('\0').next() {
|
||||
Some(x) => x,
|
||||
None => str,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue