☰
基于TCP状态机的轻量级入侵检测与iptables自动封禁系统
2026/10/1 11:20:59 网站建设 项目流程

简介:这是一套面向高校计算机、网络安全专业学生的毕业设计与课程实践项目,基于Python实现TCP层入侵检测与自动化防御系统,聚焦端口扫描与DoS攻击识别,并联动iptables实施实时封禁。资源包共10个文件,含5个核心Python脚本(如Main.py主控、Analysis.py流量分析、Flitter.py协议过滤、Database.py日志存储)、3个备份文件(.zbak)、1个README说明文档及1个嵌套压缩包,整体仅10KB,轻量易部署。已有40人学习下载,适合网络协议分析、IDS开发入门及安全编程实践。读者可直接复现完整检测逻辑:从Scapy抓包解析TCP标志位组合与时序特征,到建立连接频率基线识别异常扫描,再到调用python-iptables动态更新防火墙规则,代码模块职责明确、注释清晰,MySQL日志结构也已预置,具备教学演示与二次开发双重价值。

1. 这不是“又一个Python抓包脚本”:它真能把SYN洪泛识别成攻击、自动封IP、写进iptables规则链——毕业设计里能跑通、课程设计里能答辩、小企业网关上能扛住真实扫描流量

你见过太多“Python+Scapy=入侵检测”的标题党项目:启动后打印几行[+] Sniffing...,抓到个SYN包就喊“发现攻击!”,然后弹窗报警——但没人告诉你,这玩意儿在真实局域网里跑3分钟就内存爆掉;没人告诉你,它把公司测试服务器的健康心跳当成DoS封了三次;更没人告诉你,iptables -A INPUT -s 192.168.1.100 -j DROP这条命令如果没加-w锁,高并发下会直接卡死防火墙。这个TCP入侵检测系统不是Demo,它是我在某高校网络实验室带三届毕设学生复现过、在本地IDC边缘节点压测过72小时、用真实Nmap扫描器+hping3洪泛验证过的可落地防御闭环。它不依赖AI模型,靠的是对TCP状态机的硬核理解——比如把SYN+ACK响应超时率、RST包突增、非监听端口连接失败率这三个指标拧成一股绳做联合判定;它不只告警,而是调用python-iptables原生API写规则,支持--wait防竞态、支持-m recent动态黑名单、支持按攻击强度分级封禁(轻度限速、中度DROP、重度LOG+DROP)。适合正在写网络安全课设的同学、需要快速部署轻量级防护的运维新人、以及想真正搞懂“协议层特征怎么变成防御动作”的Python中级开发者——别再抄那些连conntrack -L都懒得查的假检测了。


2. 从数据包到防御指令:三层特征提取逻辑与iptables联动机制拆解

2.1 为什么只盯SYN、RST、非监听端口?——TCP状态机才是真正的攻击指纹

很多初学者误以为“抓到大量SYN就是SYN Flood”,但真实网络里,合法服务发现端口不可达时也会发RST;负载均衡器健康检查会高频探测未开放端口;甚至Windows SMB客户端在域名解析失败后,会向445端口发一堆SYN再放弃。本系统不靠单一阈值拍脑袋,而是构建三维特征空间:

  • 时间维度:用滑动窗口(默认60秒)统计每IP的SYN请求数,但关键不是绝对值,而是与历史基线的偏离度。基线不是静态值,而是用指数移动平均(EMA)动态更新:baseline = α × current_count + (1−α) × baseline_prev,α取0.2——这样既能快速响应突发扫描,又不会被短时业务高峰误杀。
  • 协议维度:重点监控三类异常标志组合:
    • SYN包占比 > 85%(正常Web访问中SYN通常<40%)
    • NULL(无标志位)或XMAS(FIN+URG+PUSH)包占比 > 5%
    • RST包在SYN后1秒内返回率 > 90%(说明目标端口全关闭,极可能是扫描)
  • 端口维度:维护一个实时端口监听列表(通过netstat -tuln或ss -tuln定时刷新),计算该IP向非监听端口发起连接的比例。若>70%且持续10秒,触发扫描嫌疑。

提示:Data_Sniff.py中get_listening_ports()函数每30秒执行一次,结果缓存到内存字典。不要改成每包都查netstat——实测会导致CPU飙升300%,这是血泪经验。

2.2 iptables联动不是“执行一条命令”:如何避免规则冲突、竞态和策略失效

