Use more efficient structure to manage banned IPs

This commit is contained in:
timvisee 2021-11-17 17:46:51 +01:00
parent 168cbceb4c
commit e816d4ff6c
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
3 changed files with 42 additions and 13 deletions

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fs; use std::fs;
use std::net::IpAddr; use std::net::IpAddr;
@ -8,6 +9,23 @@ use serde::Deserialize;
/// File name. /// File name.
pub const FILE: &str = "banned-ips.json"; pub const FILE: &str = "banned-ips.json";
/// List of banned IPs.
#[derive(Debug, Default)]
pub struct BannedIps {
/// List of banned IPs.
ips: HashMap<IpAddr, BannedIp>,
}
impl BannedIps {
/// Check whether the given IP is banned.
///
/// This uses the latest known `banned-ips.json` contents if known.
/// If this feature is disabled, this will always return false.
pub fn is_banned(&self, ip: &IpAddr) -> bool {
self.ips.get(ip).map(|ip| ip.is_banned()).unwrap_or(false)
}
}
/// A banned IP entry. /// A banned IP entry.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct BannedIp { pub struct BannedIp {
@ -27,11 +45,22 @@ pub struct BannedIp {
pub reason: String, pub reason: String,
} }
impl BannedIp {
/// Check if this entry is currently banned.
pub fn is_banned(&self) -> bool {
// TODO: check expiry date here!
true
}
}
/// Load banned IPs from file. /// Load banned IPs from file.
pub fn load(path: &Path) -> Result<Vec<BannedIp>, Box<dyn Error>> { pub fn load(path: &Path) -> Result<BannedIps, Box<dyn Error>> {
// Load file contents // Load file contents
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
// Parse contents // Parse contents, transform into map
Ok(serde_json::from_str(&contents)?) let ips: Vec<BannedIp> = serde_json::from_str(&contents)?;
let ips = ips.into_iter().map(|ip| (ip.ip, ip)).collect();
Ok(BannedIps { ips })
} }

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::BannedIp; use crate::mc::ban::BannedIps;
use crate::os; use crate::os;
/// Server cooldown after the process quit. /// Server cooldown after the process quit.
@ -66,7 +66,7 @@ pub struct Server {
kill_at: RwLock<Option<Instant>>, kill_at: RwLock<Option<Instant>>,
/// List of banned IPs. /// List of banned IPs.
banned_ips: RwLock<Vec<BannedIp>>, banned_ips: RwLock<BannedIps>,
/// Lock for exclusive RCON operations. /// Lock for exclusive RCON operations.
#[cfg(feature = "rcon")] #[cfg(feature = "rcon")]
@ -317,7 +317,7 @@ impl Server {
/// This uses the latest known `banned-ips.json` contents if known. /// This uses the latest known `banned-ips.json` contents if known.
/// If this feature is disabled, this will always return false. /// If this feature is disabled, this will always return false.
pub async fn is_banned_ip(&self, ip: &IpAddr) -> bool { pub async fn is_banned_ip(&self, ip: &IpAddr) -> bool {
self.banned_ips.read().await.iter().any(|i| &i.ip == ip) self.banned_ips.read().await.is_banned(ip)
} }
/// Check whether the given IP is banned. /// Check whether the given IP is banned.
@ -329,7 +329,7 @@ impl Server {
} }
/// Update the list of banned IPs. /// Update the list of banned IPs.
pub async fn set_banned_ips(&self, ips: Vec<BannedIp>) { pub async fn set_banned_ips(&self, ips: BannedIps) {
*self.banned_ips.write().await = ips; *self.banned_ips.write().await = ips;
} }
} }

View File

@ -6,7 +6,7 @@ use futures::FutureExt;
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use crate::config::Config; use crate::config::Config;
use crate::mc::ban::{self, BannedIp}; use crate::mc::ban::{self, BannedIps};
use crate::proto::client::Client; use crate::proto::client::Client;
use crate::proxy; use crate::proxy;
use crate::server::{self, Server}; use crate::server::{self, Server};
@ -160,10 +160,10 @@ pub fn route_proxy_address_queue(inbound: TcpStream, addr: SocketAddr, queue: By
/// Load banned IPs if IP banning is enabled. /// Load banned IPs if IP banning is enabled.
/// ///
/// If disabled or on error, an empty list is returned. /// If disabled or on error, an empty list is returned.
fn load_banned_ips(config: &Config) -> Vec<BannedIp> { fn load_banned_ips(config: &Config) -> BannedIps {
// Blocking banned IPs must be enabled // Blocking banned IPs must be enabled
if !config.server.block_banned_ips { if !config.server.block_banned_ips {
return vec![]; return BannedIps::default();
} }
// Ensure server directory is set, it must exist // Ensure server directory is set, it must exist
@ -171,7 +171,7 @@ fn load_banned_ips(config: &Config) -> Vec<BannedIp> {
Some(dir) => dir, Some(dir) => dir,
None => { None => {
warn!(target: "lazymc", "Not blocking banned IPs, server directory not configured, unable to find {} file", ban::FILE); warn!(target: "lazymc", "Not blocking banned IPs, server directory not configured, unable to find {} file", ban::FILE);
return vec![]; return BannedIps::default();
} }
}; };
@ -179,7 +179,7 @@ fn load_banned_ips(config: &Config) -> Vec<BannedIp> {
let path = dir.join(crate::mc::ban::FILE); let path = dir.join(crate::mc::ban::FILE);
if !path.is_file() { if !path.is_file() {
warn!(target: "lazymc", "Not blocking banned IPs, {} file does not exist", ban::FILE); warn!(target: "lazymc", "Not blocking banned IPs, {} file does not exist", ban::FILE);
return vec![]; return BannedIps::default();
} }
// Load banned IPs // Load banned IPs
@ -188,7 +188,7 @@ fn load_banned_ips(config: &Config) -> Vec<BannedIp> {
Err(err) => { Err(err) => {
// TODO: quit here, require user to disable feature as security feature? // TODO: quit here, require user to disable feature as security feature?
error!(target: "lazymc", "Failed to load banned IPs from {}: {}", ban::FILE, err); error!(target: "lazymc", "Failed to load banned IPs from {}: {}", ban::FILE, err);
return vec![]; return BannedIps::default();
} }
}; };