Optimize IPset integration for maximum performance in live attack monitor

PROBLEM:
Live attack monitor was calling CSF unnecessarily for every block,
causing performance overhead during DDoS attacks. The code was creating
a new temporary IPset (live_monitor_$$) instead of using CSF's existing
chain_DENY IPset, resulting in:
- IPset add failures (IP already in CSF's set)
- Unnecessary CSF fallback calls
- Slower blocking due to CSF overhead
- Duplicate blocking attempts

ROOT CAUSE:
Lines 68-86: Created unique per-process IPset instead of detecting/using
CSF's existing chain_DENY IPset

THE FIX:

1. Smart IPset Detection (lines 67-103):
   ✓ Detects CSF's chain_DENY IPset FIRST (preferred)
   ✓ Uses chain_DENY directly if found
   ✓ Falls back to temporary live_monitor_$$ if no CSF
   ✓ Auto-detects timeout support capability
   ✓ Never destroys CSF's permanent IPset on cleanup (line 141)

2. Aggressive IPset Prioritization (lines 855-911):
   block_ip_temporary():
   ✓ ALWAYS tries IPset first if available
   ✓ Uses -exist flag to handle duplicates gracefully
   ✓ For CSF chain_DENY without timeout: Adds to IPset immediately,
     then calls CSF in background for timeout management
   ✓ CSF only used as fallback if IPset unavailable

   block_ip_permanent():
   ✓ Adds to IPset immediately for instant blocking
   ✓ CSF called after for persistent management
   ✓ Handles both timeout/no-timeout IPsets

3. Subnet Blocking Optimization (lines 2307-2320):
   ✓ Uses $IPSET_NAME variable instead of hardcoded "blocklist"
   ✓ IPset subnet block happens FIRST (instant)
   ✓ CSF called in background after IPset

PERFORMANCE BENEFITS:
✓ Kernel-level blocking (IPset) instead of userspace (CSF)
✓ Instant blocking during DDoS attacks
✓ No CSF overhead for every block
✓ Integrates with CSF's existing infrastructure
✓ Backward compatible (works without CSF)

TESTED:
✓ Bash syntax validation passed
✓ Files synced (main + v2)
✓ All blocking paths prioritize IPset
This commit is contained in:
cschantz
2025-12-25 16:16:22 -05:00
parent 2e176aa310
commit 6b3b0ed503
2 changed files with 136 additions and 76 deletions
+68 -38
View File
@@ -65,21 +65,38 @@ echo "0" > "$TEMP_DIR/event_counter"
echo "0" > "$TEMP_DIR/total_blocks" echo "0" > "$TEMP_DIR/total_blocks"
# IPset configuration # IPset configuration
IPSET_NAME="live_monitor_$$" IPSET_NAME=""
IPSET_AVAILABLE=0 IPSET_AVAILABLE=0
IPSET_SUPPORTS_TIMEOUT=0
# Initialize IPset for fast blocking (if available) # Initialize IPset for fast blocking (if available)
if command -v ipset &>/dev/null; then if command -v ipset &>/dev/null; then
# Create temporary IPset with 1-hour default timeout # Check if CSF's chain_DENY IPset exists (preferred - already integrated with CSF)
if ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>/dev/null; then if ipset list chain_DENY &>/dev/null 2>&1; then
IPSET_NAME="chain_DENY"
IPSET_AVAILABLE=1 IPSET_AVAILABLE=1
# Add iptables rule to block IPs in the set # Check if chain_DENY supports timeouts
iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null if ipset list chain_DENY | grep -q "^Type:.*timeout"; then
IPSET_SUPPORTS_TIMEOUT=1
echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log" echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log"
else
echo "✓ Using CSF IPset: chain_DENY (no timeout support, will use CSF for temp blocks)" >> "$TEMP_DIR/debug.log"
fi
else else
echo "✗ IPset creation failed - falling back to CSF" >> "$TEMP_DIR/debug.log" # No CSF IPset found, create our own temporary one
IPSET_NAME="live_monitor_$$"
if ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>/dev/null; then
IPSET_AVAILABLE=1
IPSET_SUPPORTS_TIMEOUT=1
# Add iptables rule to block IPs in the set
iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null
echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log"
else
echo "✗ IPset creation failed - falling back to CSF" >> "$TEMP_DIR/debug.log"
fi
fi fi
else else
echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log" echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log"
@@ -120,8 +137,8 @@ cleanup() {
# Wait a moment for background jobs # Wait a moment for background jobs
sleep 1 sleep 1
# Clean up IPset and iptables rule if we created them # Clean up IPset and iptables rule ONLY if we created them (not CSF's chain_DENY)
if [ "$IPSET_AVAILABLE" -eq 1 ]; then if [ "$IPSET_AVAILABLE" -eq 1 ] && [ "$IPSET_NAME" != "chain_DENY" ]; then
echo "Removing IPset firewall rules..." echo "Removing IPset firewall rules..."
iptables -D INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null iptables -D INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null
ipset destroy "$IPSET_NAME" 2>/dev/null ipset destroy "$IPSET_NAME" 2>/dev/null
@@ -847,33 +864,41 @@ block_ip_temporary() {
return 1 return 1
fi fi
# Use IPset for instant blocking if available # PRIORITY 1: Use IPset for instant kernel-level blocking (performance critical)
if [ "$IPSET_AVAILABLE" -eq 1 ]; then if [ "$IPSET_AVAILABLE" -eq 1 ]; then
echo "Blocking $ip for ${hours}h: $reason" # Try IPset with timeout if supported
if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" 2>/dev/null; then if [ "$IPSET_SUPPORTS_TIMEOUT" -eq 1 ]; then
echo "$ip blocked via IPset (auto-expires in ${hours}h)" if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" -exist 2>/dev/null; then
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" echo "$ip blocked via IPset $IPSET_NAME (expires in ${hours}h)"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
# Update counter atomically increment_block_counter 1
increment_block_counter 1 return 0
fi
return 0
else else
echo "✗ Warning: IPset add failed (IP may already be blocked)" # IPset without timeout (CSF's chain_DENY) - add to IPset for instant block,
return 1 # then let CSF manage the timeout removal
if ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null; then
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
increment_block_counter 1
# Let CSF manage the timeout in background (IPset already blocking)
if command -v csf &>/dev/null; then
csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1 &
echo "$ip blocked via IPset $IPSET_NAME (CSF managing timeout: ${hours}h)"
else
echo "$ip blocked via IPset $IPSET_NAME (permanent - no CSF timeout)"
fi
return 0
fi
fi fi
fi fi
# Fallback to CSF if IPset not available # FALLBACK: CSF-only blocking (slower, but still works)
if command -v csf &>/dev/null; then if command -v csf &>/dev/null; then
echo "Blocking $ip for ${hours}h: $reason"
if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then
echo "$ip blocked via CSF" echo "$ip blocked via CSF (expires in ${hours}h)"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
# Update counter atomically
increment_block_counter 1 increment_block_counter 1
return 0 return 0
else else
echo "✗ Warning: CSF block failed for $ip" echo "✗ Warning: CSF block failed for $ip"
@@ -881,7 +906,7 @@ block_ip_temporary() {
fi fi
fi fi
echo "✗ Error: CSF not available" echo "✗ Error: No blocking method available"
return 1 return 1
} }
@@ -911,13 +936,18 @@ block_ip_permanent() {
return 1 return 1
fi fi
# Permanent blocks always use CSF (IPset is temp-only for this script) # PRIORITY: Add to IPset immediately for instant kernel-level blocking
# But we can add to IPset with max timeout as well for immediate effect
if [ "$IPSET_AVAILABLE" -eq 1 ]; then if [ "$IPSET_AVAILABLE" -eq 1 ]; then
# Add to IPset with 24-hour timeout for immediate blocking if [ "$IPSET_SUPPORTS_TIMEOUT" -eq 1 ]; then
ipset add "$IPSET_NAME" "$ip" timeout 86400 2>/dev/null # IPset with timeout - use max timeout (24 hours)
ipset add "$IPSET_NAME" "$ip" timeout 86400 -exist 2>/dev/null
else
# IPset without timeout (CSF's chain_DENY) - permanent add
ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null
fi
fi fi
# CSF for persistent management (runs after IPset for immediate effect)
if command -v csf &>/dev/null; then if command -v csf &>/dev/null; then
echo "Permanently blocking $ip: $reason" echo "Permanently blocking $ip: $reason"
if csf -d "$ip" "$reason" >/dev/null 2>&1; then if csf -d "$ip" "$reason" >/dev/null 2>&1; then
@@ -2279,16 +2309,16 @@ monitor_network_attacks() {
for subnet in "${!hostile_subnets[@]}"; do for subnet in "${!hostile_subnets[@]}"; do
local subnet_ip_count=${hostile_subnets[$subnet]} local subnet_ip_count=${hostile_subnets[$subnet]}
if [ "$subnet_ip_count" -ge 10 ]; then if [ "$subnet_ip_count" -ge 10 ]; then
# Block entire /24 subnet via IPset # Block entire /24 subnet via IPset (PRIORITY) then CSF
local subnet_cidr="${subnet}.0/24" local subnet_cidr="${subnet}.0/24"
if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then
echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets" echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets"
( (
# Add to IPset if available # PRIORITY: Add to IPset for instant kernel-level blocking
if command -v ipset &>/dev/null && ipset list blocklist &>/dev/null 2>&1; then if [ "$IPSET_AVAILABLE" -eq 1 ]; then
ipset add blocklist "$subnet_cidr" -exist 2>/dev/null ipset add "$IPSET_NAME" "$subnet_cidr" -exist 2>/dev/null
fi fi
# Also add to CSF # CSF for persistent management (runs in background after IPset)
if command -v csf &>/dev/null; then if command -v csf &>/dev/null; then
csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null
fi fi
+68 -38
View File
@@ -65,21 +65,38 @@ echo "0" > "$TEMP_DIR/event_counter"
echo "0" > "$TEMP_DIR/total_blocks" echo "0" > "$TEMP_DIR/total_blocks"
# IPset configuration # IPset configuration
IPSET_NAME="live_monitor_$$" IPSET_NAME=""
IPSET_AVAILABLE=0 IPSET_AVAILABLE=0
IPSET_SUPPORTS_TIMEOUT=0
# Initialize IPset for fast blocking (if available) # Initialize IPset for fast blocking (if available)
if command -v ipset &>/dev/null; then if command -v ipset &>/dev/null; then
# Create temporary IPset with 1-hour default timeout # Check if CSF's chain_DENY IPset exists (preferred - already integrated with CSF)
if ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>/dev/null; then if ipset list chain_DENY &>/dev/null 2>&1; then
IPSET_NAME="chain_DENY"
IPSET_AVAILABLE=1 IPSET_AVAILABLE=1
# Add iptables rule to block IPs in the set # Check if chain_DENY supports timeouts
iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null if ipset list chain_DENY | grep -q "^Type:.*timeout"; then
IPSET_SUPPORTS_TIMEOUT=1
echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log" echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log"
else
echo "✓ Using CSF IPset: chain_DENY (no timeout support, will use CSF for temp blocks)" >> "$TEMP_DIR/debug.log"
fi
else else
echo "✗ IPset creation failed - falling back to CSF" >> "$TEMP_DIR/debug.log" # No CSF IPset found, create our own temporary one
IPSET_NAME="live_monitor_$$"
if ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>/dev/null; then
IPSET_AVAILABLE=1
IPSET_SUPPORTS_TIMEOUT=1
# Add iptables rule to block IPs in the set
iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null
echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log"
else
echo "✗ IPset creation failed - falling back to CSF" >> "$TEMP_DIR/debug.log"
fi
fi fi
else else
echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log" echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log"
@@ -120,8 +137,8 @@ cleanup() {
# Wait a moment for background jobs # Wait a moment for background jobs
sleep 1 sleep 1
# Clean up IPset and iptables rule if we created them # Clean up IPset and iptables rule ONLY if we created them (not CSF's chain_DENY)
if [ "$IPSET_AVAILABLE" -eq 1 ]; then if [ "$IPSET_AVAILABLE" -eq 1 ] && [ "$IPSET_NAME" != "chain_DENY" ]; then
echo "Removing IPset firewall rules..." echo "Removing IPset firewall rules..."
iptables -D INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null iptables -D INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null
ipset destroy "$IPSET_NAME" 2>/dev/null ipset destroy "$IPSET_NAME" 2>/dev/null
@@ -847,33 +864,41 @@ block_ip_temporary() {
return 1 return 1
fi fi
# Use IPset for instant blocking if available # PRIORITY 1: Use IPset for instant kernel-level blocking (performance critical)
if [ "$IPSET_AVAILABLE" -eq 1 ]; then if [ "$IPSET_AVAILABLE" -eq 1 ]; then
echo "Blocking $ip for ${hours}h: $reason" # Try IPset with timeout if supported
if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" 2>/dev/null; then if [ "$IPSET_SUPPORTS_TIMEOUT" -eq 1 ]; then
echo "$ip blocked via IPset (auto-expires in ${hours}h)" if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" -exist 2>/dev/null; then
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" echo "$ip blocked via IPset $IPSET_NAME (expires in ${hours}h)"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
# Update counter atomically increment_block_counter 1
increment_block_counter 1 return 0
fi
return 0
else else
echo "✗ Warning: IPset add failed (IP may already be blocked)" # IPset without timeout (CSF's chain_DENY) - add to IPset for instant block,
return 1 # then let CSF manage the timeout removal
if ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null; then
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
increment_block_counter 1
# Let CSF manage the timeout in background (IPset already blocking)
if command -v csf &>/dev/null; then
csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1 &
echo "$ip blocked via IPset $IPSET_NAME (CSF managing timeout: ${hours}h)"
else
echo "$ip blocked via IPset $IPSET_NAME (permanent - no CSF timeout)"
fi
return 0
fi
fi fi
fi fi
# Fallback to CSF if IPset not available # FALLBACK: CSF-only blocking (slower, but still works)
if command -v csf &>/dev/null; then if command -v csf &>/dev/null; then
echo "Blocking $ip for ${hours}h: $reason"
if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then
echo "$ip blocked via CSF" echo "$ip blocked via CSF (expires in ${hours}h)"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
# Update counter atomically
increment_block_counter 1 increment_block_counter 1
return 0 return 0
else else
echo "✗ Warning: CSF block failed for $ip" echo "✗ Warning: CSF block failed for $ip"
@@ -881,7 +906,7 @@ block_ip_temporary() {
fi fi
fi fi
echo "✗ Error: CSF not available" echo "✗ Error: No blocking method available"
return 1 return 1
} }
@@ -911,13 +936,18 @@ block_ip_permanent() {
return 1 return 1
fi fi
# Permanent blocks always use CSF (IPset is temp-only for this script) # PRIORITY: Add to IPset immediately for instant kernel-level blocking
# But we can add to IPset with max timeout as well for immediate effect
if [ "$IPSET_AVAILABLE" -eq 1 ]; then if [ "$IPSET_AVAILABLE" -eq 1 ]; then
# Add to IPset with 24-hour timeout for immediate blocking if [ "$IPSET_SUPPORTS_TIMEOUT" -eq 1 ]; then
ipset add "$IPSET_NAME" "$ip" timeout 86400 2>/dev/null # IPset with timeout - use max timeout (24 hours)
ipset add "$IPSET_NAME" "$ip" timeout 86400 -exist 2>/dev/null
else
# IPset without timeout (CSF's chain_DENY) - permanent add
ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null
fi
fi fi
# CSF for persistent management (runs after IPset for immediate effect)
if command -v csf &>/dev/null; then if command -v csf &>/dev/null; then
echo "Permanently blocking $ip: $reason" echo "Permanently blocking $ip: $reason"
if csf -d "$ip" "$reason" >/dev/null 2>&1; then if csf -d "$ip" "$reason" >/dev/null 2>&1; then
@@ -2279,16 +2309,16 @@ monitor_network_attacks() {
for subnet in "${!hostile_subnets[@]}"; do for subnet in "${!hostile_subnets[@]}"; do
local subnet_ip_count=${hostile_subnets[$subnet]} local subnet_ip_count=${hostile_subnets[$subnet]}
if [ "$subnet_ip_count" -ge 10 ]; then if [ "$subnet_ip_count" -ge 10 ]; then
# Block entire /24 subnet via IPset # Block entire /24 subnet via IPset (PRIORITY) then CSF
local subnet_cidr="${subnet}.0/24" local subnet_cidr="${subnet}.0/24"
if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then
echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets" echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets"
( (
# Add to IPset if available # PRIORITY: Add to IPset for instant kernel-level blocking
if command -v ipset &>/dev/null && ipset list blocklist &>/dev/null 2>&1; then if [ "$IPSET_AVAILABLE" -eq 1 ]; then
ipset add blocklist "$subnet_cidr" -exist 2>/dev/null ipset add "$IPSET_NAME" "$subnet_cidr" -exist 2>/dev/null
fi fi
# Also add to CSF # CSF for persistent management (runs in background after IPset)
if command -v csf &>/dev/null; then if command -v csf &>/dev/null; then
csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null
fi fi