Reduce EWMA false positives with min_pps threshold
- Add min_pps (default 20) to skip anomaly detection for low-traffic IPs - Increase threshold_multiplier from 3.0 to 5.0 - Increase rate_limit_after from 1 to 3 violations - Support min_pps in SIGHUP config reload Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ rate_limits:
|
|||||||
|
|
||||||
escalation:
|
escalation:
|
||||||
# Violations before escalation
|
# Violations before escalation
|
||||||
rate_limit_after: 1 # violations before eBPF rate limiting kicks in
|
rate_limit_after: 3 # violations before eBPF rate limiting kicks in
|
||||||
temp_block_after: 5 # violations before temporary block
|
temp_block_after: 5 # violations before temporary block
|
||||||
perm_block_after: 999999 # effectively disabled
|
perm_block_after: 999999 # effectively disabled
|
||||||
|
|
||||||
@@ -48,7 +48,8 @@ escalation:
|
|||||||
ewma:
|
ewma:
|
||||||
alpha: 0.3 # EWMA smoothing factor (0-1, higher = more reactive)
|
alpha: 0.3 # EWMA smoothing factor (0-1, higher = more reactive)
|
||||||
poll_interval: 1 # seconds between rate counter polls
|
poll_interval: 1 # seconds between rate counter polls
|
||||||
threshold_multiplier: 3.0 # alert when EWMA > multiplier * baseline
|
threshold_multiplier: 5.0 # alert when EWMA > multiplier * baseline
|
||||||
|
min_pps: 20 # ignore anomalies below this PPS (reduce false positives)
|
||||||
|
|
||||||
ai:
|
ai:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ DEFAULT_CONFIG = {
|
|||||||
'ewma': {
|
'ewma': {
|
||||||
'alpha': 0.3,
|
'alpha': 0.3,
|
||||||
'poll_interval': 1,
|
'poll_interval': 1,
|
||||||
'threshold_multiplier': 3.0,
|
'threshold_multiplier': 5.0,
|
||||||
|
'min_pps': 20,
|
||||||
},
|
},
|
||||||
'ai': {
|
'ai': {
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
@@ -170,9 +171,10 @@ class ViolationTracker:
|
|||||||
class EWMAAnalyzer:
|
class EWMAAnalyzer:
|
||||||
"""Per-IP EWMA calculation for rate anomaly detection."""
|
"""Per-IP EWMA calculation for rate anomaly detection."""
|
||||||
|
|
||||||
def __init__(self, alpha=0.3, threshold_multiplier=3.0):
|
def __init__(self, alpha=0.3, threshold_multiplier=5.0, min_pps=20):
|
||||||
self.alpha = alpha
|
self.alpha = alpha
|
||||||
self.threshold_multiplier = threshold_multiplier
|
self.threshold_multiplier = threshold_multiplier
|
||||||
|
self.min_pps = min_pps
|
||||||
self.ewma = {}
|
self.ewma = {}
|
||||||
self.baseline = {}
|
self.baseline = {}
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
@@ -188,6 +190,9 @@ class EWMAAnalyzer:
|
|||||||
self.ewma[ip] = self.alpha * current_pps + (1 - self.alpha) * self.ewma[ip]
|
self.ewma[ip] = self.alpha * current_pps + (1 - self.alpha) * self.ewma[ip]
|
||||||
self.baseline[ip] = 0.01 * current_pps + 0.99 * self.baseline[ip]
|
self.baseline[ip] = 0.01 * current_pps + 0.99 * self.baseline[ip]
|
||||||
|
|
||||||
|
if current_pps < self.min_pps:
|
||||||
|
return False
|
||||||
|
|
||||||
base = max(self.baseline[ip], 1)
|
base = max(self.baseline[ip], 1)
|
||||||
if self.ewma[ip] > base * self.threshold_multiplier:
|
if self.ewma[ip] > base * self.threshold_multiplier:
|
||||||
return True
|
return True
|
||||||
@@ -614,7 +619,8 @@ class DDoSDaemon:
|
|||||||
self.violation_tracker = ViolationTracker(self.cfg['escalation'])
|
self.violation_tracker = ViolationTracker(self.cfg['escalation'])
|
||||||
self.ewma_analyzer = EWMAAnalyzer(
|
self.ewma_analyzer = EWMAAnalyzer(
|
||||||
alpha=self.cfg['ewma'].get('alpha', 0.3),
|
alpha=self.cfg['ewma'].get('alpha', 0.3),
|
||||||
threshold_multiplier=self.cfg['ewma'].get('threshold_multiplier', 3.0),
|
threshold_multiplier=self.cfg['ewma'].get('threshold_multiplier', 5.0),
|
||||||
|
min_pps=self.cfg['ewma'].get('min_pps', 20),
|
||||||
)
|
)
|
||||||
self.ai_detector = AIDetector(self.cfg['ai'])
|
self.ai_detector = AIDetector(self.cfg['ai'])
|
||||||
self.profile_manager = ProfileManager(self.cfg['rate_limits'])
|
self.profile_manager = ProfileManager(self.cfg['rate_limits'])
|
||||||
@@ -673,7 +679,8 @@ class DDoSDaemon:
|
|||||||
# Build all new values before swapping anything
|
# Build all new values before swapping anything
|
||||||
new_escalation = new_cfg['escalation']
|
new_escalation = new_cfg['escalation']
|
||||||
new_alpha = new_cfg['ewma'].get('alpha', 0.3)
|
new_alpha = new_cfg['ewma'].get('alpha', 0.3)
|
||||||
new_threshold = new_cfg['ewma'].get('threshold_multiplier', 3.0)
|
new_threshold = new_cfg['ewma'].get('threshold_multiplier', 5.0)
|
||||||
|
new_min_pps = new_cfg['ewma'].get('min_pps', 20)
|
||||||
new_ai_cfg = new_cfg['ai']
|
new_ai_cfg = new_cfg['ai']
|
||||||
new_rate_cfg = new_cfg['rate_limits']
|
new_rate_cfg = new_cfg['rate_limits']
|
||||||
new_ewma_interval = new_cfg['ewma'].get('poll_interval', 1)
|
new_ewma_interval = new_cfg['ewma'].get('poll_interval', 1)
|
||||||
@@ -684,6 +691,7 @@ class DDoSDaemon:
|
|||||||
self.violation_tracker.cfg = new_escalation
|
self.violation_tracker.cfg = new_escalation
|
||||||
self.ewma_analyzer.alpha = new_alpha
|
self.ewma_analyzer.alpha = new_alpha
|
||||||
self.ewma_analyzer.threshold_multiplier = new_threshold
|
self.ewma_analyzer.threshold_multiplier = new_threshold
|
||||||
|
self.ewma_analyzer.min_pps = new_min_pps
|
||||||
self.ai_detector.cfg = new_ai_cfg
|
self.ai_detector.cfg = new_ai_cfg
|
||||||
self.profile_manager.cfg = new_rate_cfg
|
self.profile_manager.cfg = new_rate_cfg
|
||||||
self._ewma_interval = new_ewma_interval
|
self._ewma_interval = new_ewma_interval
|
||||||
|
|||||||
Reference in New Issue
Block a user