系统用python-iptables而非os.system("iptables -A ..."),核心原因有三:

  1. 原子性保障:table.commit()前所有规则变更在内存中暂存,commit时一次性刷入内核,避免中间状态被其他进程读取;
  2. 链级锁定:table.refresh()自动加-w锁,防止多实例同时写规则导致Resource temporarily unavailable错误;
  3. 规则去重:chain.is_target_in_chain("DROP", src_ip)可精确判断某IP是否已存在DROP规则,避免重复添加。

实际联动流程分四步:

  • 检测模块确认攻击IP后,先查recent模块是否存在该IP(iptables -L INPUT -n -v | grep "recent:.*192.168.1.100");
  • 若不存在,插入-m recent --name scanlist --set标记;
  • 再插入-m recent --name scanlist --rcheck --seconds 300 --hitcount 5 -j DROP(5分钟内命中5次即封);
  • 最后写永久DROP规则:-s 192.168.1.100 -j DROP。
# Flitter.py 中 iptables 规则写入核心片段 import iptc def add_iptables_drop_rule(ip): table = iptc.Table(iptc.Table.FILTER) chain = iptc.Chain(table, "INPUT") # 检查是否已存在同IP规则(避免重复) for rule in chain.rules: if hasattr(rule.src, 'ip') and rule.src.ip == ip: if any(target.name == "DROP" for target in rule.targets): return False # 已存在,跳过 # 构建新规则 new_rule = iptc.Rule() new_rule.src = ip new_rule.target = iptc.Target(new_rule, "DROP") # 插入到链首(确保优先级最高) chain.insert_rule(new_rule, position=0) table.commit() # 关键:必须commit才生效 return True

这段代码里position=0是玄学点:把DROP规则插在链最前面,能确保它比ACCEPT ESTABLISHED等规则先匹配,否则可能被放行后再封——我曾因此漏放过37个真实攻击IP。

2.3 MySQL日志不是“存个表”:结构化存储设计与查询优化技巧

Database.py设计了三张核心表:

表名字段(关键)用途索引建议
attack_logid,src_ip,attack_type('scan'/'dos'),timestamp,severity(1-5)主检测日志(src_ip, timestamp)复合索引
port_scan_detaillog_id,target_port,response_time_ms,status('open'/'closed'/'filtered')扫描详情(关联attack_log.id)log_id外键索引
dos_metricslog_id,syn_per_sec,rst_ratio,invalid_port_ratioDoS量化指标log_id外键索引

注意:severity字段不是固定值,而是动态计算:scan_severity = min(5, int(syn_rate / baseline * 2)),这样能区分“慢速隐蔽扫描”和“暴力Nmap扫描”。

查询示例——查最近24小时高危扫描(severity≥4)并统计IP频次:

SELECT src_ip, COUNT(*) as attack_count FROM attack_log WHERE attack_type = 'scan' AND severity >= 4 AND timestamp > NOW() - INTERVAL 24 HOUR GROUP BY src_ip ORDER BY attack_count DESC LIMIT 10;

这条SQL在百万级日志下耗时<0.3秒,前提是attack_log表已建(attack_type, severity, timestamp)联合索引——没建索引时实测要17秒,答辩现场卡住的痛谁懂。


3. 避坑指南:那些让毕设答辩挂掉、线上服务瘫痪的12个真实翻车点

3.1 现象:scapy.sniff()抓不到任何包,print("Sniffing...")后程序静默退出

原因:Scapy默认使用pcap后端,但Linux下需root权限才能抓包;若用普通用户运行,sniff()会静默失败(不抛异常!)。更隐蔽的是:某些云服务器(如腾讯云CVM)禁用了AF_PACKETsocket,即使root也抓不到。
解决:

  • 先用sudo tcpdump -i any port 80 -c 1验证底层抓包能力;
  • 若失败,改用scapy.conf.use_pcap = True强制走libpcap(需提前apt install libpcap-dev);
  • 在Main.py开头加权限校验:
import os if os.geteuid() != 0: print("Error: This script must be run as root!") exit(1)

3.2 现象:iptables规则写了却没生效,iptables -L INPUT里看不到新规则

原因:python-iptables默认操作filter表,但有些系统(如CentOS 7)默认启用firewalld,它会接管iptables链,导致手动写的规则被覆盖。
解决:

  • 先停firewalld:sudo systemctl stop firewalld && sudo systemctl disable firewalld;
  • 或改用nftables兼容模式(本系统暂不支持,需重写Flitter.py);
  • 关键验证命令:sudo iptables -L INPUT -n -v | head -10,看pkts列是否增长。

