别让黑客打爆你的电话!FreeSwitch + Fail2Ban 自动化防护深度解析

72 阅读11分钟

一句话精华:一个Python脚本解决FreeSwitch暴力攻击,让SIP服务器从"肉鸡"变"堡垒"

凌晨两点,运维小张被手机铃声吵醒。客户的FreeSwitch服务器又被攻击了——服务器CPU飙到100%,上千个失败的SIP注册请求疯狂涌入,话费账单上出现了几十个陌生的国际长途。这已经是本月第三次了。

"能不能写个脚本,自动把这些攻击者封禁掉?"客户的质问让小张彻夜难眠。如果你也遇到过类似的问题,或者正在运维FreeSwitch服务器,那么这篇文章将为你揭秘:如何用一个Python脚本,构建自动化的SIP攻击防护系统

读完本文你将收获:

  • ✅ 理解VoIP系统常见的4种攻击方式
  • ✅ 掌握Fail2Ban防护机制的工作原理
  • ✅ 学会用Python自动化部署安全策略
  • ✅ 获得一套生产环境可用的防护脚本

🌟 基础篇:FreeSwitch的安全困境

什么是FreeSwitch?

FreeSwitch是一个开源的电信平台,可以理解为"软件版的电话交换机"。它能处理SIP协议的语音、视频通话,广泛应用于呼叫中心、企业电话系统、视频会议等场景。但正因为它需要对外开放端口(通常是5060、5080等),就成了黑客的重点攻击目标。

黑客是怎么攻击的?

想象你家门口的门铃,正常人按一次就够了。但黑客会带一个机器人,每秒按1000次门铃,还用各种假名字尝试开锁。在VoIP世界里,这些攻击主要有四种形式:

1. SIP暴力注册攻击 🔐
黑客用自动化工具疯狂尝试用户名和密码组合,企图破解合法账号。就像小偷拿着一串钥匙挨个试你家的锁。

2. SIP扫描攻击 🔍
攻击者大量发送"探测性请求",扫描哪些用户存在、哪些端口开放。这就像小偷先踩点,看哪户人家有钱。

3. SIP洪水攻击 🌊
短时间内发送海量SIP请求,耗尽服务器资源。相当于用DDoS把你家门铃堵死。

4. SIP暴力破解 💣
针对已知用户名,暴力破解密码。最直接也最暴力的方式。

为什么需要自动化防护?

手动封禁IP?太慢了。等你发现攻击、找到IP、敲完iptables命令,黑客已经尝试了几千次登录。我们需要的是:

  • 毫秒级响应:发现攻击立即封禁
  • 🤖 无人值守:不需要运维24小时盯着
  • 🧠 智能识别:区分正常请求和恶意攻击
  • 🛡️ 多层防护:结合日志分析、防火墙规则、服务监控

这就是Fail2Ban + Python自动化脚本的价值所在。

最小可运行示例

在深入复杂脚本之前,先看一个最简单的防护逻辑:

import re
import os

# 读取FreeSwitch日志
with open('/var/log/freeswitch/freeswitch.log', 'r') as f:
    for line in f:
        # 检测失败的注册尝试
        if "Can't find user" in line:
            # 提取攻击者IP
            match = re.search(r'from ([\d.]+)', line)
            if match:
                attack_ip = match.group(1)
                # 封禁IP
                os.system(f"iptables -I INPUT -s {attack_ip} -j DROP")
                print(f"已封禁攻击IP: {attack_ip}")

这个10行代码已经能实现基础防护了!但生产环境需要更多考虑:误封、配置管理、服务重启、日志轮转……这就是我们要深入分析的753行脚本解决的问题。

🔍 深入篇:自动化防护系统的工作原理

整体架构设计

这套防护系统采用"三层防御"架构:

graph TD
    A[攻击者发起SIP请求] --> B[FreeSwitch处理请求]
    B --> C[记录失败日志]
    C --> D[Fail2Ban监控日志]
    D --> E{匹配攻击特征?}
    E -->|是| F[触发防御规则]
    E -->|否| G[继续监控]
    F --> H[添加iptables规则]
    H --> I[封禁攻击IP]
    I --> J[记录封禁日志]
    J --> K[定时解封或永久封禁]

💡 图解说明

  • 第一层:FreeSwitch日志记录(数据源)
  • 第二层:Fail2Ban模式匹配(智能大脑)
  • 第三层:iptables防火墙(执行层)

核心类设计:FreeSwitchFail2BanSetup

脚本采用面向对象设计,核心类FreeSwitchFail2BanSetup封装了所有部署逻辑:

class FreeSwitchFail2BanSetup:
    def __init__(self):
        self.freeswitch_conf_path = "/usr/local/freeswitch/conf"
        self.freeswitch_log_path = "/var/log/freeswitch"
        self.fail2ban_jail_path = "/etc/fail2ban/jail.d"
        self.fail2ban_filter_path = "/etc/fail2ban/filter.d"
        self.install_type = None

这个类就像一个"防护管家",知道所有配置文件的位置,能处理从检查系统到部署配置的全流程。

安装流程时序图

sequenceDiagram
    participant User as 用户
    participant Script as Python脚本
    participant System as 系统
    participant Fail2Ban as Fail2Ban服务
    
    User->>Script: 执行脚本
    Script->>System: 检查系统要求
    System-->>Script: 返回检查结果
    Script->>System: 检查现有安装
    System-->>Script: 返回安装状态
    
    alt 需要安装fail2ban
        Script->>System: apt install fail2ban
        System-->>Script: 安装完成
    end
    
    Script->>System: 读取FreeSwitch配置
    System-->>Script: 返回端口、日志路径
    Script->>System: 创建Filter规则
    Script->>System: 创建Jail配置
    Script->>Fail2Ban: 重启服务
    Fail2Ban-->>Script: 服务启动成功
    Script->>System: 封禁已知攻击IP
    Script->>User: 显示防护状态

关键技术细节解析

1. 智能日志路径查找

FreeSwitch的日志路径可能因安装方式而异,脚本实现了智能查找机制,尝试多个可能的路径,如果都没找到,还会检查系统日志,最后创建默认路径。这保证了在各种安装环境下都能正常工作。

2. 正则表达式攻击特征匹配

Fail2Ban的核心是正则表达式匹配,脚本为4种攻击创建了专门的Filter:

# SIP暴力注册攻击特征
failregex = ^\\s*.*Can't find user \\[.*@.*\\] from <HOST>.*
            ^\\s*.*WARNING sofia_reg\\.c:.*Can't find user.*from <HOST>.*
            ^.*SIP auth failure.* from <HOST>
            ^.*Registration failed.* from <HOST>

这些正则表达式匹配日志中的攻击模式,<HOST>是Fail2Ban的特殊标记,会自动提取IP地址。

3. 动态配置生成

根据FreeSwitch的实际配置(端口、日志路径),动态生成Jail配置:

def create_freeswitch_jail(self, config):
    jail_config = f'''
[freeswitch-sip-registration]
enabled = true
filter = freeswitch-sip-registration
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 3
findtime = 300
bantime = 7200
'''

参数含义

  • maxretry = 3:300秒内失败3次就封禁
  • findtime = 300:检测时间窗口5分钟
  • bantime = 7200:封禁2小时

4. 带超时的命令执行

防止服务重启卡死,所有关键命令都加了超时控制。这在fail2ban服务重启时特别重要——有时服务会卡住几十秒。

4种防护规则对比

防护类型触发条件封禁时长适用场景严格程度
SIP注册攻击5分钟内失败3次2小时暴力破解注册⭐⭐⭐⭐
SIP扫描攻击1分钟内失败2次1小时端口扫描探测⭐⭐⭐⭐⭐
SIP洪水攻击1分钟内失败10次1小时DDoS攻击⭐⭐⭐
SIP暴力破解5分钟内失败5次2小时密码爆破⭐⭐⭐⭐

💡 策略说明:扫描攻击最严格(2次就封),因为正常用户不会频繁触发"用户不存在"错误。

⚡ 实战篇:真实场景应用

场景1:全新服务器部署

某创业公司刚搭建好FreeSwitch服务器,需要快速部署防护:

# 1. 下载脚本
wget https://example.com/freeswitch_fail2ban_setup.py

# 2. 赋予执行权限
chmod +x freeswitch_fail2ban_setup.py

# 3. root权限运行
sudo python3 freeswitch_fail2ban_setup.py

脚本自动完成

  • ✅ 检测系统环境(Debian/Ubuntu)
  • ✅ 安装fail2ban软件包
  • ✅ 读取FreeSwitch配置(端口、日志)
  • ✅ 创建4种攻击防护规则
  • ✅ 备份原有配置
  • ✅ 重启服务并验证

实际效果:部署后1小时内,自动封禁了17个扫描IP,服务器CPU使用率从75%降到15%。

场景2:现有系统升级防护

某呼叫中心已部署fail2ban,但只有SSH防护,需要增加SIP防护:

脚本智能识别安装状态:

⚠️  检测到fail2ban已安装,但未配置FreeSwitch规则
✅ 将仅配置FreeSwitch防护规则

只创建缺失的SIP防护配置,不影响现有SSH等规则。

场景3:紧急处理攻击事件

凌晨3点,FreeSwitch被攻击,管理员紧急执行脚本:

sudo python3 freeswitch_fail2ban_setup.py

脚本除了部署防护规则,还会立即封禁已知攻击IP

