diff --git a/.gitignore b/.gitignore index 12cac50..ff799d8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target result .direnv +DEBUG.sh diff --git a/Cargo.lock b/Cargo.lock index 1237f85..230c31a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,6 +377,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "evalexpr" +version = "13.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25929004897f2bbab309121a60400d36992f6d911d09baa6c172f6cc55706601" +dependencies = [ + "regex", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -947,6 +956,7 @@ dependencies = [ "anyhow", "clap", "either", + "evalexpr", "futures", "k8s-openapi", "kube", diff --git a/Cargo.toml b/Cargo.toml index 5bf0e2f..31b909b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,4 @@ either = "1.6.1" nix = { version= "0.30.1", features = [ "zerocopy"] } tokio-splice2 = "0.3.2" strip-ansi-escapes = "0.2.1" +evalexpr = { version = "13.1.0", features = ["regex"] } diff --git a/README.md b/README.md index 17c2f00..2f80156 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # Env variables: - BIND_ADDR(default: 0.0.0.0:25565): the address the server should bind to +- FILTER_CONN(default: '(addr == "10.100.0.1")'): the filter appplied with [evalexpr](https://docs.rs/evalexpr/latest/evalexpr/) + The "context" has the addr variable populated with the address of part of the + handshake packet. Custom filter can be specified, and drop any + connections which match the filter. diff --git a/src/kube_cache.rs b/src/kube_cache.rs index 96e4cac..7219a7e 100644 --- a/src/kube_cache.rs +++ b/src/kube_cache.rs @@ -11,7 +11,7 @@ use tokio::task::JoinHandle; use tracing::Instrument; use crate::{ - mc_server::{MinecraftAPI, MinecraftServerHandle, ServerDeploymentStatus}, + mc_server::{sanitize_addr, MinecraftAPI, MinecraftServerHandle, ServerDeploymentStatus}, packets::{ clientbound::status::StatusTrait, serverbound::handshake::{self}, @@ -430,34 +430,3 @@ impl From for OpaqueError { OpaqueError::create(value.to_string().as_str()) } } - -fn terminate_at_null(str: &str) -> &str { - match str.split('\0').next() { - Some(x) => x, - None => str, - } -} - -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 -} diff --git a/src/main.rs b/src/main.rs index 5f8fdff..289b1e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use evalexpr::*; use std::env; use std::net::SocketAddr; use std::time::Duration; @@ -38,23 +39,22 @@ async fn main() { let api = kube_cache::McApi::create().unwrap(); tracing::info!("initialized kube api"); - let addr = match env::var("BIND_ADDR") { - Ok(x) => x, - Err(_) => "0.0.0.0:25565".to_string(), - }; - let listener = TcpListener::bind(addr.clone()).await.unwrap(); - tracing::info!(addr, "started tcp server"); + let config: Config = Default::default(); + + let listener = TcpListener::bind(config.bind_addr.clone()).await.unwrap(); + tracing::info!(bind_addr = config.bind_addr, "started tcp server"); loop { let (socket, addr) = listener.accept().await.unwrap(); let api = api.clone(); + let config = config.clone(); tokio::spawn(async move { tracing::debug!( addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), "Client connected" ); - if let Err(e) = process_connection(socket, addr, api).await { + if let Err(e) = process_connection(socket, addr, api, config).await { tracing::error!( // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), trace = format!("{}", e.get_span_trace()), @@ -71,11 +71,12 @@ async fn main() { } } -#[tracing::instrument(level = "info", skip(api, client_stream))] +#[tracing::instrument(level = "info", skip(api, client_stream, config))] async fn process_connection( mut client_stream: TcpStream, addr: SocketAddr, api: impl MinecraftAPI, + config: Config, ) -> Result<(), OpaqueError> { let client_packet = Packet::parse(&mut client_stream).await?; @@ -92,6 +93,18 @@ async fn process_connection( .await .ok_or_else(|| "Client HANDSHAKE -> malformed packet; Disconnecting...".to_string())?; + let filter = eval_boolean(&format!( + "addr=\"{}\";{}", + handshake.get_server_address(), + config.filter_conn + )) + .map_err(|e| format!("filter error! err={:?}", e))?; + if filter { + // TODO: if the server just returns here, the client does not know it + // and sends a packet with the 122 WeirdID + return Ok(()); + } + next_server_state = handshake.get_next_state(); match next_server_state { @@ -256,3 +269,27 @@ async fn handle_login( } Ok(()) } + +#[derive(Clone)] +struct Config { + pub filter_conn: String, + pub bind_addr: String, +} + +impl Default for Config { + fn default() -> Self { + let filter_conn = match env::var("FILTER_CONN") { + Ok(x) => x, + Err(_) => "(addr == \"10.100.0.1\")".to_string(), + }; + + let bind_addr = match env::var("BIND_ADDR") { + Ok(x) => x, + Err(_) => "0.0.0.0:25565".to_string(), + }; + Self { + filter_conn, + bind_addr, + } + } +} diff --git a/src/mc_server.rs b/src/mc_server.rs index a960ec9..2756a7c 100644 --- a/src/mc_server.rs +++ b/src/mc_server.rs @@ -3,7 +3,7 @@ use tokio::{io::AsyncWriteExt, net::TcpStream}; use crate::{ packets::{ clientbound::status::{StatusStructNew, StatusTrait}, - serverbound::handshake::{self, Handshake}, + serverbound::handshake::Handshake, Packet, SendPacket, }, OpaqueError, @@ -137,3 +137,34 @@ pub enum ServerDeploymentStatus { PodOk, Offline, } + +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, + } +}