简介:这套基于Python的网络入侵检测与防御系统,面向毕业设计和课程设计场景,解决网络流量实时分析、攻击行为识别、自动防御响应及可视化监控等核心需求。系统可捕获网络数据包并提取特征,检测到扫描、暴力破解等异常行为后自动告警和拦截,Web界面展示实时流量、攻击日志和统计图表;后端使用Flask提供接口,SocketIO实现实时刷新,Scapy完成流量解析,MongoDB存储记录,并支持Docker容器化快速部署。资源包共38个文件,主要包含14个Python源码文件、4个HTML页面、3个JavaScript脚本及若干配置和部署文件,压缩包约92KB。随包附带详细运行指南,覆盖依赖安装、数据库配置、服务启动与常见问题处理,目录按后端、模板、静态资源分层,便于快速定位和二次开发。目前已有172人学习或下载,适合需要可直接运行项目用于毕业答辩或动手实践的学生。
1. 网络入侵检测与防御系统:把毕设从「能跑」做成「能讲明白」
做毕业设计最怕的不是代码写不出来,而是写出来之后老师问「为什么这么设计」时,自己只能答一句「网上抄的」。这套基于 Python 的网络入侵检测与防御系统,恰好把这个问题绕过去了——它不是单个算法或单块代码,而是一条完整的链路:Scapy 负责实时流量分析,Flask 提供 Web 可视化监控,MongoDB 存储攻击日志,配合自动防御模块完成检测到拦截的闭环。适合拿来做网络安全方向的毕设或课程设计,尤其是想要展示「完整系统」而不是「某个函数实现」的同学。下文按照我拆解源码时的思路,先讲清楚架构与数据流,再给可复现的部署步骤和核心代码说明,最后把实际运行中容易翻车的几个坑一并列出来。
2. 架构与数据流:Flask、Scapy、MongoDB 如何各司其职
2.1 技术栈选型:为什么是 Scapy 而不是 dpkt 或 pcapy
拿到源码我第一反应是看它选了哪套抓包库,因为这直接决定了整个流量分析模块的写法。这套项目用的是 Scapy,理由其实很现实:Scapy 不只是一套抓包库,它自带协议栈解析能力,从 Ethernet 帧到 IP、TCP、UDP 再到应用层,haslayer()一步就能判断包里有哪个协议层,省掉了一大堆手工解析字节流的活。如果换成 dpkt,你得自己处理bytes切片、计算首部长度、区分 IPv4 和 IPv6,代码量和出错概率都会明显上升。
Scapy 的价值在毕设场景里还有一个隐性优势:它的sniff()可以直接带BPF过滤表达式,比如sniff(filter="tcp port 80", prn=callback),相当于在网卡层面先丢掉了不关心的报文,对后续实时流量分析的性能压力小很多。它还能wrpcap()保存报文、rdpcap()回放抓包文件,后面做攻击模拟测试时非常好使。
提示:Scapy 解析数据包的能力强,但它是纯 Python 实现,性能上限摆在那里。这套系统定位是教学演示和中小型网络的监控,不是扛 IDC 骨干流量的,理解这个边界很重要。
2.2 目录结构:拿到压缩包之后先看这几个文件
解压后第一件事不是急着跑,而是先把结构捋清楚。app.py是 Flask 应用入口;app/__init__.py是应用工厂,负责初始化 Flask 实例、注册蓝图、连接 MongoDB;app/routes.py是全部 HTTP 路由和 SocketIO 事件;app/modules/目录是核心,流量捕获和攻击检测逻辑都在这;app/templates/是 Jinja2 模板,app/static/放前端 JS 和 CSS。另外有个docker/目录里面是Dockerfile和docker-compose.yml,install.py和install.sh负责环境初始化。
看源码的顺序我建议反着来:先打开docker/docker-compose.yml看它编排了哪些服务,就能知道系统运行依赖什么;再读app/routes.py看暴露了哪些接口;最后才沉到modules/里抠检测逻辑。这样是从整体到局部,不容易被细节绕晕。装环境时requirements.txt里的版本号是经过验证的,不要手痒去升大版本,尤其是 Flask-SocketIO 和它的 JavaScript 客户端版本要配套,不然前端拿不到实时推送。
2.3 数据流拆解:数据包从网卡到浏览器图表走的是什么路
整个系统最有展示价值的就是这条数据流水线,答辩时能把这条线画清楚就已经赢了一半。流量进来第一步,Scapy 在指定的网络接口上sniff(),每个被捕获的报文进入回调函数;第二步,回调里做协议解析和特征提取,拿到源 IP、目的 IP、端口、协议号、TCP 标志位这些关键字段;第三步,检测引擎拿这些字段和规则库做匹配,一旦命中威胁模式就生成一条告警记录,写进 MongoDB;第四步,后端通过 Flask-SocketIO 把告警事件推送到浏览器,前端 Chart.js 的实时曲线和攻击记录表随之刷新。如果是自动防御模块命中的 IP,还会执行拦截动作。
这套链路里最容易忽略的是同步问题。抓包回调天然是并发的,而 MongoDB 写入是异步的,SocketIO 推送有自己的事件循环。如果检测模块里共享变量不加锁,高流量场景下会出现统计数字竞态错乱。源码里用的是单线程回调加全局计数器的方式降低复杂度,这个取舍对毕设来说是合理的——性能换取可读性,答辩时能讲清楚为什么这样设计,反而是加分项。
3. 部署:Docker 一键拉起和裸机手动安装两条路都走通
3.1 方式一:docker-compose 拉起整套环境
如果你机器上装了 Docker,这是最不容易出错的部署方式。系统编排了 MongoDB 和 Flask 应用两个容器,应用容器依赖数据库容器,启动顺序和连接地址都由 Compose 管理。看一下默认的docker/docker-compose.yml:
version: '3' services: mongo: image: mongo:4.4 container_name: nids-mongo ports: - "27017:27017" volumes: - mongodb_data:/data/db app: build: . container_name: nids-app ports: - "5000:5000" depends_on: - mongo environment: - MONGO_URI=mongodb://mongo:27017/nids volumes: - ./..:/app volumes: mongodb_data:这条配置里有三个参数值得说明。depends_on保证 MongoDB 容器先启动,但注意它只管容器层面的启动顺序,不管 MongoDB 服务是否真正就绪,所以应用容器启动后偶尔会先遇到短暂的连接拒绝,Flask 内部只要有重试逻辑就不影响。MONGO_URI里主机名写的是mongo而不是localhost,因为 Compose 网络里每个服务名就是一个 DNS 名,这是 Docker 内部 DNS 的机制,裸机部署时这几处连接串必须改回127.0.0.1。ports映射把容器的 5000 端口挂到宿主机,浏览器打开http://localhost:5000访问的是应用容器。
执行就三条命令:
cd docker/ docker-compose build docker-compose up -dup -d是后台模式,日志要用docker-compose logs -f app看。如果启动后页面打不开,第一步应该看的是docker-compose ps回来是不是Up状态,而不是直接怀疑代码有 bug。容器化部署的好处是 MongoDB 和 Python 依赖都不用你管,代价是网卡隔离——容器默认只能看到虚拟网络接口,想要抓宿主机的真实流量需要额外配network_mode: host,这块放到避坑章节细说。
3.2 方式二:裸机安装手动搭环境
不想用 Docker 的话,项目也提供了install.sh和install.py两条安装路径。Linux 下走install.sh是最省事的,它会把 Python 依赖、系统级抓包库、MongoDB 客户端逐项装好。核心动作拆开看是这样:
#!/bin/bash # install.sh 核心步骤:装系统依赖 -> 装Python包 -> 检查MongoDB apt-get update apt-get install -y python3-pip libpcap-dev mongodb pip3 install -r requirements.txt # 启动 MongoDB 服务(按发行版略有差异) systemctl enable mongod systemctl start mongodlibpcap-dev是 Scapy 在 Linux 上抓包所需的底层库,很多「Scapy 装了但一抓包就报错」的翻车现场都是漏了他。MongoDB 装好之后要确认它在 27017 端口上监听,一个快速校验:
ss -lnt | grep 27017 mongo --eval "db.runCommand({ ping: 1 })"ping: 1能通就说明数据库服务是活的,如果是 200、404 这种 HTTP 状态码容易误导人——MongoDB 的 ping 返回的是一份 BSON 文档,不是数字状态码。
Windows 用户走install.py,原理一样但管道不通的地方是apt-get和libpcap-dev。Windows 上 Scapy 提供的是 Npcap 后端,安装器会引导你下载 Npcap 驱动。这一步要注意选「Install Npcap in WinPcap API-compatible Mode」这个选项,兼容模式才能在旧代码里直接引用。装完 Npcap 重启终端再跑python app.py。
3.3 第一个启动命令:入口文件和参数含义
环境就绪后启动应用,入口是项目根目录下的app.py,代码极其薄,所有初始化逻辑都封装在app/__init__.py里。启动方式:
python app.py默认会监听0.0.0.0:5000,0.0.0.0意味着局域网里的机器也能访问,答辩演示的时候可以让老师用自己的手机连同一个 WiFi 打开你的页面看监控数据,展示效果比围在一个屏幕前好得多。Sniff 的网络接口在modules/的配置文件里定义,常见值是eth0、wlan0或者any,这个参数直接决定你抓不抓得到包,很多「跑起来但一直零流量」的问题都是在这里埋下的。
浏览器打开http://localhost:5000之后,页面上的流量速率曲线、攻击类型分布图、告警日志列表会等待数据接入。此时可以先打开另一个终端自己造一点流量:
ping -c 100 8.8.8.8 # 产生 ICMP 流量,验证探测链路如果曲线开始波动,说明抓包、入库、推送、绘图的整条链路是通的。用了any接口的话,本机回环流量也能被捕获,测试阶段用any是最省心的选择,但注意any只能看,不能发包,后面做攻击模拟时要绑到具体网卡上。
4. 核心代码拆解:把攻击检测从黑匣子变成白盒
4.1 routes.py:后端路由与 SocketIO 事件如何组织
app/routes.py是后端对外的门面,同时挂了 HTTP 路由和 SocketIO 事件。HTTP 部分负责页面加载和初始数据,SocketIO 负责实时推送。看一个典型的页面路由和统计接口:
from flask import Blueprint, render_template, jsonify from app.modules import stats web = Blueprint('web', __name__) @web.route('/') def index(): # 渲染主监控面板 return render_template('index.html') @web.route('/api/attack_stats') def attack_stats(): # 从 MongoDB 读出最近攻击频率统计,按分钟分组 data = stats.attack_summary(window_minutes=30) return jsonify(data)这已经是 Flask 应用工厂加 Blueprint 的结构了:app/__init__.py里app = Flask(__name__)之后app.register_blueprint(web)把路由挂进来。第一次拆项目的人容易在这些文件之间迷路,这里教你一个快速定位法:在routes.py里看到render_template('index.html'),就去templates/里找同名模板;看到jsonify,就顺着函数体里的调用找它 import 的模块。从 routes 出发能走遍全项目。
SocketIO 事件和 HTTP 路由写法完全不同,它是事件名称加处理函数的注册方式:
from flask_socketio import SocketIO, emit socketio = SocketIO(app) @socketio.on('subscribe_traffic') def handle_subscribe(payload): # 前端页面加载完成后调用此事件订阅实时流量流 room = payload.get('room', 'default') emit('traffic_start', {'status': 'ok'}, room=room)socketio.on监听的是前端socket.emit('subscribe_traffic', {...})发起的消息,事件名在前后端必须严格一致,差一个字母前端订阅就收不到推送。room参数是 SocketIO 的多房间分组机制,多个浏览器开着同一个监控面板时,后端可以只向指定房间广播,避免每次把数据推给所有连接者。
4.2 流量分析模块:Scapy 回调函数里做了什么
流量分析的灵魂在modules/下的抓包模块。典型实现是用sniff()注册一个回调,每进来一个报文就解析一次。核心代码骨架大概是这样的:
from scapy.all import sniff, IP, TCP captured_packets = [] def packet_callback(packet): if not packet.haslayer(IP): return src = packet[IP].src dst = packet[IP].dst proto = packet[IP].proto if packet.haslayer(TCP): sport = packet[TCP].sport dport = packet[TCP].dport flags = packet[TCP].flags # 这里把分析结果塞进队列,供检测引擎消费 analyze_packet(src, dst, sport, dport, proto, flags) # 抓包入口:filter 是 BPF 表达式,如果只想抓 TCP 流量就写 "tcp" sniff(filter="ip", prn=packet_callback, store=False, count=0)这段代码有几个参数需要理解清楚。sniff的prn是回调函数,每捕获一个包就会调用一次,返回什么一般不关心,核心逻辑都写在回调里;store=False表示不把原始报文存在内存里,因为实时监控只要统计特征,不需要留全文,否则长时间运行内存会被报文占满;count=0代表无限抓取,直到进程被终止。
analyze_packet是回调往外传递数据的出口,通常连接到一个全局队列或直接更新内存中的计数器。毕设阶段用全局字典计数就够了,每个源 IP 对应一个包计数器,定期把快照落库。要注意 Scapy 的sniff是同步阻塞的,你在 Jupyter 或脚本里调用它,代码会一直停在那里等包,如果被抓包循环卡住了,后面的 Flask 服务根本起不来。源码里采用的做法通常是开一个后台线程跑sniff,主线程继续服务 Web 请求,启动时留意模块里有没有threading.Thread的痕迹就能确认这一点。
4.3 攻击检测逻辑:告警阈值与特征判定怎么写
检测引擎是这套系统里最值得在答辩时展开讲的部分。它不一定用复杂的机器学习模型,而是用更透明的阈值判定加上特征规则。以最常见的 SYN Flood 检测为例,逻辑是统计窗口内每个源 IP 发来的 SYN 包速率,超过阈值就触发告警:
import time from collections import defaultdict syn_counter = defaultdict(list) SYN_THRESHOLD = 100 # 每秒 SYN 包数量阈值 TIME_WINDOW = 5 # 统计窗口(秒) def check_syn_flood(src_ip): now = time.time() syn_counter[src_ip] = [t for t in syn_counter[src_ip] if now - t < TIME_WINDOW] syn_counter[src_ip].append(now) if len(syn_counter[src_ip]) > SYN_THRESHOLD: # 触发告警:写入日志,推送前端,并交给防御模块 trigger_alert(src_ip, "SYN Flood", "太多次TCP SYN请求") syn_counter[src_ip].clear() # 避免重复告警刷屏这段代码体现了三个关键设计点。第一,defaultdict(list)存的是每个源 IP 最近到达的时间戳列表,通过过滤掉窗口外的老时间戳来滑动统计,简单实现了一个滑窗计数器;第二,阈值和窗口是两个可调参数,等会讲调优时会展开;第三,告警触发后清掉当前计数,防止同一攻击源连续刷屏,这是一个非常实用的防重复告警手段。
同样的思路可以扩展到端口扫描检测——统计单个源 IP 在窗口内访问的目的端口数量,超过PORT_SCAN_THRESHOLD就判定扫描行为;也可以对 UDP 泛洪做类似的计数。规则之间互相独立,新增一种攻击类型就是新增一个函数加一条调用,这个架构扩展起来很顺手。如果把规则参数(阈值、窗口)全部抽到 MongoDB 的配置表里,就做成了「热更新规则」,运行时改配置不用重启服务,这是进阶用法里值得做的第一个升级方向。
自动防御模块在检测命中后执行拦截。Linux 环境下常见的实现是靠 iptables 规则封禁源 IP,代码里类似os.system(f"iptables -A INPUT -s {ip} -j DROP")这样的调用——还原机制是清除对应规则即可。Windows 上这个逻辑实现不了,所以要先看项目文档确认你需要在哪个平台跑,源码一般会把防御逻辑做平台分支,非 Linux 平台打印告警但不实际执行拦截。
5. 避坑记录:从装不上到误报,五条血泪经验
5.1 现象:抓包页面一片空白,所有流量图表都是零
- 原因:Scapy 默认绑定的网卡不对。最常见的是把
sniff()默认绑到了eth0上,而实际流量经过的是wlan0或虚拟机的ens33。Docker 部署时尤其坑,容器默认只暴露虚拟网络接口,抓不到宿主机网卡流量。 - 解决:先
ifconfig确认目标网卡名,把接口参数改成对的;Docker 场景改成network_mode: host禁止网络隔离;测试阶段直接用any接口捕获所有网卡流量,快速验证链路通不通。
5.2 现象:页面能打开,但告警记录接口持续返回 500
- 原因:MongoDB 服务没起来,或者连接串里主机名解析不到。日志里会看到
pymongo.errors.ServerSelectionTimeoutError,本质是客户端在 serverSelectionTimeoutMS 的时限内没有找到可用的 MongoDB 节点。查routes.py里MONGO_URI用的是localhost还是mongo,环境不一致就会踩。 - 解决:先
ss -lnt | grep 27017确认数据库监听;再打开 Flask 日志定位是连接拒绝(端口没监听)还是 DNS 解析失败(主机名不对);最后检查环境变量里MONGO_URI是否被覆盖。裸机部署一律改成mongodb://127.0.0.1:27017/nids。
5.3 现象:Docker 容器里跑检测,Permission denied或抓不到包
- 原因:容器默认不是 root 用户,社区版 Docker 默认没有给容器
CAP_NET_RAW权限,Scapy 创建原始套接字的操作被内核拒绝。 - 解决:在
docker-compose.yml的app服务下加privileged: true,或用cap_add: - NET_RAW最小化授权。改了配置之后要docker-compose down && docker-compose up -d让容器重新创建,restart不会触发权限重配。
5.4 现象:流量稍一变大就丢失告警,页面卡顿明显
- 原因:Scapy 是纯 Python 抓包,包解析全在用户态完成,高 PPS(包每秒)场景下 Python 循环成了瓶颈,内核缓冲区溢出导致丢包。这不是代码 bug,是这类技术路线天然的边界。
- 解决:先加 BPF 过滤缩减流量入口——只抓关注的协议和端口,比如
filter="tcp port 80 or icmp";再调大内核抓包缓冲区sysctl -w net.core.rmem_max=26214400;终极解法是把抓包链路降级到 C 层面的tcpdump落地 pcap 文件,Python 侧异步读取,这也是「实时」到「准实时」的工程妥协。
5.5 现象:阈值设 100,正常访问也疯狂报告警,全是误报
- 原因:检测窗口设置太短、阈值太低。
TIME_WINDOW=5, THRESHOLD=100意味着平均每秒 20 个 SYN 就算攻击,而教室或办公室的 NAT 出口完全可能有几十台设备同时访问外网,叠加起来轻松超阈值。另一个陷阱是防御模块在告警后清空了计数器,恶意流量和正常流量会被同样对待。 - 解决:把阈值调高到 300 至 500 再观察,同时增加「确认机制」——只有连续三个时间窗口都超过阈值才真正触发攻击告警,过滤掉瞬时抖动。调参时打开 MongoDB 看告警日志对应的时间点,对照那段时间的业务流量判断是真实攻击还是误报。
6. 进阶用法:自定义检测规则,并用模拟流量验证有效
部署跑通、基础链路没问题之后,下一步就是把它变成「你自己的系统」。第一个值得做的升级是抽化规则参数——把阈值、统计窗口写进 MongoDB 配置表,检测引擎启动时读取,运行时改配置不用重启服务。这样你在答辩时可以直接演示「调低阈值,立刻看到告警刷出来」,比口头讲解有说服力得多。
第二个动作是自定义一种检测规则。以 DNS 隧道检测为例,思路是统计单个 IP 在窗口内请求的 DNS 域名数量与域名长度分布,超过基线就告警。代码层面只需要在modules/里加一个函数,然后注册到检测引擎的detections列表,复用已有的告警推送和防御调用接口,改动量很小。扩展这个动作本身就是答辩亮点:「我不仅跑通了项目,还扩展了它的检测能力」。
验证系统有效性时,自己造流量是最可靠的方式,Scapy 天生就具备发包能力。模拟一次 SYN Flood 攻击看系统反应:
from scapy.all import Ether, IP, TCP, sendp # 构造大量 SYN 包,源 IP 随机变化,目标固定端口 for _ in range(1000): pkt = Ether()/IP(dst="192.168.1.100")/TCP(dport=80, flags="S") sendp(pkt, iface="eth0", verbose=False)用TCP(flags="S")只是发握手包、不建立连接,正常流量中不可能有这种速率。注入之后观察数据库中的告警记录,确认五元组信息被正确解析,再检查自动防御是否对该源 IP 执行了拦截。这类验证的价值在于它把「系统能跑」变成「系统有效」——后者才是技术报告里最有分量的证据。
误报调优有一个原则:宁可延迟触发,不要频繁误报。实践中我会把阈值调到基线的 5 至 8 倍,同时把确认窗口拉长到 30 秒,让系统在攻击持续一段时间后才告警,虽然「实时性」牺牲了几十秒,但换来的是告警可信度。从那以后我拿到任何检测类项目,第一件事不是调 UI、换主题,而是把检测规则的可信链路先走通——对着文档死磕部署步骤,远不如自己抓到一条真实告警来得踏实。希望这套拆解思路能帮你在毕设和课设上少走几步弯路。
本文还有配套的精品资源,点击获取