Show MOTD for banned players, kick with reason on login

This commit is contained in:
timvisee 2021-11-17 18:14:02 +01:00
parent e816d4ff6c
commit 28dbcdbfd6
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
7 changed files with 65 additions and 45 deletions

View File

@ -588,11 +588,15 @@ async fn connect_to_server_no_timeout(
.await .await
.map_err(|_| ())?; .map_err(|_| ())?;
let (mut reader, mut writer) = outbound.split(); // Construct temporary server client
let tmp_client = match outbound.local_addr() {
let tmp_client = Client::default(); Ok(addr) => Client::new(addr),
Err(_) => Client::dummy(),
};
tmp_client.set_state(ClientState::Login); tmp_client.set_state(ClientState::Login);
let (mut reader, mut writer) = outbound.split();
// Handshake packet // Handshake packet
packet::write_packet( packet::write_packet(
Handshake { Handshake {

View File

@ -17,6 +17,14 @@ pub struct BannedIps {
} }
impl 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. /// Check whether the given IP is banned.
/// ///
/// This uses the latest known `banned-ips.json` contents if known. /// This uses the latest known `banned-ips.json` contents if known.
@ -27,7 +35,7 @@ impl BannedIps {
} }
/// A banned IP entry. /// A banned IP entry.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct BannedIp { pub struct BannedIp {
/// Banned IP. /// Banned IP.
pub ip: IpAddr, pub ip: IpAddr,

View File

@ -98,7 +98,7 @@ async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus,
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?; let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
// Dummy client // Dummy client
let client = Client::default(); let client = Client::dummy();
send_handshake(&client, &mut stream, config, addr).await?; send_handshake(&client, &mut stream, config, addr).await?;
request_status(&client, &mut stream).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(|_| ())?; let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
// Dummy client // Dummy client
let client = Client::default(); let client = Client::dummy();
send_handshake(&client, &mut stream, config, addr).await?; send_handshake(&client, &mut stream, config, addr).await?;
let token = send_ping(&client, &mut stream).await?; let token = send_ping(&client, &mut stream).await?;

View File

@ -1,3 +1,4 @@
use std::net::SocketAddr;
use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex; use std::sync::Mutex;
@ -6,6 +7,9 @@ use std::sync::Mutex;
/// Note: this does not keep track of encryption states. /// Note: this does not keep track of encryption states.
#[derive(Debug)] #[derive(Debug)]
pub struct Client { pub struct Client {
/// Client peer address.
pub peer: SocketAddr,
/// Current client state. /// Current client state.
pub state: Mutex<ClientState>, pub state: Mutex<ClientState>,
@ -16,6 +20,20 @@ pub struct Client {
} }
impl 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. /// Get client state.
pub fn state(&self) -> ClientState { pub fn state(&self) -> ClientState {
*self.state.lock().unwrap() *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. /// Protocol state a client may be in.
/// ///
/// Note: this does not include the `play` state, because this is never used anymore when a client /// Note: this does not include the `play` state, because this is never used anymore when a client

View File

@ -13,7 +13,7 @@ use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
use tokio::time; use tokio::time;
use crate::config::Config; use crate::config::Config;
use crate::mc::ban::BannedIps; use crate::mc::ban::{BannedIp, BannedIps};
use crate::os; use crate::os;
/// Server cooldown after the process quit. /// Server cooldown after the process quit.
@ -320,6 +320,11 @@ impl Server {
self.banned_ips.read().await.is_banned(ip) 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. /// Check whether the given IP is banned.
/// ///
/// This uses the latest known `banned-ips.json` contents if known. /// This uses the latest known `banned-ips.json` contents if known.

View File

@ -70,48 +70,33 @@ pub async fn service(config: Arc<Config>) -> Result<(), ()> {
/// Route inbound TCP stream to correct service, spawning a new task. /// Route inbound TCP stream to correct service, spawning a new task.
#[inline] #[inline]
fn route(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) { 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 // Get user peer address
let peer = match inbound.peer_addr() { let peer = match inbound.peer_addr() {
Ok(peer) => peer, Ok(peer) => peer,
Err(err) => { Err(err) => {
warn!(target: "lazymc", "Connection from unknown peer, disconnecting: {}", err); warn!(target: "lazymc", "Connection from unknown peer address, disconnecting: {}", err);
return false; return;
} }
}; };
// Check if user is banned // Check ban state
let is_banned = server.is_banned_ip_blocking(&peer.ip()); let banned = server.is_banned_ip_blocking(&peer.ip());
if is_banned {
warn!(target: "lazymc", "Connection from banned IP {}, disconnecting", peer);
return false;
}
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. /// Route inbound TCP stream to status server, spawning a new task.
#[inline] #[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 // 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| { let service = status::serve(client, inbound, config, server).map(|r| {
if let Err(err) = r { if let Err(err) = r {
warn!(target: "lazymc", "Failed to serve status: {:?}", err); warn!(target: "lazymc", "Failed to serve status: {:?}", err);

View File

@ -127,6 +127,15 @@ pub async fn serve(
break; 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 // Start server if not starting yet
Server::start(config.clone(), server.clone(), username).await; Server::start(config.clone(), server.clone(), username).await;