def ban_known_attackers(self):
    known_attackers = [
        "xx.xx.xx.xx",
        "xx.xx.xx.xx",
    ]
    for attack_ip in known_attackers:
        os.system(f"iptables -I INPUT -s {attack_ip} -j DROP")

这是一个有趣的设计——脚本内置了"黑名单",可以立即阻断正在进行的攻击。

最佳实践总结

✅ DO(推荐做法)

1. 定期检查封禁日志

# 查看今天封禁的IP
grep "Ban" /var/log/fail2ban.log | grep $(date +%Y-%m-%d)

2. 监控误封情况

# 解封被误封的IP
fail2ban-client set freeswitch-sip-registration unbanip 1.2.3.4

3. 持久化iptables规则

# 保存规则,重启后生效
iptables-save > /etc/iptables/rules.v4

4. 配置日志轮转防止磁盘满

# /etc/logrotate.d/freeswitch
/var/log/freeswitch/*.log {
    daily
    rotate 7
    compress
    delaycompress
}

❌ DON'T(避免这样做)

1. 不要盲目降低封禁阈值

maxretry = 1  # ❌ 太严格,容易误封正常用户

2. 不要永久封禁IP

bantime = -1  # ❌ IP可能被重新分配给其他用户

3. 不要忽略服务监控 定期检查fail2ban服务状态:

systemctl status fail2ban
fail2ban-client ping

常见陷阱与避坑指南

陷阱1:日志路径不匹配

现象:防护规则不生效,fail2ban状态显示0个匹配。

原因:配置的logpath与实际日志路径不一致。

解决

# 检查实际日志路径
ls -la /var/log/freeswitch/

# 手动修改jail配置
vim /etc/fail2ban/jail.d/freeswitch.conf

陷阱2:正则表达式过于宽松

现象:大量正常用户被误封。

原因:failregex匹配了正常的连接失败。

解决:使用fail2ban测试工具验证:

fail2ban-regex /var/log/freeswitch/freeswitch.log \
    /etc/fail2ban/filter.d/freeswitch-sip-registration.conf

陷阱3:服务重启卡死

现象:执行脚本时卡在"重启fail2ban服务"步骤。

原因:fail2ban服务偶尔会因socket文件问题卡住。

解决:脚本已内置修复逻辑,自动删除旧socket文件并强制重启。

💡 总结与进阶

知识脉络回顾

让我们串联一下整个防护体系的关键点:

  1. 攻击识别:通过正则表达式匹配日志中的4种攻击特征
  2. 自动响应:Fail2Ban检测到攻击后自动添加iptables规则
  3. 智能配置:Python脚本自动读取FreeSwitch配置并生成防护规则
  4. 容错处理:处理各种异常情况(日志路径、服务重启、APT源问题)
  5. 运维友好:一键部署、自动备份、状态监控

记忆口诀

"一日志,二匹配,三封禁,四监控"

  • 一日志:确保FreeSwitch正确记录攻击日志
  • 二匹配:Fail2Ban用正则表达式匹配攻击特征
  • 三封禁:iptables自动封禁攻击IP
  • 四监控:持续监控防护状态和误封情况

延伸学习方向

如果你对VoIP安全感兴趣,接下来可以学习

  1. SIP协议深度解析:理解INVITE、REGISTER、ACK等消息流程
  2. FreeSWITCH模块开发:用C/Lua开发自定义防护模块
  3. iptables进阶:conntrack连接跟踪、ipset批量封禁
  4. 威胁情报集成:对接威胁情报平台,提前封禁恶意IP
  5. 蜜罐技术:部署SIP蜜罐,主动诱捕攻击者

推荐工具

  • sngrep:SIP协议抓包分析神器
  • sipvicious:SIP安全扫描工具(用于测试防护效果)
  • fail2ban-dashboard:Web可视化监控面板

脚本核心功能清单

这个753行的Python脚本主要实现了以下功能:

  • 智能环境检测:自动识别系统、权限、FreeSwitch安装
  • 灵活安装模式:支持全新安装、仅配置、重新安装三种模式
  • 自动依赖处理:智能修复APT源问题、自动安装fail2ban
  • 动态配置生成:根据实际环境生成最优配置
  • 4层攻击防护:注册、扫描、洪水、暴力破解全覆盖
  • 健壮错误处理:超时控制、服务修复、配置验证
  • 运维管理功能:状态查看、手动封禁、一键卸载
  • 紧急响应能力:内置黑名单立即封禁已知攻击者

📖 附录:完整脚本代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeSwitch Fail2Ban 一键安装配置脚本
用于防护FreeSwitch暴力注册和呼叫轰炸攻击
支持Debian系统
"""

import os
import sys
import subprocess
import json
import re
import time
from pathlib import Path

class FreeSwitchFail2BanSetup:
    def __init__(self):
        self.freeswitch_conf_path = "/usr/local/freeswitch/conf"
        self.freeswitch_log_path = "/var/log/freeswitch"
        self.fail2ban_jail_path = "/etc/fail2ban/jail.d"
        self.fail2ban_filter_path = "/etc/fail2ban/filter.d"
        self.config_backup_path = "/tmp/freeswitch_fail2ban_backup"
        self.install_type = None  # 安装类型:full_install, configure_only, reinstall

    def run_command(self, command, check=True, capture_output=True):
        """执行系统命令"""
        try:
            result = subprocess.run(command, shell=True, check=check,
                                  capture_output=capture_output, text=True)
            return result.stdout.strip() if capture_output else ""
        except subprocess.CalledProcessError as e:
            print(f"❌ 命令执行失败: {command}")
            print(f"错误信息: {e.stderr}")
            return None

    def check_existing_installation(self):
        """检查是否已安装fail2ban"""
        print("🔍 检查现有安装...")

        # 检查fail2ban是否已安装
        fail2ban_installed = self.run_command("which fail2ban-client", check=False) is not None

        # 检查FreeSwitch配置是否已存在
        freeswitch_config_exists = os.path.exists(os.path.join(self.fail2ban_jail_path, "freeswitch.conf"))

        if fail2ban_installed and freeswitch_config_exists:
            print("⚠️  检测到FreeSwitch fail2ban配置已存在")
            choice = input("是否要重新安装并覆盖现有配置?[y/N]: ").lower().strip()
            if choice != 'y' and choice != 'yes':
                print("🚫 用户取消安装")
                return False, "existing_installation"

            print("🔄 将重新安装并覆盖现有配置...")
            return True, "reinstall"

        if fail2ban_installed and not freeswitch_config_exists:
            print("✅ fail2ban已安装,但未配置FreeSwitch规则")
            return True, "configure_only"

        if not fail2ban_installed:
            print("✅ fail2ban未安装,将进行完整安装")
            return True, "full_install"

        return True, "unknown"

    def check_system_requirements(self):
        """检查系统要求"""
        print("🔍 检查系统要求...")

        # 检查是否为Debian系统
        if not os.path.exists("/etc/debian_version"):
            print("❌ 此脚本仅支持Debian系统")
            return False

        # 检查root权限
        if os.geteuid() != 0:
            print("❌ 请使用root权限运行此脚本")
            return False

        # 检查FreeSwitch安装
        if not os.path.exists(self.freeswitch_conf_path):
            print(f"❌ 未找到FreeSwitch配置目录: {self.freeswitch_conf_path}")
            return False

        # 检查现有安装状态
        install_ok, install_type = self.check_existing_installation()
        if not install_ok:
            return False

        self.install_type = install_type
        print("✅ 系统要求检查通过")
        return True

    def find_freeswitch_logs(self):
        """智能查找FreeSwitch日志文件"""
        possible_log_paths = [
            "/var/log/freeswitch/freeswitch.log",
            "/var/log/freeswitch.log",
            "/usr/local/freeswitch/log/freeswitch.log",
            "/opt/freeswitch/log/freeswitch.log",
            "/var/log/freeswitch/freeswitch.log.1",
            "/var/log/freeswitch.log.1"
        ]

        found_logs = []
        for log_path in possible_log_paths:
            if os.path.exists(log_path):
                found_logs.append(log_path)

        # 如果都没找到,尝试查找系统日志中的FreeSwitch条目
        if not found_logs:
            # 检查系统日志
            system_logs = ["/var/log/syslog", "/var/log/messages"]
            for sys_log in system_logs:
                if os.path.exists(sys_log):
                    # 检查是否有FreeSwitch日志条目
                    if self.run_command(f"grep -q 'freeswitch' {sys_log}", check=False):
                        found_logs.append(sys_log)
                        break

        # 如果仍然没找到,创建一个默认的日志路径
        if not found_logs:
            print("⚠️  未找到FreeSwitch日志文件,将创建默认配置")
            default_log = "/var/log/freeswitch/freeswitch.log"
            # 创建日志目录
            os.makedirs(os.path.dirname(default_log), exist_ok=True)
            # 创建一个空的日志文件
            Path(default_log).touch()
            found_logs.append(default_log)

        return found_logs

    def get_freeswitch_config(self):
        """读取FreeSwitch配置信息"""
        print("🔍 读取FreeSwitch配置...")

        config = {
            "sip_ports": [],
            "tls_ports": [],
            "log_files": []
        }

        # 读取vars.xml获取端口信息
        vars_xml = os.path.join(self.freeswitch_conf_path, "vars.xml")
        if os.path.exists(vars_xml):
            content = self.read_file(vars_xml)
            if content:
                # 查找SIP端口
                sip_ports = re.findall(r'<X-PRE-PROCESS\s+cmd="set"\s+data="sip_port=(\d+)"', content)
                config["sip_ports"] = list(set(sip_ports))

                # 查找TLS端口
                tls_ports = re.findall(r'<X-PRE-PROCESS\s+cmd="set"\s+data="tls_sip_port=(\d+)"', content)
                config["tls_ports"] = list(set(tls_ports))

        # 设置默认端口
        if not config["sip_ports"]:
            config["sip_ports"] = ["5060", "5080"]
        if not config["tls_ports"]:
            config["tls_ports"] = ["5061", "5081"]

        # 智能查找日志文件
        config["log_files"] = self.find_freeswitch_logs()

        print(f"✅ 发现SIP端口: {', '.join(config['sip_ports'])}")
        print(f"✅ 发现TLS端口: {', '.join(config['tls_ports'])}")
        print(f"✅ 发现日志文件: {', '.join(config['log_files'])}")

        return config

    def read_file(self, filepath):
        """读取文件内容"""
        try:
            with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
                return f.read()
        except Exception as e:
            print(f"❌ 读取文件失败 {filepath}: {e}")
            return None

    def write_file(self, filepath, content):
        """写入文件内容"""
        try:
            os.makedirs(os.path.dirname(filepath), exist_ok=True)
            with open(filepath, 'w', encoding='utf-8') as f:
                f.write(content)
            return True
        except Exception as e:
            print(f"❌ 写入文件失败 {filepath}: {e}")
            return False

    def fix_apt_sources(self):
        """修复APT源问题"""
        print("🔧 修复APT源问题...")

        # 临时禁用有问题的MySQL源
        mysql_sources = [
            "/etc/apt/sources.list.d/mysql.list",
            "/etc/apt/sources.list.d/mysql-apt-config.list"
        ]

        for source_file in mysql_sources:
            if os.path.exists(source_file):
                backup_file = f"{source_file}.backup"
                self.run_command(f"mv {source_file} {backup_file}", check=False)
                print(f"✅ 已备份并禁用: {source_file}")

        # 尝试更新,忽略GPG错误
        print("⚠️  尝试更新包列表(忽略GPG警告)...")
        result = self.run_command("apt update --allow-unauthenticated 2>/dev/null || apt update --allow-insecure-repositories 2>/dev/null || apt update", check=False)

        if result is None:
            print("⚠️  APT更新失败,尝试使用缓存...")
            # 如果更新失败,尝试直接安装
            return True

        print("✅ APT源问题已处理")
        return True

    def install_fail2ban(self):
        """安装fail2ban"""
        # 根据安装类型决定操作
        if hasattr(self, 'install_type') and self.install_type in ['configure_only', 'reinstall']:
            print("📦 fail2ban已安装,跳过安装步骤...")
            return True

        print("📦 安装fail2ban...")

        # 修复APT源问题
        if not self.fix_apt_sources():
            print("⚠️  APT源修复失败,尝试继续安装...")

        # 尝试安装fail2ban(多种方法)
        install_commands = [
            "apt install -y fail2ban",
            "apt-get install -y fail2ban --force-yes",
            "DEBIAN_FRONTEND=noninteractive apt install -y fail2ban -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\""
        ]

        for cmd in install_commands:
            print(f"尝试安装命令: {cmd}")
            if self.run_command(cmd, check=False):
                print("✅ fail2ban安装成功")
                break
        else:
            print("❌ fail2ban安装失败")
            return False

        # 启动并启用fail2ban服务
        self.run_command("systemctl start fail2ban", check=False)
        self.run_command("systemctl enable fail2ban", check=False)

        # 验证安装
        if self.run_command("which fail2ban-client", check=False):
            print("✅ fail2ban安装完成")
            return True
        else:
            print("❌ fail2ban安装验证失败")
            return False

    def create_freeswitch_filters(self):
        """创建FreeSwitch过滤器规则"""
        print("🛡️  创建FreeSwitch过滤器规则...")

        # SIP暴力注册攻击过滤器(增强版本,覆盖更多攻击模式)
        sip_registration_filter = '''# Fail2Ban filter for FreeSwitch SIP registration attacks - Enhanced
[Definition]
failregex = ^\\s*.*Can't find user \\[.*@.*\\] from <HOST>.*
            ^\\s*.*WARNING sofia_reg\\.c:.*Can't find user.*from <HOST>.*
            ^.*SIP auth failure.* from <HOST>
            ^.*Registration failed.* from <HOST>
            ^.*Failed authentication.* from <HOST>
            ^.*Authentication failed.* from <HOST>
            ^.*Invalid password.* from <HOST>
            ^.*Failed to authenticate.* from <HOST>
            ^.*Bad credentials.* from <HOST>
ignoreregex =
'''

        # SIP扫描攻击过滤器(新增,专门处理扫描攻击)
        sip_scan_filter = '''# Fail2Ban filter for FreeSwitch SIP scanning attacks
[Definition]
failregex = ^\\s*.*Can't find user .* from <HOST>
            ^\\s*.*New Channel sofia/internal/.*@<HOST>.*CALL_REJECTED
            ^\\s*.*WARNING sofia_reg\\.c:.*Can't find user.*from <HOST>.*
            ^\\s*.*sofia/internal/.*@<HOST>.*Can't find user
ignoreregex =
'''

        # SIP呼叫轰炸攻击过滤器(简化版本)
        sip_flood_filter = '''# Fail2Ban filter for FreeSwitch SIP flood attacks
[Definition]
failregex = ^.*SIP message length exceeded.* from <HOST>
            ^.*Invalid SIP packet.* from <HOST>
            ^.*SIP parsing failed.* from <HOST>
            ^.*Malformed SIP message.* from <HOST>
            ^.*Rate limit exceeded.* <HOST>
ignoreregex =
'''

        # SIP暴力破解密码过滤器(简化版本)
        sip_bruteforce_filter = '''# Fail2Ban filter for FreeSwitch SIP brute force attacks
[Definition]
failregex = ^.*Invalid password.* from <HOST>
            ^.*Failed to authenticate.* from <HOST>
            ^.*Bad credentials.* from <HOST>
ignoreregex =
'''

        # 写入过滤器文件(包含新增的SIP扫描过滤器)
        filters = {
            "freeswitch-sip-registration.conf": sip_registration_filter,
            "freeswitch-sip-scan.conf": sip_scan_filter,  # 新增
            "freeswitch-sip-flood.conf": sip_flood_filter,
            "freeswitch-sip-bruteforce.conf": sip_bruteforce_filter
        }

        for filename, content in filters.items():
            filepath = os.path.join(self.fail2ban_filter_path, filename)

            # 如果是重新安装,先删除旧文件
            if hasattr(self, 'install_type') and self.install_type == 'reinstall':
                if os.path.exists(filepath):
                    os.remove(filepath)
                    print(f"   删除旧文件: {filename}")

            if self.write_file(filepath, content):
                print(f"✅ 创建过滤器: {filename}")
            else:
                print(f"❌ 创建过滤器失败: {filename}")
                return False

        return True

    def create_freeswitch_jail(self, config):
        """创建FreeSwitch jail配置"""
        print("🔒 创建FreeSWitch jail配置...")

        # 使用主日志文件,避免重复logpath选项
        main_log_file = config["log_files"][0] if config["log_files"] else "/var/log/freeswitch.log"

        jail_config = f'''# FreeSwitch Fail2Ban Configuration
# Generated by FreeSwitch Fail2Ban Setup Script
# Enhanced with SIP scanning attack protection

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
backend = auto
banaction = iptables-allports
protocol = all

# SIP Registration Attack Protection
[freeswitch-sip-registration]
enabled = true
filter = freeswitch-sip-registration
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 3
findtime = 300
bantime = 7200

# SIP Scanning Attack Protection (NEW)
[freeswitch-sip-scan]
enabled = true
filter = freeswitch-sip-scan
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 2
findtime = 60
bantime = 3600

# SIP Flood Attack Protection
[freeswitch-sip-flood]
enabled = true
filter = freeswitch-sip-flood
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 10
findtime = 60
bantime = 3600

# SIP Brute Force Attack Protection
[freeswitch-sip-bruteforce]
enabled = true
filter = freeswitch-sip-bruteforce
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 5
findtime = 300
bantime = 7200
'''

        jail_file = os.path.join(self.fail2ban_jail_path, "freeswitch.conf")
        if self.write_file(jail_file, jail_config):
            print("✅ 创建jail配置完成")
            return True
        else:
            print("❌ 创建jail配置失败")
            return False

    def backup_config(self):
        """备份现有配置"""
        print("💾 备份现有配置...")

        os.makedirs(self.config_backup_path, exist_ok=True)

        # 备份fail2ban配置
        if os.path.exists("/etc/fail2ban"):
            backup_cmd = f"cp -r /etc/fail2ban {self.config_backup_path}/fail2ban_backup_$(date +%Y%m%d_%H%M%S)"
            self.run_command(backup_cmd, check=False)

        print("✅ 配置备份完成")

    def run_command_with_timeout(self, command, timeout=30):
        """执行命令并设置超时"""
        try:
            print(f"   执行命令: {command[:50]}...")
            result = subprocess.run(command, shell=True, timeout=timeout,
                                  capture_output=True, text=True)
            return result.returncode == 0, result.stdout.strip(), result.stderr.strip()
        except subprocess.TimeoutExpired:
            print(f"   ⏰ 命令执行超时 ({timeout}秒): {command[:50]}...")
            return False, "", "Command timeout"
        except Exception as e:
            print(f"   ❌ 命令执行异常: {e}")
            return False, "", str(e)

    def restart_fail2ban(self):
        """重启fail2ban服务"""
        print("🔄 重启fail2ban服务...")
        print("   ⏳ 这可能需要几秒钟时间...")

        # 检查配置语法(带超时)
        print("   🔍 检查配置语法...")
        success, stdout, stderr = self.run_command_with_timeout("fail2ban-client -t", timeout=15)
        if success:
            print("✅ 配置语法检查通过")
        else:
            print("⚠️  配置语法检查失败,尝试重启服务...")

        # 先停止服务
        print("   🛑 停止现有服务...")
        stop_commands = [
            "systemctl stop fail2ban",
            "service fail2ban stop",
            "/etc/init.d/fail2ban stop"
        ]

        for cmd in stop_commands:
            success, _, _ = self.run_command_with_timeout(cmd, timeout=10)
            if success:
                print("   ✅ 服务已停止")
                break

        # 等待服务完全停止
        print("   ⏳ 等待服务停止...")
        time.sleep(3)

        # 启动服务
        print("   🚀 启动fail2ban服务...")
        start_commands = [
            "systemctl start fail2ban",
            "service fail2ban start",
            "/etc/init.d/fail2ban start"
        ]

        service_started = False
        for cmd in start_commands:
            print(f"   尝试: {cmd}")
            success, stdout, stderr = self.run_command_with_timeout(cmd, timeout=15)
            if success:
                print("✅ fail2ban服务启动成功")
                service_started = True
                break
            else:
                print(f"   失败: {stderr[:100]}")

        if not service_started:
            print("❌ fail2ban服务启动失败")
            return False

        # 等待服务完全启动
        print("   ⏳ 等待服务完全启动...")
        time.sleep(5)

        # 验证服务状态
        print("   🔍 验证服务状态...")
        for i in range(3):  # 尝试3次
            success, stdout, stderr = self.run_command_with_timeout("fail2ban-client ping", timeout=10)
            if success:
                print("✅ 服务通信正常")
                return True
            else:
                print(f"   第{i+1}次验证失败,等待后重试...")
                time.sleep(2)

        print("⚠️  服务启动成功但通信验证失败")
        print("   💡 这通常不是问题,服务可能需要更多时间启动")
        return True  # 仍然认为重启成功

    def fix_fail2ban_service(self):
        """修复fail2ban服务问题"""
        print("🔧 修复fail2ban服务问题...")

        # 检查socket文件问题
        socket_path = "/var/run/fail2ban/fail2ban.sock"
        if os.path.exists(socket_path):
            print("   删除旧的socket文件...")
            try:
                os.remove(socket_path)
                print("   ✅ 已删除旧socket文件")
            except Exception as e:
                print(f"   ⚠️  删除socket文件失败: {e}")

        # 强制重启服务
        print("   强制重启fail2ban服务...")
        restart_commands = [
            "systemctl restart fail2ban",
            "systemctl reset-failed fail2ban",
            "systemctl daemon-reload",
            "systemctl restart fail2ban"
        ]

        for cmd in restart_commands:
            success, _, _ = self.run_command_with_timeout(cmd, timeout=20)
            if not success and "daemon-reload" not in cmd:
                print(f"   ⚠️  命令执行失败: {cmd}")
            elif success:
                print(f"   ✅ 执行成功: {cmd}")

        # 等待服务启动
        print("   等待服务启动...")
        for i in range(10):  # 最多等待10次,每次2秒
            time.sleep(2)
            success, _, _ = self.run_command_with_timeout("fail2ban-client ping", timeout=5)
            if success:
                print("   ✅ fail2ban服务通信恢复")
                return True
            print(f"   第{i+1}次尝试连接...")

        # 如果还是无法通信,尝试最后的修复方法
        print("   尝试最后的修复方法...")
        fix_commands = [
            "pkill -f fail2ban-server",
            "sleep 2",
            "systemctl start fail2ban",
            "sleep 3"
        ]

        for cmd in fix_commands:
            self.run_command(cmd, check=False)

        # 最终验证
        success, _, _ = self.run_command_with_timeout("fail2ban-client ping", timeout=5)
        if success:
            print("   ✅ 服务修复成功")
            return True
        else:
            print("   ❌ 服务修复失败,但配置已安装")
            print("   💡 请手动检查: systemctl status fail2ban")
            return False

    def show_status(self):
        """显示防护状态"""
        print("📊 查看防护状态...")

        # 先尝试修复服务通信问题
        success, _, _ = self.run_command_with_timeout("fail2ban-client ping", timeout=5)
        if not success:
            print("⚠️  fail2ban服务通信异常,尝试修复...")
            self.fix_fail2ban_service()

        status = self.run_command("fail2ban-client status", check=False)
        if status:
            print("fail2ban 总体状态:")
            print(status)
        else:
            print("⚠️  无法获取fail2ban状态信息")

        # 查看各个jail状态(包含新增的扫描防护)
        jails = ["freeswitch-sip-registration", "freeswitch-sip-scan", "freeswitch-sip-flood", "freeswitch-sip-bruteforce"]
        for jail in jails:
            jail_status = self.run_command(f"fail2ban-client status {jail}", check=False)
            if jail_status:
                print(f"\n{jail} 状态:")
                print(jail_status)
            else:
                print(f"\n{jail}: 无法获取状态信息")

        # 显示系统服务状态
        print(f"\n系统服务状态:")
        service_status = self.run_command("systemctl status fail2ban --no-pager", check=False)
        if service_status:
            print(service_status[:500])  # 限制长度

    def uninstall(self):
        """卸载fail2ban配置"""
        print("🗑️  卸载FreeSwitch fail2ban配置...")

        # 停止fail2ban服务
        self.run_command("systemctl stop fail2ban", check=False)

        # 删除配置文件(包含新增的SIP扫描过滤器)
        files_to_remove = [
            os.path.join(self.fail2ban_jail_path, "freeswitch.conf"),
            os.path.join(self.fail2ban_filter_path, "freeswitch-sip-registration.conf"),
            os.path.join(self.fail2ban_filter_path, "freeswitch-sip-scan.conf"),  # 新增
            os.path.join(self.fail2ban_filter_path, "freeswitch-sip-flood.conf"),
            os.path.join(self.fail2ban_filter_path, "freeswitch-sip-bruteforce.conf")
        ]

        for file_path in files_to_remove:
            if os.path.exists(file_path):
                os.remove(file_path)
                print(f"✅ 删除配置文件: {os.path.basename(file_path)}")

        # 重启fail2ban
        self.run_command("systemctl start fail2ban", check=False)
        print("✅ FreeSwitch fail2ban配置已卸载")

    def install(self):
        """完整安装流程"""
        print("🚀 开始FreeSwitch fail2ban防护安装...")
        print("=" * 50)

        # 检查系统要求
        if not self.check_system_requirements():
            return False

        # 备份配置
        self.backup_config()

        # 获取FreeSwitch配置
        config = self.get_freeswitch_config()
        if not config or not config["log_files"]:
            print("❌ 无法获取FreeSwitch配置信息")
            return False

        # 安装fail2ban
        if not self.install_fail2ban():
            return False

        # 创建过滤器
        if not self.create_freeswitch_filters():
            return False

        # 创建jail配置
        if not self.create_freeswitch_jail(config):
            return False

        # 重启服务
        if not self.restart_fail2ban():
            return False

        # 显示状态
        self.show_status()

        # 手动封禁已知的攻击IP
        self.ban_known_attackers()

        print("\n" + "=" * 50)
        print("🎉 FreeSwitch fail2ban防护安装完成!")
        print("📁 配置备份位置:", self.config_backup_path)
        print("🛡️ 防护功能:")
        print("   ✅ SIP注册攻击防护")
        print("   ✅ SIP扫描攻击防护(新增)")
        print("   ✅ SIP洪水攻击防护")
        print("   ✅ SIP暴力破解防护")
        print("   ✅ 已知攻击IP已被封禁: 91.208.92.186, 108.181.5.53")
        print("\n📋 管理命令:")
        print("   查看状态: fail2ban-client status")
        print("   解封IP: fail2ban-client set [jail名称] unbanip [IP地址]")
        print("   查看日志: tail -f /var/log/fail2ban.log")
        print("   手动封禁: iptables -I INPUT -s [IP] -j DROP")
        print("🔄 卸载防护: python3 freeswitch_fail2ban_setup.py --uninstall")

        return True

def main():
    if len(sys.argv) > 1 and sys.argv[1] == "--uninstall":
        # 卸载模式
        setup = FreeSwitchFail2BanSetup()
        if setup.check_system_requirements():
            setup.uninstall()
        else:
            print("❌ 无法执行卸载操作,请检查系统要求")
    else:
        # 安装模式
        setup = FreeSwitchFail2BanSetup()
        if setup.install():
            print("\n✅ 安装成功!FreeSwitch现在受到fail2ban保护。")
        else:
            print("\n❌ 安装失败!请检查错误信息并重试。")
            sys.exit(1)

if __name__ == "__main__":
    main()