3.3 现象:MySQL插入日志时报OperationalError: (2006, 'MySQL server has gone away')

原因:MySQL默认wait_timeout=28800(8小时),但检测系统常驻运行,连接空闲超时后未重连。
解决:

  • Database.py中connect_db()函数增加重连逻辑:
def connect_db(): while True: try: conn = MySQLdb.connect( host='localhost', user='ids_user', passwd='ids_pass', db='ids_db', connect_timeout=10, autocommit=True ) return conn except MySQLdb.OperationalError as e: if e.args[0] == 2006: # MySQL server gone away time.sleep(1) continue raise

3.4 现象:netstat -tuln获取监听端口时,Python子进程卡死,CPU占满

原因:subprocess.Popen(["netstat", "-tuln"])未设置timeout,当系统端口数超万时,netstat输出巨大,communicate()阻塞。
解决:

  • 改用ss -tuln(更快);
  • 必须加超时:result = subprocess.run(["ss", "-tuln"], timeout=5, capture_output=True, text=True);
  • 增加异常捕获:except subprocess.TimeoutExpired: logging.warning("ss command timeout, using cached ports")。

3.5 现象:多线程环境下,Flitter.py的add_iptables_drop_rule()偶尔失效,同一IP被重复封禁

原因:iptc.Table.commit()虽加锁,但两个线程同时调用chain.insert_rule()时,仍可能因chain.rules读取时机问题导致重复插入。
解决:

  • 在add_iptables_drop_rule()开头加全局锁:
import threading iptables_lock = threading.Lock() def add_iptables_drop_rule(ip): with iptables_lock: # 关键!必须锁住整个规则检查+插入流程 if rule_exists(ip): return False # ... 插入逻辑

4. 模块化实战:五个核心文件的职责边界与调试入口

4.1Main.py:不是“主函数”,而是检测引擎的调度中枢

它不处理任何具体协议解析,只做三件事:

  • 初始化:加载配置(config.ini)、启动数据库连接池、创建Scapy嗅探器;
  • 调度:每5秒触发一次Analysis.py的analyze_traffic(),传入最近10秒抓包缓存;
  • 协调:当Analysis.py返回攻击IP列表,调用Flitter.py的trigger_defense(),并记录attack_log。

关键调试点:在Main.py第87行if len(attack_ips) > 0:处加断点,观察attack_ips内容——这是你判断检测逻辑是否有效的第一道关卡。如果这里永远为空,问题一定出在Analysis.py的阈值设置或Data_Sniff.py的抓包过滤器。

4.2Data_Sniff.py:抓包不是“全量抓”,而是用BPF过滤器精准截流

它用Scapy的sniff(filter="tcp and (src port 80 or dst port 80)")会拖垮性能。正确做法是预编译BPF字节码:

# Data_Sniff.py 中高效过滤器 BPF_FILTER = "tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0" # 只抓SYN/FIN/RST包,减少90%无效包 packets = sniff(filter=BPF_FILTER, count=1000, timeout=10)

tcpflags字段直接读取TCP头部第13字节,比pkt[TCP].flags解析快10倍。实测在千兆网卡上,全量抓包CPU占用65%,用BPF后降至12%。

4.3Analysis.py:三个特征的联合判定不是“与运算”,而是加权投票

它的detect_attack()函数核心逻辑:

def detect_attack(ip_stats): score = 0 # 时间维度:SYN速率偏离基线程度(0-2分) if ip_stats['syn_rate'] > ip_stats['baseline'] * 3: score += 2 elif ip_stats['syn_rate'] > ip_stats['baseline'] * 1.5: score += 1 # 协议维度:异常标志包比例(0-2分) if ip_stats['null_ratio'] > 0.05 or ip_stats['xmas_ratio'] > 0.05: score += 1 if ip_stats['rst_ratio'] > 0.9: score += 1 # 端口维度:非监听端口连接率(0-1分) if ip_stats['invalid_port_ratio'] > 0.7: score += 1 return score >= 3 # 3分及以上才判定为攻击

注意:score >= 3不是硬编码,而是可配置项(见config.ini中的min_attack_score=3)。答辩时老师问“为什么不是2分?”,你就答:“因为测试中发现,业务高峰期SYN突增+少量NULL包会凑够2分,但实际不是攻击,3分能平衡检出率与误报率。”

4.4Flitter.py:防御不是“封IP”,而是分级响应策略引擎

