connflood: refuse connection floods from a single IP
This commit is contained in:
parent
1eca745092
commit
d4ce5c008f
4 changed files with 49 additions and 1 deletions
|
|
@ -80,3 +80,6 @@ amu_target = both
|
|||
# --- command aliases: /NS ... -> PRIVMSG <target> :... (services shortcuts) ---
|
||||
# alias = NS NickServ
|
||||
# alias = CS ChanServ
|
||||
|
||||
# --- connflood: refuse >max connections per <secs> from a single IP ---
|
||||
# connflood = 5 10
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ pub struct Config {
|
|||
pub opermotd: Vec<String>, // OPERMOTD text, one line per entry
|
||||
pub vhosts: Vec<(String, String, String)>, // self-service vhosts: (user, pass, host)
|
||||
pub aliases: Vec<(String, String)>, // command aliases: (name, target-nick)
|
||||
pub connflood: Option<(u32, u64)>, // (max conns, per secs) from one IP before refusing
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
|
@ -110,6 +111,7 @@ impl Default for Config {
|
|||
opermotd: Vec::new(),
|
||||
vhosts: Vec::new(),
|
||||
aliases: Vec::new(),
|
||||
connflood: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -268,6 +270,17 @@ impl Config {
|
|||
.push((name.to_ascii_uppercase(), target.to_string()));
|
||||
}
|
||||
}
|
||||
"connflood" => {
|
||||
// connflood = <max> <secs> — refuse >max connections/secs from one IP
|
||||
let mut it = v.split_whitespace();
|
||||
if let (Some(mx), Some(sc)) = (it.next(), it.next()) {
|
||||
if let (Ok(mx), Ok(sc)) = (mx.parse::<u32>(), sc.parse::<u64>()) {
|
||||
if mx > 0 && sc > 0 {
|
||||
c.connflood = Some((mx, sc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@ impl Ircd {
|
|||
self.server.ping_links(); // keepalive on every server link
|
||||
self.server.purge_xlines(); // drop expired server bans
|
||||
self.server.purge_tbans(); // lift expired timed channel bans (TBAN)
|
||||
self.server.prune_conn_history(); // connflood bookkeeping
|
||||
let now = crate::server::now();
|
||||
let (to_ping, to_quit) = self.server.idle_check(now);
|
||||
for uid in to_ping {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::net::{IpAddr, SocketAddr, TcpStream};
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -135,6 +135,8 @@ pub struct Server {
|
|||
pub opermotd: Vec<String>, // OPERMOTD text
|
||||
pub vhosts: Vec<(String, String, String)>, // self-service vhosts: (user, pass, host)
|
||||
pub aliases: Vec<(String, String)>, // command aliases: (name, target-nick)
|
||||
pub connflood: Option<(u32, u64)>, // (max, secs) connection throttle per IP
|
||||
pub conn_history: HashMap<IpAddr, Vec<u64>>, // recent connection times per IP (connflood)
|
||||
// labeled-response: while Some((uid, buf)), that client's own responses are
|
||||
// diverted into `buf` instead of the socket, so `on_line` can wrap them with
|
||||
// the command's `label` (single tag, BATCH, or ACK). RefCell because the
|
||||
|
|
@ -189,6 +191,8 @@ impl Server {
|
|||
opermotd: cfg.opermotd,
|
||||
vhosts: cfg.vhosts,
|
||||
aliases: cfg.aliases,
|
||||
connflood: cfg.connflood,
|
||||
conn_history: HashMap::new(),
|
||||
label_capture: RefCell::new(None),
|
||||
event_tx,
|
||||
conn_counter,
|
||||
|
|
@ -273,6 +277,22 @@ impl Server {
|
|||
},
|
||||
);
|
||||
|
||||
// connflood — refuse an IP that's opening connections too fast
|
||||
if let Some((max, secs)) = self.connflood {
|
||||
let n = now();
|
||||
let hist = self.conn_history.entry(ip).or_default();
|
||||
hist.retain(|&t| n.saturating_sub(t) < secs);
|
||||
hist.push(n);
|
||||
if hist.len() as u32 > max {
|
||||
self.send(
|
||||
uid,
|
||||
"ERROR :Closing link: (Too many connections from your IP)".to_string(),
|
||||
);
|
||||
self.remove_user(uid, "Connection throttled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-registration connection notices, InspIRCd / solanum style. Ident-113
|
||||
// is archaic and firewalled, so those two are cosmetic; the hostname lookup
|
||||
// is real (see `resolver`) — its result arrives later as an Event.
|
||||
|
|
@ -317,6 +337,17 @@ impl Server {
|
|||
}
|
||||
}
|
||||
|
||||
/// Drop stale per-IP connflood bookkeeping (called on the background tick).
|
||||
pub fn prune_conn_history(&mut self) {
|
||||
if let Some((_, secs)) = self.connflood {
|
||||
let n = now();
|
||||
self.conn_history.retain(|_, times| {
|
||||
times.retain(|&t| n.saturating_sub(t) < secs);
|
||||
!times.is_empty()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A pre-registration `:server NOTICE * :*** <msg>` line.
|
||||
pub(crate) fn notice_star(&self, uid: Uid, msg: &str) {
|
||||
self.send(uid, format!(":{} NOTICE * :*** {msg}", self.name));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue