From efe080a9e70c4d233f7e69b999d6f0e5ded4615d Mon Sep 17 00:00:00 2001 From: Tamipes Date: Thu, 28 May 2026 17:56:42 +0200 Subject: [PATCH 1/7] feat: add localized tmux server --- README.md | 5 +++++ flake.nix | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 8a10d6f..54b2413 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# Development +TODO: fill this out +### Nix shell with tmux +In the nix flake there is a devshell setup with cargo and tmux. +When using tmux it uses a set tmux server path, so only this project uses it. # Env variables: ```bash # Address the server should bind to diff --git a/flake.nix b/flake.nix index b64872c..2f97def 100644 --- a/flake.nix +++ b/flake.nix @@ -142,6 +142,11 @@ # Extra inputs can be added here; cargo and rustc are provided by default. packages = [ pkgs.rust-analyzer + + (pkgs.writers.writeBashBin "tmux" '' + exec ${lib.getExe pkgs.tmux} -S /tmp/tmux-kube_ingress "$@" + '' + ) ]; }; }); From ee7a05152e054e9211362b169ac9c105081baec7 Mon Sep 17 00:00:00 2001 From: Tamipes Date: Thu, 28 May 2026 18:00:29 +0200 Subject: [PATCH 2/7] feat: cleaner logging - less clutter with unusable `span` info (on my personal preference) - less errors logged when being scanned (ex.: after status response don't log errors with ping packets, scanners usually close the connection) - implemented more detailed OpaqueError fields - `tracing::Level` - error `kind` --- src/kube_cache.rs | 14 ++++---- src/main.rs | 82 +++++++++++++++++++++++++++++++-------------- src/opaque_error.rs | 33 ++++++++++++++++-- src/packets/mod.rs | 8 +++-- 4 files changed, 102 insertions(+), 35 deletions(-) diff --git a/src/kube_cache.rs b/src/kube_cache.rs index ae54ce8..490db59 100644 --- a/src/kube_cache.rs +++ b/src/kube_cache.rs @@ -99,17 +99,19 @@ impl MinecraftAPI for McApi { let dep_name = match self.cache.query_dep_addr(&addr, &port).await { Some(x) => x, None => { - return Err(OpaqueError::create(&format!( - "Failed to find deployment name by addr" - ))) + return Err(OpaqueError::create_with_kind( + "Failed to find deployment name by addr", + "DepNameLookupFailed", + )) } }; let srv_name = match self.cache.query_srv_addr(&addr, &port).await { Some(x) => x, None => { - return Err(OpaqueError::create(&format!( - "Failed to find service name by addr" - ))) + return Err(OpaqueError::create_with_kind( + "Failed to find service name by addr", + "SrvNameLookupFailed", + )) } }; diff --git a/src/main.rs b/src/main.rs index 798ea01..fe32e77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,12 +58,35 @@ async fn main() { "Client connected" ); 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()), - err = format!("{}", e.context), - "Client disconnected" - ); + match e.level { + tracing::Level::ERROR => tracing::error!( + // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), + trace = format!("{}", e.get_span_trace()), + err = format!("{}", e.context), + "Client disconnected" + ), + tracing::Level::WARN => tracing::warn!( + // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), + trace = format!("{}", e.get_span_trace()), + err = format!("{}", e.context), + "Client disconnected" + ), + tracing::Level::INFO => tracing::info!( + // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), + trace = format!("{}", e.get_span_trace()), + err = format!("{}", e.context), + "Client disconnected" + ), + _ => { + tracing::error!( + // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), + trace = format!("{}", e.get_span_trace()), + err = format!("{}", e.context), + actual_level = ?e.level, + "Client disconnected (bad level)" + ) + } + } } else { tracing::debug!( addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), @@ -74,7 +97,6 @@ async fn main() { } } -#[tracing::instrument(level = "info", skip(api, client_stream, config))] async fn process_connection( mut client_stream: TcpStream, addr: SocketAddr, @@ -159,7 +181,6 @@ async fn handle_status( }; let server_addr = handshake.get_server_address(); - let commit_hash: &'static str = env!("COMMIT_HASH"); let mut status_struct = StatusStructNew::create(); status_struct.version.protocol = handshake.protocol_version.get_int(); @@ -172,7 +193,7 @@ async fn handle_status( { Ok(x) => x, Err(e) => { - let span = tracing::span!(tracing::Level::WARN, "not_found", err = e.context); + let span = tracing::span!(tracing::Level::INFO, "unavailable", err = e.get_kind()); status_struct.players.max = 0; status_struct.players.online = 0; status_struct.description.text = format!( @@ -184,22 +205,25 @@ async fn handle_status( .await?; // Recieve the ping packet, so the client does not send it again - let _ping = Packet::parse(client_stream).instrument(span.clone()).await; + let _ping = Packet::parse(client_stream) + .instrument(span.clone()) + .await + .map_err(|err| { + tracing::debug!( + err = OpaqueError::from(err).context, + "failed to parse ping packet" + ) + }); // Send a bad ping packet back, so the client shows *searching* icon let _pong = Packet::new(9, vec![0; 8]) .ok_or("failed to create empty pong packet?")? .send_packet(client_stream) .instrument(span.clone()) .await - .map_err(|_| "failed to send pong packet")?; + .map_err(|_| tracing::debug!("failed to send pong packet")); let _guard = span.enter(); - match _ping { - Ok(_) => tracing::info!("sent status with error"), - Err(err) => { - tracing::error!(err = OpaqueError::from(err).context) - } - }; + tracing::info!(status = "unavailable", "status request"); return Ok(()); } }; @@ -209,7 +233,6 @@ async fn handle_status( .get_motd() .unwrap_or("A minecraft server (proxy motd)".to_string()); - tracing::trace!(motd); match status { ServerDeploymentStatus::Connectable(mut server_stream) => { return server @@ -245,7 +268,8 @@ async fn handle_login( &handshake.get_server_address(), &handshake.server_port.get_value().to_string(), ) - .await?; + .await + .map_err(|e| e.set_level(tracing::Level::WARN))?; let status = server.query_status().await?; tracing::debug!(msg = "server status", status = ?status); @@ -281,12 +305,20 @@ async fn handle_login( .execute(client_stream, &mut server_stream) .await; - tracing::debug!("data exchanged: tx: {} rx: {}", traffic.tx, traffic.rx); - if let Some(e) = traffic.error { - return Err(OpaqueError::create(&format!( - "failed to splice; err = {}", - e - ))); + match traffic.error { + Some(e) => { + tracing::info!( + tx = traffic.tx, + rx = traffic.rx, + err = ?e, + "connection splicing ended with error", + ) + } + None => tracing::info!( + tx = traffic.tx, + rx = traffic.rx, + "connection splicing ended", + ), } } ServerDeploymentStatus::PodOk | ServerDeploymentStatus::Starting => { diff --git a/src/opaque_error.rs b/src/opaque_error.rs index ad70182..626936f 100644 --- a/src/opaque_error.rs +++ b/src/opaque_error.rs @@ -1,10 +1,13 @@ use std::{error::Error, fmt}; +use tracing::Level; use tracing_error::SpanTrace; #[derive(Debug)] pub struct OpaqueError { span_trace: SpanTrace, pub context: String, + pub level: Level, + kind: Option, } impl fmt::Display for OpaqueError { @@ -31,10 +34,20 @@ impl fmt::Display for OpaqueError { } impl Error for OpaqueError {} impl OpaqueError { - pub fn create(str: &str) -> OpaqueError { + pub fn create(context: &str) -> OpaqueError { Self { span_trace: SpanTrace::capture(), - context: str.to_string(), + context: context.to_string(), + level: Level::ERROR, + kind: None, + } + } + pub fn create_with_kind(context: &str, kind: &str) -> OpaqueError { + Self { + span_trace: SpanTrace::capture(), + context: context.to_string(), + level: Level::ERROR, + kind: Some(kind.to_string()), } } pub fn get_span_trace(&self) -> String { @@ -62,6 +75,16 @@ impl OpaqueError { Err(_) => str, } } + pub fn set_level(mut self, lvl: Level) -> Self { + self.level = lvl; + self + } + pub fn get_kind(&self) -> &str { + match self.kind.as_ref() { + Some(x) => &x, + None => &self.context, + } + } } impl From for OpaqueError { @@ -69,6 +92,8 @@ impl From for OpaqueError { Self { span_trace: SpanTrace::capture(), context: value, + level: Level::ERROR, + kind: None, } } } @@ -78,6 +103,8 @@ impl From<&str> for OpaqueError { Self { span_trace: SpanTrace::capture(), context: value.to_string(), + level: Level::ERROR, + kind: None, } } } @@ -87,6 +114,8 @@ impl From for OpaqueError { Self { span_trace: value.context, context: format!("{:?}", value.inner), + level: Level::ERROR, + kind: None, } } } diff --git a/src/packets/mod.rs b/src/packets/mod.rs index 28c6a04..9a20335 100644 --- a/src/packets/mod.rs +++ b/src/packets/mod.rs @@ -61,7 +61,7 @@ impl Packet { pub async fn parse(stream: &mut TcpStream) -> Result { let length = VarInt::parse(stream) .await - .ok_or(ParseError::IDParseError)?; + .ok_or(ParseError::LengthParseError)?; tracing::trace!(length = length.get_int()); let id = match VarInt::parse(stream).await { @@ -150,6 +150,7 @@ impl From for ParseErrorTrace { } pub enum ParseError { + LengthParseError, IDParseError, WeirdID, LengthIsTooBig(i32), @@ -162,7 +163,10 @@ pub enum ParseError { impl fmt::Debug for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::IDParseError => write!(f, "IDParseError: could not parse packet id"), + Self::LengthParseError => { + write!(f, "LengthParseError: could not parse packet length VarInt") + } + Self::IDParseError => write!(f, "IDParseError: could not parse packet id VarInt"), Self::WeirdID => write!(f, "WeirdID: weird packet id encountered: 122"), Self::LengthIsTooBig(x) => { write!(f, "LengthIsTooBig: packet length is too big; len={x}") From 122912710136b8589c80e36b83d28bcac08988f1 Mon Sep 17 00:00:00 2001 From: Tamipes Date: Fri, 29 May 2026 10:40:51 +0200 Subject: [PATCH 3/7] feat: add better logging span to `process_connection` --- src/main.rs | 66 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/main.rs b/src/main.rs index fe32e77..e4039f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,35 +103,49 @@ async fn process_connection( api: impl MinecraftAPI, config: Config, ) -> Result<(), OpaqueError> { - let client_packet = Packet::parse(&mut client_stream).await?; + // this is wrapper so that async doesnt mess up the span, and + // to make sure this doesn't propagate to later `handle_*` + #[tracing::instrument(level = "info", skip(client_stream, config))] + async fn first_packet( + client_stream: &mut TcpStream, + config: Config, + ) -> Result, OpaqueError> { + let client_packet = Packet::parse(client_stream).await?; - // --- Handshake --- - let handshake; - let next_server_state; - let packet_id = client_packet.id.get_int(); - if packet_id != 0 { - return Err(OpaqueError::create(&format!( - "Client HANDSHAKE -> bad packet; id={packet_id} Disconnecting..." - ))); + // --- Handshake --- + let handshake; + let packet_id = client_packet.id.get_int(); + if packet_id != 0 { + return Err(OpaqueError::create(&format!( + "Client HANDSHAKE -> bad packet; id={packet_id} Disconnecting..." + ))); + } + handshake = packets::serverbound::handshake::Handshake::parse(client_packet) + .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 + tracing::trace!("filtered out the connection"); + return Ok(None); + } + + Ok(Some(handshake)) } - handshake = packets::serverbound::handshake::Handshake::parse(client_packet) - .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(); + let handshake = match first_packet(&mut client_stream, config).await? { + Some(x) => x, + // this is needed because of the filter + None => return Ok(()), + }; + let next_server_state = handshake.get_next_state(); match next_server_state { packets::ProtocolState::Status => { handle_status(&mut client_stream, &handshake, api).await?; From 1bce912a1a0c64640aa2c6fa8a0f31952b0e5a0e Mon Sep 17 00:00:00 2001 From: Tamipes Date: Fri, 29 May 2026 10:42:24 +0200 Subject: [PATCH 4/7] feat: polish logging if the server is unavailable --- src/kube_cache.rs | 7 ++----- src/main.rs | 8 +++++--- src/mc_server.rs | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/kube_cache.rs b/src/kube_cache.rs index 490db59..95e34d9 100644 --- a/src/kube_cache.rs +++ b/src/kube_cache.rs @@ -12,11 +12,7 @@ use tracing::Instrument; use crate::{ mc_server::{sanitize_addr, MinecraftAPI, MinecraftServerHandle, ServerDeploymentStatus}, - packets::{ - clientbound::status::StatusTrait, - serverbound::handshake::{self}, - SendPacket, - }, + packets::{clientbound::status::StatusTrait, SendPacket}, OpaqueError, }; @@ -438,6 +434,7 @@ impl fmt::Debug for ServerDeploymentStatus { Self::Starting => write!(f, "Starting"), Self::PodOk => write!(f, "PodOk"), Self::Offline => write!(f, "Offline"), + Self::Unavailable(s) => write!(f, "Unavailable ({s})"), } } } diff --git a/src/main.rs b/src/main.rs index e4039f2..6a8322d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -235,9 +235,9 @@ async fn handle_status( .instrument(span.clone()) .await .map_err(|_| tracing::debug!("failed to send pong packet")); - let _guard = span.enter(); - tracing::info!(status = "unavailable", "status request"); + let status = ServerDeploymentStatus::Unavailable(e.get_kind().to_string()); + tracing::info!(status = ?status, "status request"); return Ok(()); } }; @@ -264,6 +264,7 @@ async fn handle_status( status_struct.description.text = format!("{motd}\n§4Offline§r §oJoin to start!§r - {BYE_MESSAGE}"); } + ServerDeploymentStatus::Unavailable(_) => unreachable!(), }; mc_server::complete_status_request(client_stream, status_struct).await?; @@ -321,7 +322,7 @@ async fn handle_login( match traffic.error { Some(e) => { - tracing::info!( + tracing::warn!( tx = traffic.tx, rx = traffic.rx, err = ?e, @@ -344,6 +345,7 @@ async fn handle_login( .await?; mc_server::send_disconnect(client_stream, format!("[\"\",{{\"text\":\"Okayy, §2starting§r the server!\n\n\"}},{{\"text\":\"{BYE_MESSAGE}\"}}]").as_str()).await?; } + ServerDeploymentStatus::Unavailable(_) => unreachable!(), } Ok(()) } diff --git a/src/mc_server.rs b/src/mc_server.rs index 069e27a..03b6a9d 100644 --- a/src/mc_server.rs +++ b/src/mc_server.rs @@ -139,6 +139,7 @@ pub enum ServerDeploymentStatus { Starting, PodOk, Offline, + Unavailable(String), } pub fn sanitize_addr(addr: &str) -> &str { From 0e2afd0772fc3729e4076385f85c9faf0df953e9 Mon Sep 17 00:00:00 2001 From: Tamipes Date: Fri, 29 May 2026 11:07:59 +0200 Subject: [PATCH 5/7] feat: polish the OpaqueError get_span_trace function looks --- src/kube_cache.rs | 2 +- src/main.rs | 8 ++++---- src/opaque_error.rs | 29 +++++++++++++++++++---------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/kube_cache.rs b/src/kube_cache.rs index 95e34d9..c8be2e5 100644 --- a/src/kube_cache.rs +++ b/src/kube_cache.rs @@ -189,7 +189,7 @@ impl MinecraftAPI for McApi { drop(guard); if let Err(err) = server.stop().await { tracing::error!( - trace = err.get_span_trace(), + trace = %err.print_span_trace(), err = err.context, msg = "failed to stop server" ); diff --git a/src/main.rs b/src/main.rs index 6a8322d..80c5a3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,26 +61,26 @@ async fn main() { match e.level { tracing::Level::ERROR => tracing::error!( // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), - trace = format!("{}", e.get_span_trace()), + trace = %e.print_span_trace(), err = format!("{}", e.context), "Client disconnected" ), tracing::Level::WARN => tracing::warn!( // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), - trace = format!("{}", e.get_span_trace()), + trace = %e.print_span_trace(), err = format!("{}", e.context), "Client disconnected" ), tracing::Level::INFO => tracing::info!( // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), - trace = format!("{}", e.get_span_trace()), + trace = %e.print_span_trace(), err = format!("{}", e.context), "Client disconnected" ), _ => { tracing::error!( // addr = format!("{}:{}", addr.ip().to_string(), addr.port().to_string()), - trace = format!("{}", e.get_span_trace()), + trace = %e.print_span_trace(), err = format!("{}", e.context), actual_level = ?e.level, "Client disconnected (bad level)" diff --git a/src/opaque_error.rs b/src/opaque_error.rs index 626936f..6db5a7a 100644 --- a/src/opaque_error.rs +++ b/src/opaque_error.rs @@ -1,4 +1,7 @@ -use std::{error::Error, fmt}; +use std::{ + error::Error, + fmt::{self, Debug}, +}; use tracing::Level; use tracing_error::SpanTrace; @@ -50,7 +53,7 @@ impl OpaqueError { kind: Some(kind.to_string()), } } - pub fn get_span_trace(&self) -> String { + pub fn print_span_trace(&self) -> FancyTrace { let mut vec: Vec<(&str, String)> = Vec::new(); self.span_trace.with_spans(|metadata, _fields| { @@ -64,16 +67,14 @@ impl OpaqueError { } acc.push_str(x.0); - acc.push_str("{"); - acc.push_str(&x.1); - acc.push_str("}"); - + if !x.1.is_empty() { + acc.push_str("{"); + acc.push_str(&x.1); + acc.push_str("}"); + } acc }); - match String::from_utf8(strip_ansi_escapes::strip(str.clone())) { - Ok(x) => x, - Err(_) => str, - } + FancyTrace { str } } pub fn set_level(mut self, lvl: Level) -> Self { self.level = lvl; @@ -86,6 +87,14 @@ impl OpaqueError { } } } +pub struct FancyTrace { + str: String, +} +impl fmt::Display for FancyTrace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.str) + } +} impl From for OpaqueError { fn from(value: String) -> Self { From 5dc61a0f12dec6cbf64820b2c1b7edb359386eca Mon Sep 17 00:00:00 2001 From: Tamipes Date: Fri, 29 May 2026 13:10:05 +0200 Subject: [PATCH 6/7] feat: make `OpaqueError` FanctTrace more legible --- src/opaque_error.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/opaque_error.rs b/src/opaque_error.rs index 6db5a7a..07c65d7 100644 --- a/src/opaque_error.rs +++ b/src/opaque_error.rs @@ -61,9 +61,10 @@ impl OpaqueError { true }); let str = vec.iter().rfold(String::new(), |mut acc, x| { - let first = acc.len() != 0; - if first { - acc.push_str(":"); + let first = acc.len() == 0; + if !first { + // TODO: should this code be hardcoded? + acc.push_str("\x1b[2m:\x1b[0m"); } acc.push_str(x.0); @@ -74,6 +75,7 @@ impl OpaqueError { } acc }); + FancyTrace { str } } pub fn set_level(mut self, lvl: Level) -> Self { @@ -92,7 +94,7 @@ pub struct FancyTrace { } impl fmt::Display for FancyTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.str) + write!(f, "\x1b[2m{{{{\x1b[0m {} \x1b[2m}}}}\x1b[0m", self.str) } } From 80b264331eee658ed4eee452eb6e13a41dfe78bb Mon Sep 17 00:00:00 2001 From: Tamipes Date: Tue, 26 May 2026 23:43:46 +0200 Subject: [PATCH 7/7] wip --- kube/roles.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 kube/roles.yaml diff --git a/kube/roles.yaml b/kube/roles.yaml new file mode 100644 index 0000000..e054159 --- /dev/null +++ b/kube/roles.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: minecraft-ingress +rules: +- apiGroups: [""] # "" indicates the core API group + resources: ["pods","deployments","services"] + verbs: ["get", "list"]