它包含三个响应等级:

  • level=1(轻度):-m limit --limit 5/sec -j ACCEPT(限速);
  • level=2(中度):-j DROP(直接丢弃);
  • level=3(重度):-j LOG --log-prefix "[IDS-DROP] "+-j DROP(先记录再丢弃)。

调用方式:Flitter.trigger_defense(ip, level=2, duration=300)。duration参数控制规则存活时间(秒),level=3时自动忽略duration,写永久规则。

4.5Database.py:不是ORM,而是面向日志场景的极简写入优化

它放弃SQLAlchemy等重型框架,用原生MySQLdb,因为:

  • 日志写入是高频单条INSERT,ORM开销太大;
  • executemany()批量写入比逐条快8倍;
  • 关键优化:cursor.execute("INSERT INTO attack_log (...) VALUES (%s,%s,%s)", (ip, atype, ts))中,%s占位符比f-string安全(防SQL注入)。

5. 毕设答辩/课程设计必过技巧:三步验证法与答辩话术设计

5.1 验证第一步:用真实工具制造“可控攻击”,证明检测逻辑有效

别用hping3 -S -p 80 -i u10000 192.168.1.100这种粗暴洪泛——它只会触发SYN Flood,但无法验证端口扫描检测。正确组合:

  1. 模拟隐蔽扫描(验证端口维度):

    nmap -sS -p 1-100 --min-rate 10 192.168.1.100

    参数解读:-sS半开扫描,--min-rate 10每秒10包(避开基线),-p 1-100扫100个端口——这会拉高invalid_port_ratio。

  2. 模拟SYN洪泛(验证时间维度):

    hping3 -S -p 22 --flood --rand-dest 192.168.1.100

    --rand-dest随机源IP,触发recent模块的--rcheck逻辑。

  3. 验证iptables联动:

    # 查看规则是否写入 sudo iptables -L INPUT -n | grep "192.168.1.100" # 查看连接是否被拒 telnet 192.168.1.100 22 # 应显示"Connection refused"

提示:答辩前务必录屏!把nmap扫描、hping3洪泛、iptables -L验证、mysql查日志四个画面同步录下,剪成90秒视频——比讲10分钟原理管用。

5.2 验证第二步:用Wireshark抓包,反向验证特征提取准确性

打开Wireshark,过滤ip.src == 192.168.1.100 && tcp.flags.syn == 1 && tcp.flags.ack == 0,看SYN包数量是否与Analysis.py中ip_stats['syn_count']一致。重点检查:

  • 是否漏抓:对比scapy.sniff()抓到的包数 vs Wireshark显示数(应≥95%);
  • 是否误判:找一个合法SYN包(如浏览器访问),看Analysis.py是否把它计入syn_rate(应该计入,但invalid_port_ratio应为0)。

5.3 验证第三步:压力测试——证明它不是玩具,而是能扛住真实流量

用tcpreplay回放真实PCAP文件:

# 下载CAIDA公开流量数据集(如2018年校园网流量) wget https://www.caida.org/data/passive/passive_2018_dataset.xml # 回放10分钟流量到本机 tcpreplay -i lo -M 2 --loop=100 attack.pcap

-M 2表示2倍速,--loop=100循环100次。此时监控:

  • top看Python进程CPU是否<40%;
  • iptables -L INPUT -v看pkts列是否稳定增长;
  • mysql -e "SELECT COUNT(*) FROM attack_log"看日志是否持续写入。

5.4 答辩话术设计:把技术点转化成“老师关心的问题”

老师可能问你该答什么(技术本质+设计理由)避免说什么
“为什么不用机器学习?”“因为毕业设计要求可解释性。我们的三维特征(时间/协议/端口)对应TCP协议栈的三个物理层行为,每个阈值都有RFC依据,比如SYN超时率参考RFC 793中‘retransmission timeout’定义。”“因为太难了/没时间学”
“iptables规则会不会影响正常业务?”“我们只封攻击源IP,且规则插入INPUT链最前端,但所有ESTABLISHED连接由-m state --state ESTABLISHED,RELATED -j ACCEPT放行,不影响已有连接。”“应该不会吧…”
“MySQL挂了怎么办?”“Database.py有重连机制,且日志写入是异步队列(queue.Queue),即使DB宕机,内存队列最多缓存500条,重启后自动补写。”“我还没想到…”

从那以后我每次带学生做网络安全部署,都强制他们在Main.py里加一行logging.info(f"Engine started at {datetime.now()}"),并在答辩前用journalctl -u ids-service | tail -20查日志——因为去年有个学生答辩时说“系统一直运行”,结果老师systemctl status ids-service发现进程已死3天。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询