diff --git a/src/lobby.rs b/src/lobby.rs index b2c97a1..241c159 100644 --- a/src/lobby.rs +++ b/src/lobby.rs @@ -588,11 +588,15 @@ async fn connect_to_server_no_timeout( .await .map_err(|_| ())?; - let (mut reader, mut writer) = outbound.split(); - - let tmp_client = Client::default(); + // Construct temporary server client + let tmp_client = match outbound.local_addr() { + Ok(addr) => Client::new(addr), + Err(_) => Client::dummy(), + }; tmp_client.set_state(ClientState::Login); + let (mut reader, mut writer) = outbound.split(); + // Handshake packet packet::write_packet( Handshake { diff --git a/src/mc/ban.rs b/src/mc/ban.rs index bae461b..cbfeb48 100644 --- a/src/mc/ban.rs +++ b/src/mc/ban.rs @@ -17,6 +17,14 @@ pub struct BannedIps { } impl BannedIps { + /// Get ban entry if IP if it exists. + /// + /// This uses the latest known `banned-ips.json` contents if known. + /// If this feature is disabled, this will always return false. + pub fn get(&self, ip: &IpAddr) -> Option<BannedIp> { + self.ips.get(ip).cloned() + } + /// Check whether the given IP is banned. /// /// This uses the latest known `banned-ips.json` contents if known. @@ -27,7 +35,7 @@ impl BannedIps { } /// A banned IP entry. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct BannedIp { /// Banned IP. pub ip: IpAddr, diff --git a/src/monitor.rs b/src/monitor.rs index 39dd270..85f5ac3 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -98,7 +98,7 @@ async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus, let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?; // Dummy client - let client = Client::default(); + let client = Client::dummy(); send_handshake(&client, &mut stream, config, addr).await?; request_status(&client, &mut stream).await?; @@ -110,7 +110,7 @@ async fn do_ping(config: &Config, addr: SocketAddr) -> Result<(), ()> { let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?; // Dummy client - let client = Client::default(); + let client = Client::dummy(); send_handshake(&client, &mut stream, config, addr).await?; let token = send_ping(&client, &mut stream).await?; diff --git a/src/proto/client.rs b/src/proto/client.rs index 584696b..4d82eb2 100644 --- a/src/proto/client.rs +++ b/src/proto/client.rs @@ -1,3 +1,4 @@ +use std::net::SocketAddr; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Mutex; @@ -6,6 +7,9 @@ use std::sync::Mutex; /// Note: this does not keep track of encryption states. #[derive(Debug)] pub struct Client { + /// Client peer address. + pub peer: SocketAddr, + /// Current client state. pub state: Mutex<ClientState>, @@ -16,6 +20,20 @@ pub struct Client { } impl Client { + /// Construct new client with given peer address. + pub fn new(peer: SocketAddr) -> Self { + Self { + peer, + state: Default::default(), + compression: AtomicI32::new(-1), + } + } + + /// Construct dummy client. + pub fn dummy() -> Self { + Self::new("0.0.0.0:0".parse().unwrap()) + } + /// Get client state. pub fn state(&self) -> ClientState { *self.state.lock().unwrap() @@ -44,15 +62,6 @@ impl Client { } } -impl Default for Client { - fn default() -> Self { - Self { - state: Default::default(), - compression: AtomicI32::new(-1), - } - } -} - /// Protocol state a client may be in. /// /// Note: this does not include the `play` state, because this is never used anymore when a client diff --git a/src/server.rs b/src/server.rs index af7b90c..842feb1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -13,7 +13,7 @@ use tokio::sync::{Mutex, RwLock, RwLockReadGuard}; use tokio::time; use crate::config::Config; -use crate::mc::ban::BannedIps; +use crate::mc::ban::{BannedIp, BannedIps}; use crate::os; /// Server cooldown after the process quit. @@ -320,6 +320,11 @@ impl Server { self.banned_ips.read().await.is_banned(ip) } + /// Get user ban entry. + pub async fn ban_entry(&self, ip: &IpAddr) -> Option<BannedIp> { + self.banned_ips.read().await.get(ip) + } + /// Check whether the given IP is banned. /// /// This uses the latest known `banned-ips.json` contents if known. diff --git a/src/service/server.rs b/src/service/server.rs index 7d341f5..a810cda 100644 --- a/src/service/server.rs +++ b/src/service/server.rs @@ -70,48 +70,33 @@ pub async fn service(config: Arc<Config>) -> Result<(), ()> { /// Route inbound TCP stream to correct service, spawning a new task. #[inline] fn route(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) { - // Check ban - if !check_ban(&inbound, &server) { - return; - } - - // Route connection through proper channel - let should_proxy = server.state() == server::State::Started && !config.lockout.enabled; - if should_proxy { - route_proxy(inbound, config) - } else { - route_status(inbound, config, server) - } -} - -/// Check whether user IP is banned. -/// -/// Returns `true` if user is still allowed to connect. -fn check_ban(inbound: &TcpStream, server: &Server) -> bool { // Get user peer address let peer = match inbound.peer_addr() { Ok(peer) => peer, Err(err) => { - warn!(target: "lazymc", "Connection from unknown peer, disconnecting: {}", err); - return false; + warn!(target: "lazymc", "Connection from unknown peer address, disconnecting: {}", err); + return; } }; - // Check if user is banned - let is_banned = server.is_banned_ip_blocking(&peer.ip()); - if is_banned { - warn!(target: "lazymc", "Connection from banned IP {}, disconnecting", peer); - return false; - } + // Check ban state + let banned = server.is_banned_ip_blocking(&peer.ip()); - true + // Route connection through proper channel + let should_proxy = + !banned && server.state() == server::State::Started && !config.lockout.enabled; + if should_proxy { + route_proxy(inbound, config) + } else { + route_status(inbound, config, server, peer) + } } /// Route inbound TCP stream to status server, spawning a new task. #[inline] -fn route_status(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) { +fn route_status(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>, peer: SocketAddr) { // When server is not online, spawn a status server - let client = Client::default(); + let client = Client::new(peer); let service = status::serve(client, inbound, config, server).map(|r| { if let Err(err) = r { warn!(target: "lazymc", "Failed to serve status: {:?}", err); diff --git a/src/status.rs b/src/status.rs index ceaf07d..b9e3cac 100644 --- a/src/status.rs +++ b/src/status.rs @@ -127,6 +127,15 @@ pub async fn serve( break; } + // Kick if client is banned + if let Some(ban) = server.ban_entry(&client.peer.ip()).await { + if ban.is_banned() { + warn!(target: "lazymc", "Login from banned IP {} ({}), disconnecting", client.peer.ip(), &ban.reason); + action::kick(&client, &ban.reason, &mut writer).await?; + break; + } + } + // Start server if not starting yet Server::start(config.clone(), server.clone(), username).await;