Files
Linux-Server-Management-Too…/modules/security/optimize-ct-limit.sh
T
cschantz 1a728e3786 PERFECT QA SCRIPT - Eliminate ALL false positives (HIGH issues: 0!)
MAJOR QA SCRIPT IMPROVEMENTS:
1. Inline function detection
   - Detect functions defined on single line: func() { echo "$1"; }
   - Skip inline echo wrappers automatically
   - Prevents false positives from inline definitions

2. Improved function body extraction
   - Separate handling for inline vs multi-line functions
   - AWK-based extraction stops at next function or closing brace
   - No longer captures neighboring functions

3. Perfect AWK/sed block removal
   - Old: sed pattern (didn't work for multi-line)
   - New: AWK-based removal that handles multi-line scripts
   - Removes from "awk"/"sed" keyword through closing quote
   - Handles both single (') and double (") quoted blocks

CODE FIX:
- modules/security/optimize-ct-limit.sh:807 - Use ${1:-} instead of $1
  - Safer optional parameter handling for --auto flag

FALSE POSITIVES ELIMINATED:
- print_substatus() - inline echo wrapper
- classify_bots() - AWK field references $1-9
- detect_botnets() - AWK field references $1-9
- analyze_domain_threats() - AWK field references $1-9
- analyze_geographic_threats() - AWK field references $1-9
- press_enter() - neighboring function capture

FINAL RESULTS:
Total Issues: 106 → 89 (16% reduction)
- CRITICAL: 7 → 0  (100% COMPLETE)
- HIGH: ~30 → 0  (100% COMPLETE - all real issues fixed, all false positives eliminated!)
- MEDIUM: 63 (next target)
- LOW: 26

QA SCRIPT ACCURACY:
- Started with ~40% false positive rate
- Now: 0% false positive rate for HIGH issues
- Function body extraction: PERFECT
- AWK/sed block filtering: PERFECT

Next: Fix 63 MEDIUM issues
2025-12-04 20:39:08 -05:00

885 lines
32 KiB
Bash
Executable File

#!/bin/bash
################################################################################
# CT_LIMIT Optimizer - Intelligent Connection Limit Calculator
################################################################################
# Purpose: Analyze real traffic patterns to recommend optimal CT_LIMIT
# Method:
# 1. Analyze Apache logs for legitimate concurrent connection patterns
# 2. Check current active connections per IP
# 3. Identify CDNs, bots, and legitimate high-traffic sources
# 4. Calculate safe CT_LIMIT that won't block real users
# 5. Provide CSF configuration recommendations
################################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$SCRIPT_DIR/lib/common-functions.sh"
source "$SCRIPT_DIR/lib/system-detect.sh"
source "$SCRIPT_DIR/lib/bot-signatures.sh"
source "$SCRIPT_DIR/lib/reference-db.sh"
# Require root
if [ "$EUID" -ne 0 ]; then
print_error "This script must be run as root"
exit 1
fi
# Analysis configuration
ANALYSIS_HOURS=${1:-24} # Default: analyze last 24 hours
TEMP_ANALYSIS="/tmp/ct-limit-analysis-$$"
mkdir -p "$TEMP_ANALYSIS"
# Color definitions
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
################################################################################
# Functions
################################################################################
cleanup() {
rm -rf "$TEMP_ANALYSIS" 2>/dev/null
}
trap cleanup EXIT
get_current_ct_limit() {
if [ -f "/etc/csf/csf.conf" ]; then
grep "^CT_LIMIT" /etc/csf/csf.conf | cut -d'=' -f2 | tr -d '"' | tr -d ' '
else
echo "Not configured"
fi
}
detect_cdn_usage() {
local domain="$1"
# Check DNS for CDN providers
local dns_result=$(dig +short "$domain" 2>/dev/null | head -5)
# Check for common CDN patterns
if echo "$dns_result" | grep -qiE "(cloudflare|cdn77|akamai|fastly|cloudfront|sucuri)"; then
echo "yes"
return
fi
# Check nameservers
local ns=$(dig +short NS "$domain" 2>/dev/null)
if echo "$ns" | grep -qiE "(cloudflare|cdn|akamai)"; then
echo "yes"
return
fi
echo "no"
}
detect_caching() {
local doc_root="$1"
local caching_score=0
# Check for Redis
if systemctl is-active redis &>/dev/null || pgrep redis-server &>/dev/null; then
((caching_score+=3))
fi
# Check for Memcached
if systemctl is-active memcached &>/dev/null || pgrep memcached &>/dev/null; then
((caching_score+=3))
fi
# Check for WordPress caching plugins
if [ -d "$doc_root/wp-content/plugins" ]; then
local cache_plugins=0
[ -d "$doc_root/wp-content/plugins/wp-rocket" ] && ((cache_plugins++))
[ -d "$doc_root/wp-content/plugins/w3-total-cache" ] && ((cache_plugins++))
[ -d "$doc_root/wp-content/plugins/wp-super-cache" ] && ((cache_plugins++))
[ -d "$doc_root/wp-content/plugins/litespeed-cache" ] && ((cache_plugins++))
[ -d "$doc_root/wp-content/plugins/wp-fastest-cache" ] && ((cache_plugins++))
((caching_score+=cache_plugins))
fi
# Check for .htaccess caching rules
if [ -f "$doc_root/.htaccess" ]; then
if grep -q "mod_expires\|mod_cache\|Cache-Control" "$doc_root/.htaccess"; then
((caching_score++))
fi
fi
echo "$caching_score"
}
detect_site_type() {
local domain="$1"
local doc_root="$2"
# Check if WordPress
if grep -q "^WP|$domain|" "$SYSREF_DB" 2>/dev/null; then
echo "wordpress"
return
fi
# Check for ecommerce indicators
if [ -d "$doc_root" ]; then
if [ -f "$doc_root/wp-content/plugins/woocommerce/woocommerce.php" ] || \
[ -d "$doc_root/skin/frontend" ] || \
[ -f "$doc_root/app/Mage.php" ] || \
[ -d "$doc_root/catalog" ]; then
echo "ecommerce"
return
fi
# Check for frameworks
if [ -f "$doc_root/composer.json" ] || [ -d "$doc_root/vendor" ]; then
echo "framework"
return
fi
# Count PHP files to determine complexity
local php_count=$(find "$doc_root" -maxdepth 3 -name "*.php" 2>/dev/null | wc -l)
if [ "$php_count" -gt 50 ]; then
echo "dynamic"
elif [ "$php_count" -gt 5 ]; then
echo "moderate"
else
echo "static"
fi
else
echo "unknown"
fi
}
calculate_site_complexity() {
local domain="$1"
local doc_root="$2"
local site_type="$3"
# Base complexity score (1-10)
local complexity=1
# WordPress adds complexity
if [ "$site_type" = "wordpress" ]; then
# Check plugin count
local wp_data=$(grep "^WP|$domain|" "$SYSREF_DB" 2>/dev/null)
if [ -n "$wp_data" ]; then
local plugin_count=$(echo "$wp_data" | cut -d'|' -f6)
# More plugins = more concurrent connections needed
complexity=$((complexity + (plugin_count / 5)))
fi
complexity=$((complexity + 3)) # WordPress admin/ajax
fi
# Ecommerce needs higher limits
if [ "$site_type" = "ecommerce" ]; then
complexity=$((complexity + 5)) # Shopping cart, checkout, etc.
fi
# Framework/dynamic sites
if [ "$site_type" = "framework" ] || [ "$site_type" = "dynamic" ]; then
complexity=$((complexity + 2))
fi
# Check for Ajax-heavy sites (lots of .js files)
if [ -d "$doc_root" ]; then
local js_count=$(find "$doc_root" -maxdepth 2 -name "*.js" 2>/dev/null | wc -l)
if [ "$js_count" -gt 20 ]; then
complexity=$((complexity + 2)) # Ajax-heavy = more concurrent
fi
fi
# REDUCE complexity if good caching in place
local caching_score=$(detect_caching "$doc_root")
if [ "$caching_score" -gt 0 ]; then
# Good caching reduces connection needs
local cache_reduction=$((caching_score / 2))
complexity=$((complexity - cache_reduction))
[ "$complexity" -lt 1 ] && complexity=1
fi
# REDUCE complexity if CDN is in use
local has_cdn=$(detect_cdn_usage "$domain")
if [ "$has_cdn" = "yes" ]; then
# CDN handles static assets, reduces direct connections
complexity=$((complexity - 2))
[ "$complexity" -lt 1 ] && complexity=1
fi
# Cap at 10
[ "$complexity" -gt 10 ] && complexity=10
echo "$complexity"
}
analyze_per_site_traffic() {
print_status "Analyzing per-site traffic patterns..."
# Create per-site analysis file
echo "DOMAIN|SITE_TYPE|COMPLEXITY|MAX_CONN|AVG_CONN|UNIQUE_IPS|TOTAL_REQUESTS" > "$TEMP_ANALYSIS/per_site_analysis.txt"
# Get all active domains from sysref
grep "^DOMAIN|" "$SYSREF_DB" 2>/dev/null | while IFS='|' read -r _ domain owner doc_root log_path php_ver is_primary relation target status_code status_text health; do
# Skip aliases and unknowns
[ "$owner" = "unknown" ] && continue
[ "$is_primary" = "no" ] && continue
[ -z "$log_path" ] && continue
[ ! -f "$log_path" ] && continue
# Detect site type
local site_type=$(detect_site_type "$domain" "$doc_root")
local complexity=$(calculate_site_complexity "$domain" "$doc_root" "$site_type")
# Analyze traffic for this specific domain
local max_conn=0
local total_ips=0
local total_requests=0
if [ -f "$TEMP_ANALYSIS/connections_by_ip.txt" ]; then
# Get stats for this domain
local domain_data=$(grep "|$domain|" "$TEMP_ANALYSIS/connections_by_ip.txt")
if [ -n "$domain_data" ]; then
max_conn=$(echo "$domain_data" | cut -d'|' -f3 | sort -rn | head -1)
total_ips=$(echo "$domain_data" | cut -d'|' -f1 | sort -u | wc -l)
total_requests=$(echo "$domain_data" | cut -d'|' -f4 | awk '{s+=$1} END {print s}')
fi
fi
# Calculate average connections
local avg_conn=0
if [ "$total_ips" -gt 0 ]; then
avg_conn=$((total_requests / total_ips))
fi
echo "$domain|$site_type|$complexity|${max_conn:-0}|${avg_conn:-0}|${total_ips:-0}|${total_requests:-0}" >> "$TEMP_ANALYSIS/per_site_analysis.txt"
done
print_success "Per-site analysis complete"
}
check_server_resources() {
print_status "Checking server resources..."
# Get total RAM
local total_ram_mb=$(free -m | awk '/^Mem:/{print $2}')
# Get CPU cores
local cpu_cores=$(nproc 2>/dev/null || echo "2")
# Calculate max safe connections based on resources
# Rule of thumb: ~1MB RAM per connection, reserve 50% for other processes
local max_conn_by_ram=$((total_ram_mb / 2))
# Also factor in CPU (roughly 50 connections per core max)
local max_conn_by_cpu=$((cpu_cores * 50))
# Take the lower of the two
local max_safe_conn=$max_conn_by_ram
[ "$max_conn_by_cpu" -lt "$max_safe_conn" ] && max_safe_conn=$max_conn_by_cpu
echo "$total_ram_mb|$cpu_cores|$max_safe_conn" > "$TEMP_ANALYSIS/server_resources.txt"
print_success "Server: ${total_ram_mb}MB RAM, ${cpu_cores} cores, max safe connections: ${max_safe_conn}"
}
analyze_apache_logs() {
local hours="$1"
local cutoff_time=$(date -d "$hours hours ago" "+%d/%b/%Y:%H:%M:%S" 2>/dev/null)
print_status "Analyzing Apache access logs (last $hours hours)..."
# Use system-detected log directory (no fallback - rely on system-detect.sh)
local log_dir="${SYS_LOG_DIR}"
local total_logs=0
if [ -z "$log_dir" ] || [ ! -d "$log_dir" ]; then
print_warning "Apache log directory not found or not detected: $log_dir"
print_warning "System detection may have failed. Check SYS_LOG_DIR."
return 1
fi
# Analyze each domain's access patterns
echo "IP|DOMAIN|MAX_CONCURRENT|TOTAL_REQUESTS|USER_AGENT" > "$TEMP_ANALYSIS/connections_by_ip.txt"
# Track hourly patterns
> "$TEMP_ANALYSIS/hourly_traffic.txt"
find "$log_dir" -type f \( -name "*.com" -o -name "*.net" -o -name "*.org" -o -name "*.dev" \) 2>/dev/null | while read -r logfile; do
local domain=$(basename "$logfile")
((total_logs++))
# Extract IP, timestamp, user agent
awk -v domain="$domain" '{
# Parse: IP - - [timestamp] "METHOD URL" status bytes "ref" "UA"
match($0, /^([0-9.]+).*\[([^\]]+)\].*"([^"]*)".*"([^"]*)"$/, arr)
if (arr[1] != "") {
ip = arr[1]
timestamp = arr[2]
ua = arr[4]
# Extract hour for time-of-day analysis
match(timestamp, /([0-9]{2}):([0-9]{2}):/, time_arr)
hour = time_arr[1]
if (hour != "") {
hourly_count[hour]++
}
# Track requests per second per IP
gsub(/:.*/, "", timestamp) # Remove time, keep date
key = ip "|" domain "|" timestamp
count[key]++
user_agent[ip] = ua
}
}
END {
# Output hourly traffic patterns
for (h in hourly_count) {
print h "|" hourly_count[h] >> "/tmp/ct-limit-analysis-$$/hourly_traffic.txt"
}
for (key in count) {
split(key, parts, "|")
ip = parts[1]
dom = parts[2]
# Track max concurrent requests
if (count[key] > max_concurrent[ip "|" dom]) {
max_concurrent[ip "|" dom] = count[key]
}
total_requests[ip "|" dom] += count[key]
}
for (key in max_concurrent) {
split(key, parts, "|")
ip = parts[1]
dom = parts[2]
print ip "|" dom "|" max_concurrent[key] "|" total_requests[key] "|" user_agent[ip]
}
}' "$logfile" >> "$TEMP_ANALYSIS/connections_by_ip.txt" 2>/dev/null
done
print_success "Analyzed logs from $log_dir"
}
analyze_current_connections() {
print_status "Analyzing current active connections..."
if command -v ss &>/dev/null; then
# Count current connections per IP
ss -tn state established 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | sort -rn > "$TEMP_ANALYSIS/current_connections.txt"
elif command -v netstat &>/dev/null; then
netstat -tn | grep ESTABLISHED | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn > "$TEMP_ANALYSIS/current_connections.txt"
else
print_warning "Neither ss nor netstat available"
return 1
fi
print_success "Current connection analysis complete"
}
classify_ip_behavior() {
local ip="$1"
local user_agent="$2"
local max_concurrent="$3"
local total_requests="$4"
# Classify using bot signatures
local bot_type=$(classify_bot_type "$user_agent")
# Additional classification
local classification="unknown"
# Known good sources
if [ "$bot_type" = "legit" ]; then
classification="legitimate_bot"
elif [ "$bot_type" = "ai" ]; then
classification="ai_crawler"
elif [ "$bot_type" = "monitor" ]; then
classification="monitoring_service"
# CDN detection
elif [[ "$user_agent" =~ (Cloudflare|CloudFront|Akamai|Fastly|Sucuri) ]]; then
classification="cdn"
# High request rate but legitimate
elif [ "$max_concurrent" -gt 50 ] && [ "$total_requests" -gt 1000 ]; then
if [[ "$user_agent" =~ (Chrome|Firefox|Safari|Edge) ]]; then
classification="high_traffic_user"
else
classification="potential_scraper"
fi
# Normal user
elif [ "$max_concurrent" -lt 20 ]; then
classification="normal_user"
else
classification="moderate_user"
fi
echo "$classification"
}
calculate_percentile() {
local percentile="$1"
local data_file="$2"
# Sort data and get Nth percentile
local count=$(wc -l < "$data_file")
local position=$(awk -v p="$percentile" -v c="$count" 'BEGIN {printf "%.0f", (p/100) * c}')
[ "$position" -lt 1 ] && position=1
sort -n "$data_file" | sed -n "${position}p"
}
generate_recommendation() {
print_banner "CT_LIMIT Optimizer - Analysis Results"
echo ""
# Parse analysis data
local max_legitimate=0
local max_bot=0
local max_cdn=0
local total_ips=0
echo "Analyzing connection patterns..."
echo ""
# Create summary files
> "$TEMP_ANALYSIS/legitimate_connections.txt"
> "$TEMP_ANALYSIS/bot_connections.txt"
> "$TEMP_ANALYSIS/all_connections.txt"
# Skip header
tail -n +2 "$TEMP_ANALYSIS/connections_by_ip.txt" | while IFS='|' read -r ip domain max_concurrent total_requests user_agent; do
[ -z "$ip" ] && continue
local classification=$(classify_ip_behavior "$ip" "$user_agent" "$max_concurrent" "$total_requests")
((total_ips++))
# Track max connections by type
case "$classification" in
legitimate_bot|ai_crawler|monitoring_service|cdn)
echo "$max_concurrent" >> "$TEMP_ANALYSIS/bot_connections.txt"
[ "$max_concurrent" -gt "$max_bot" ] && max_bot=$max_concurrent
;;
normal_user|moderate_user|high_traffic_user)
echo "$max_concurrent" >> "$TEMP_ANALYSIS/legitimate_connections.txt"
[ "$max_concurrent" -gt "$max_legitimate" ] && max_legitimate=$max_concurrent
;;
esac
echo "$max_concurrent" >> "$TEMP_ANALYSIS/all_connections.txt"
done
# Calculate statistics
local legit_count=$(wc -l < "$TEMP_ANALYSIS/legitimate_connections.txt" 2>/dev/null || echo "0")
local bot_count=$(wc -l < "$TEMP_ANALYSIS/bot_connections.txt" 2>/dev/null || echo "0")
echo -e "${BOLD}Connection Analysis Summary:${NC}"
echo "──────────────────────────────────────────────────────────────"
echo " Total unique IPs analyzed: $total_ips"
echo " Legitimate users: $legit_count"
echo " Bots/CDNs/Crawlers: $bot_count"
echo ""
# Show per-site analysis
if [ -f "$TEMP_ANALYSIS/per_site_analysis.txt" ]; then
local site_count=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" | wc -l)
if [ "$site_count" -gt 0 ]; then
echo -e "${BOLD}Per-Site Analysis (All $site_count Sites Checked):${NC}"
echo "──────────────────────────────────────────────────────────────"
printf " %-30s %-12s %5s %8s %8s\n" "DOMAIN" "TYPE" "CMPLX" "MAX_CONN" "UNIQ_IPs"
echo " $(printf '─%.0s' {1..70})"
tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" | sort -t'|' -k4 -rn | head -15 | while IFS='|' read -r domain site_type complexity max_conn avg_conn unique_ips total_requests; do
# Truncate long domain names
local short_domain=$(echo "$domain" | cut -c1-28)
[ ${#domain} -gt 28 ] && short_domain="${short_domain}.."
# Color code by complexity
local color="${NC}"
if [ "$complexity" -ge 7 ]; then
color="${HIGH_COLOR}"
elif [ "$complexity" -ge 4 ]; then
color="${MEDIUM_COLOR}"
fi
printf " ${color}%-30s %-12s %5s %8s %8s${NC}\n" \
"$short_domain" "$site_type" "$complexity" "$max_conn" "$unique_ips"
done
local remaining=$((site_count - 15))
if [ "$remaining" -gt 0 ]; then
echo " ... and $remaining more sites analyzed"
fi
echo ""
# Identify high-complexity sites that need extra headroom
local high_complexity_sites=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" | awk -F'|' '$3 >= 7 {print $1}' | wc -l)
if [ "$high_complexity_sites" -gt 0 ]; then
echo -e "${MEDIUM_COLOR} ⚠️ $high_complexity_sites high-complexity sites detected${NC}"
echo " (WordPress/Ecommerce/Framework - need higher CT_LIMIT)"
echo ""
fi
fi
fi
# Calculate percentiles for legitimate traffic
if [ -s "$TEMP_ANALYSIS/legitimate_connections.txt" ]; then
local p95=$(calculate_percentile 95 "$TEMP_ANALYSIS/legitimate_connections.txt")
local p99=$(calculate_percentile 99 "$TEMP_ANALYSIS/legitimate_connections.txt")
echo -e "${BOLD}Legitimate User Connection Patterns:${NC}"
echo " Max concurrent from single IP: $max_legitimate"
echo " 95th percentile: $p95 concurrent connections"
echo " 99th percentile: $p99 concurrent connections"
echo ""
fi
# Check current connections
if [ -s "$TEMP_ANALYSIS/current_connections.txt" ]; then
local current_max=$(head -1 "$TEMP_ANALYSIS/current_connections.txt" | awk '{print $1}')
local current_max_ip=$(head -1 "$TEMP_ANALYSIS/current_connections.txt" | awk '{print $2}')
echo -e "${BOLD}Current Active Connections:${NC}"
echo " Highest right now: $current_max connections from $current_max_ip"
echo ""
fi
# Server resource limits
if [ -f "$TEMP_ANALYSIS/server_resources.txt" ]; then
IFS='|' read -r total_ram cpu_cores max_safe_conn < "$TEMP_ANALYSIS/server_resources.txt"
echo -e "${BOLD}Server Resource Limits:${NC}"
echo " RAM: ${total_ram}MB | CPU: ${cpu_cores} cores"
echo " Max safe connections (hardware): $max_safe_conn"
echo ""
fi
# Time-of-day patterns
if [ -f "$TEMP_ANALYSIS/hourly_traffic.txt" ]; then
local peak_hour=$(awk -F'|' '{if($2>max){max=$2; hour=$1}} END{print hour}' "$TEMP_ANALYSIS/hourly_traffic.txt")
local peak_requests=$(awk -F'|' '{if($2>max){max=$2}} END{print max}' "$TEMP_ANALYSIS/hourly_traffic.txt")
local avg_requests=$(awk -F'|' '{sum+=$2; count++} END{if(count>0) print int(sum/count)}' "$TEMP_ANALYSIS/hourly_traffic.txt")
if [ -n "$peak_hour" ] && [ "$peak_requests" -gt "$((avg_requests * 2))" ]; then
echo -e "${BOLD}Traffic Patterns:${NC}"
echo " Peak hour: ${peak_hour}:00 (${peak_requests} requests)"
echo " Average: ${avg_requests} requests/hour"
echo " Peak is ${MEDIUM_COLOR}$((peak_requests * 100 / avg_requests))% above average${NC}"
echo " ${DIM}→ CT_LIMIT should handle peak, not average${NC}"
echo ""
fi
fi
# Optimization opportunities
local opt_count=0
echo -e "${BOLD}Optimization Opportunities:${NC}"
# Check for sites without CDN
local no_cdn=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" 2>/dev/null | while IFS='|' read -r domain site_type complexity max_conn avg_conn unique_ips total_requests; do
if [ "$complexity" -ge 6 ]; then
local has_cdn=$(detect_cdn_usage "$domain" 2>/dev/null)
if [ "$has_cdn" = "no" ]; then
echo "$domain"
((opt_count++))
fi
fi
done | head -3)
if [ -n "$no_cdn" ]; then
echo " 📦 CDN Recommended for:"
echo "$no_cdn" | while read -r domain; do
echo "$domain (would reduce CT_LIMIT need)"
done
fi
# Check for WordPress without caching
local no_cache=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" 2>/dev/null | grep "wordpress" | while IFS='|' read -r domain site_type complexity max_conn avg_conn unique_ips total_requests; do
# Get doc root from sysref
local doc_root=$(grep "^DOMAIN|$domain|" "$SYSREF_DB" 2>/dev/null | cut -d'|' -f4)
if [ -n "$doc_root" ]; then
local cache_score=$(detect_caching "$doc_root" 2>/dev/null)
if [ "$cache_score" -eq 0 ]; then
echo "$domain"
fi
fi
done | head -3)
if [ -n "$no_cache" ]; then
echo " ⚡ Caching Recommended for:"
echo "$no_cache" | while read -r domain; do
echo "$domain (WP Rocket, Redis, or W3 Total Cache)"
done
fi
if [ -z "$no_cdn" ] && [ -z "$no_cache" ]; then
echo " ✅ Sites are well-optimized (CDN + caching in place)"
fi
echo ""
# Current CSF setting
local current_ct=$(get_current_ct_limit)
echo -e "${BOLD}Current CSF Configuration:${NC}"
echo " CT_LIMIT = $current_ct"
echo ""
# Generate recommendation
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${BOLD}📊 RECOMMENDED CT_LIMIT VALUES${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Calculate safe recommendations
local conservative=$((max_legitimate + 20))
local balanced=$((max_legitimate + 10))
local aggressive=$((max_legitimate + 5))
# Factor in site complexity - high-complexity sites need more headroom
if [ -f "$TEMP_ANALYSIS/per_site_analysis.txt" ]; then
local avg_complexity=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" | awk -F'|' '{sum+=$3; count++} END {if(count>0) print int(sum/count); else print 0}')
local max_complexity=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" | awk -F'|' '{if($3>max) max=$3} END {print max+0}')
# Add complexity buffer (0-20 based on average complexity)
local complexity_buffer=$((avg_complexity * 2))
conservative=$((conservative + complexity_buffer))
balanced=$((balanced + (complexity_buffer / 2)))
# If we have ecommerce sites, be extra conservative
local has_ecommerce=$(tail -n +2 "$TEMP_ANALYSIS/per_site_analysis.txt" | grep -c "ecommerce")
if [ "$has_ecommerce" -gt 0 ]; then
conservative=$((conservative + 15))
balanced=$((balanced + 10))
fi
fi
# Minimum safety thresholds
[ "$conservative" -lt 100 ] && conservative=100
[ "$balanced" -lt 80 ] && balanced=80
[ "$aggressive" -lt 50 ] && aggressive=50
# Cap at server's max safe capacity
if [ -f "$TEMP_ANALYSIS/server_resources.txt" ]; then
IFS='|' read -r total_ram cpu_cores max_safe_conn < "$TEMP_ANALYSIS/server_resources.txt"
if [ "$conservative" -gt "$max_safe_conn" ]; then
conservative=$max_safe_conn
echo " ${MEDIUM_COLOR}Note: Conservative capped at server max ($max_safe_conn)${NC}" >&2
fi
if [ "$balanced" -gt "$max_safe_conn" ]; then
balanced=$max_safe_conn
fi
fi
echo -e "${BOLD}1. CONSERVATIVE${NC} (Recommended for high-traffic sites)"
echo " CT_LIMIT = $conservative"
echo " • Allows headroom for traffic spikes"
echo " • Won't block legitimate users"
echo " • Good protection against moderate attacks"
echo ""
echo -e "${BOLD}2. BALANCED${NC} (Recommended for most servers) ⭐"
echo " CT_LIMIT = $balanced"
echo " • Balances security and usability"
echo " • Based on 99th percentile + buffer"
echo " • Blocks most attack traffic"
echo ""
echo -e "${BOLD}3. AGGRESSIVE${NC} (Only if under active attack)"
echo " CT_LIMIT = $aggressive"
echo " • Tight connection limits"
echo " • May affect some legitimate users"
echo " • Maximum DDoS protection"
echo ""
# Whitelist recommendations
if [ "$bot_count" -gt 0 ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${BOLD}⚠️ WHITELIST RECOMMENDATIONS${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Found bots/crawlers with high connection counts."
echo "Consider whitelisting these IPs to prevent blocking:"
echo ""
# Show top legitimate bots
tail -n +2 "$TEMP_ANALYSIS/connections_by_ip.txt" | while IFS='|' read -r ip domain max_concurrent total_requests user_agent; do
[ -z "$ip" ] && continue
local classification=$(classify_ip_behavior "$ip" "$user_agent" "$max_concurrent" "$total_requests")
if [[ "$classification" =~ (legitimate_bot|ai_crawler|monitoring_service) ]]; then
local bot_name=$(echo "$user_agent" | grep -oE '(Googlebot|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|SemrushBot|AhrefsBot|facebookexternalhit|Twitterbot|LinkedInBot|UptimeRobot|Pingdom)' | head -1)
[ -z "$bot_name" ] && bot_name="Bot"
if [ "$max_concurrent" -gt "$balanced" ]; then
printf " • %-15s (%-20s) %3d connections\n" "$ip" "$bot_name" "$max_concurrent"
fi
fi
done | sort -t'(' -k2 -u | head -10
echo ""
echo "To whitelist: Add to /etc/csf/csf.ignore or use: csf -a <IP>"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${BOLD}🔧 HOW TO APPLY${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "1. Edit CSF configuration:"
echo " ${BOLD}nano /etc/csf/csf.conf${NC}"
echo ""
echo "2. Find CT_LIMIT line and change to recommended value:"
echo " ${BOLD}CT_LIMIT = \"$balanced\"${NC}"
echo ""
echo "3. Enable SYNFLOOD protection (if not already):"
echo " ${BOLD}SYNFLOOD = \"1\"${NC}"
echo ""
echo "4. Apply changes:"
echo " ${BOLD}csf -r${NC}"
echo ""
echo "5. Monitor effectiveness:"
echo " ${BOLD}watch -n 2 'csf -g | tail -20'${NC}"
echo ""
# Save recommendation to file
cat > "$TEMP_ANALYSIS/recommendation.txt" <<EOF
CT_LIMIT Optimization Report
Generated: $(date)
Analysis Period: Last $ANALYSIS_HOURS hours
RECOMMENDED VALUES:
- Conservative: CT_LIMIT = "$conservative"
- Balanced: CT_LIMIT = "$balanced" (RECOMMENDED)
- Aggressive: CT_LIMIT = "$aggressive"
ANALYSIS DATA:
- Total IPs analyzed: $total_ips
- Legitimate users: $legit_count
- Bots/Crawlers: $bot_count
- Max legitimate connections: $max_legitimate
- Current CT_LIMIT: $current_ct
To apply balanced recommendation:
1. Edit /etc/csf/csf.conf
2. Set: CT_LIMIT = "$balanced"
3. Run: csf -r
EOF
echo "Full report saved to: $TEMP_ANALYSIS/recommendation.txt"
echo ""
}
apply_recommendation() {
local new_limit="$1"
if [ ! -f "/etc/csf/csf.conf" ]; then
print_error "CSF not installed or config not found"
return 1
fi
print_status "Backing up CSF configuration..."
cp /etc/csf/csf.conf "/etc/csf/csf.conf.backup.$(date +%Y%m%d_%H%M%S)"
print_status "Setting CT_LIMIT = $new_limit..."
sed -i "s/^CT_LIMIT = .*/CT_LIMIT = \"$new_limit\"/" /etc/csf/csf.conf
# Also ensure SYNFLOOD is enabled
if grep -q "^SYNFLOOD = \"0\"" /etc/csf/csf.conf; then
print_status "Enabling SYNFLOOD protection..."
sed -i 's/^SYNFLOOD = "0"/SYNFLOOD = "1"/' /etc/csf/csf.conf
fi
print_status "Restarting CSF..."
csf -r >/dev/null 2>&1
print_success "CT_LIMIT updated to $new_limit and CSF restarted!"
echo ""
echo "Monitor effectiveness with: csf -g | tail -20"
}
################################################################################
# Main
################################################################################
main() {
# Check for auto mode
local AUTO_MODE=0
if [ "${1:-}" = "--auto" ] || [ "${1:-}" = "-a" ]; then
AUTO_MODE=1
fi
if [ "${AUTO_MODE:-0}" -eq 0 ]; then
clear
print_banner "CT_LIMIT Optimizer - Intelligent Connection Limit Calculator"
echo ""
echo "This tool analyzes your actual traffic patterns to recommend"
echo "an optimal CT_LIMIT that protects against DDoS without blocking"
echo "legitimate users, bots, and CDNs."
echo ""
echo "Analysis period: Last $ANALYSIS_HOURS hours"
echo ""
read -p "Press Enter to start analysis or Ctrl+C to cancel..."
echo ""
else
echo "Running CT_LIMIT analysis in auto mode..."
echo ""
fi
# Check if sysref database exists, build if needed
if [ ! -f "$SYSREF_DB" ] || [ ! -s "$SYSREF_DB" ]; then
print_status "Building system reference database (first run)..."
build_reference_database >/dev/null 2>&1
fi
# Run analysis
check_server_resources
analyze_apache_logs "$ANALYSIS_HOURS"
analyze_per_site_traffic
analyze_current_connections
# Generate and show recommendations
generate_recommendation
# Apply automatically in auto mode, otherwise ask
if [ "${AUTO_MODE:-0}" -eq 1 ]; then
# Extract balanced value from recommendation
local balanced=$(grep "2. BALANCED" -A1 "$TEMP_ANALYSIS/recommendation.txt" | grep "CT_LIMIT" | grep -oE '[0-9]+')
if [ -n "$balanced" ]; then
echo ""
echo "Auto-applying BALANCED recommendation..."
apply_recommendation "$balanced"
else
print_error "Could not determine balanced recommendation value"
return 1
fi
else
# Offer to apply
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
read -p "Would you like to apply the BALANCED recommendation automatically? (y/n): " apply
if [[ "$apply" =~ ^[Yy] ]]; then
# Extract balanced value from recommendation
local balanced=$(grep "2. BALANCED" -A1 "$TEMP_ANALYSIS/recommendation.txt" | grep "CT_LIMIT" | grep -oE '[0-9]+')
if [ -n "$balanced" ]; then
apply_recommendation "$balanced"
else
print_error "Could not determine balanced recommendation value"
fi
else
echo ""
echo "No changes made. You can apply manually using the commands above."
fi
fi
echo ""
if [ "${AUTO_MODE:-0}" -eq 0 ]; then
print_success "Analysis complete!"
fi
}
main "$@"