channel: reclaim departed-member +f flood counters and expired +J entries on the tick (slow high-churn leak)
This commit is contained in:
parent
92a3f5ba86
commit
d6cc813aad
3 changed files with 45 additions and 0 deletions
|
|
@ -608,6 +608,28 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drop per-member flood state left behind by users who have left a channel
|
||||||
|
/// (a slow leak on high-churn channels — Uids are never reused, so departed
|
||||||
|
/// entries never get overwritten), and +J rejoin-block entries whose window has
|
||||||
|
/// elapsed. Called from the background tick.
|
||||||
|
pub fn purge_flood_state(&mut self) {
|
||||||
|
let now = now();
|
||||||
|
for ch in self.channels.values_mut() {
|
||||||
|
// +f message-time counters only matter for current members
|
||||||
|
if !ch.msgflood_hits.is_empty() {
|
||||||
|
let members = &ch.members;
|
||||||
|
ch.msgflood_hits.retain(|uid, _| members.contains_key(uid));
|
||||||
|
}
|
||||||
|
// +J recent-kick timestamps only matter within the (current) rejoin-block
|
||||||
|
// window; once it's elapsed — or +J is off — the entry can't block anyone.
|
||||||
|
if !ch.recent_kicks.is_empty() {
|
||||||
|
let window = ch.modes.kicknorejoin.unwrap_or(0) as u64;
|
||||||
|
ch.recent_kicks
|
||||||
|
.retain(|_, ts| window > 0 && now.saturating_sub(*ts) < window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Force `uid` out of `chan` (SAPART / SVSPART enforcement): announce the PART
|
/// Force `uid` out of `chan` (SAPART / SVSPART enforcement): announce the PART
|
||||||
/// to the channel and to links, drop the membership, reap the channel if empty.
|
/// to the channel and to links, drop the membership, reap the channel if empty.
|
||||||
/// No-op if the user isn't a member.
|
/// No-op if the user isn't a member.
|
||||||
|
|
|
||||||
|
|
@ -639,6 +639,7 @@ impl Ircd {
|
||||||
self.server.ping_links(); // keepalive on every server link
|
self.server.ping_links(); // keepalive on every server link
|
||||||
self.server.purge_xlines(); // drop expired server bans
|
self.server.purge_xlines(); // drop expired server bans
|
||||||
self.server.purge_tbans(); // lift expired timed channel bans (TBAN)
|
self.server.purge_tbans(); // lift expired timed channel bans (TBAN)
|
||||||
|
self.server.purge_flood_state(); // reclaim per-member +f/+J state of departed users
|
||||||
for m in &mut self.modules {
|
for m in &mut self.modules {
|
||||||
m.on_tick(&mut self.server); // timer-driven modules (e.g. reputation)
|
m.on_tick(&mut self.server); // timer-driven modules (e.g. reputation)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1278,6 +1278,28 @@ mod tests {
|
||||||
Server::new(Config::default(), tx, Arc::new(AtomicU64::new(1)))
|
Server::new(Config::default(), tx, Arc::new(AtomicU64::new(1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The tick purge reclaims per-member flood state of users who left, and +J
|
||||||
|
// rejoin-block entries whose window elapsed — the slow high-churn leak.
|
||||||
|
#[test]
|
||||||
|
fn purge_flood_state_reclaims_departed_and_expired() {
|
||||||
|
use crate::channels::{Channel, Member};
|
||||||
|
let mut s = srv();
|
||||||
|
let mut c = Channel::new("#c");
|
||||||
|
c.members.insert(1, Member::default()); // uid 1 is a current member
|
||||||
|
c.msgflood_hits.insert(1, vec![now()]); // member -> kept
|
||||||
|
c.msgflood_hits.insert(99, vec![now()]); // departed -> dropped
|
||||||
|
c.modes.kicknorejoin = Some(60);
|
||||||
|
c.recent_kicks.insert(99, now().saturating_sub(120)); // expired -> dropped
|
||||||
|
c.recent_kicks.insert(88, now()); // still within window -> kept
|
||||||
|
s.channels.insert("#c".into(), c);
|
||||||
|
s.purge_flood_state();
|
||||||
|
let ch = &s.channels["#c"];
|
||||||
|
assert!(ch.msgflood_hits.contains_key(&1));
|
||||||
|
assert!(!ch.msgflood_hits.contains_key(&99), "departed member's +f state dropped");
|
||||||
|
assert!(!ch.recent_kicks.contains_key(&99), "expired +J entry dropped");
|
||||||
|
assert!(ch.recent_kicks.contains_key(&88), "fresh +J entry kept");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolved_host_applied_only_when_configured() {
|
fn resolved_host_applied_only_when_configured() {
|
||||||
let mut s = srv(); // use_resolved_host = true (default)
|
let mut s = srv(); // use_resolved_host = true (default)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue