
简介这套基于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(filtertcp port 80, prncallback)相当于在网卡层面先丢掉了不关心的报文对后续实时流量分析的性能压力小很多。它还能wrpcap()保存报文、rdpcap()回放抓包文件后面做攻击模拟测试时非常好使。提示Scapy 解析数据包的能力强但它是纯 Python 实现性能上限摆在那里。这套系统定位是教学演示和中小型网络的监控不是扛 IDC 骨干流量的理解这个边界很重要。2.2 目录结构拿到压缩包之后先看这几个文件解压后第一件事不是急着跑而是先把结构捋清楚。app.py是 Flask 应用入口app/__init__.py是应用工厂负责初始化 Flask 实例、注册蓝图、连接 MongoDBapp/routes.py是全部 HTTP 路由和 SocketIO 事件app/modules/目录是核心流量捕获和攻击检测逻辑都在这app/templates/是 Jinja2 模板app/static/放前端 JS 和 CSS。另外有个docker/目录里面是Dockerfile和docker-compose.ymlinstall.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.ymlversion: 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_URImongodb://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:50000.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_minutes30) 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}, roomroom)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(filterip, prnpacket_callback, storeFalse, count0)这段代码有几个参数需要理解清楚。sniff的prn是回调函数每捕获一个包就会调用一次返回什么一般不关心核心逻辑都写在回调里storeFalse表示不把原始报文存在内存里因为实时监控只要统计特征不需要留全文否则长时间运行内存会被报文占满count0代表无限抓取直到进程被终止。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(fiptables -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 过滤缩减流量入口——只抓关注的协议和端口比如filtertcp port 80 or icmp再调大内核抓包缓冲区sysctl -w net.core.rmem_max26214400终极解法是把抓包链路降级到 C 层面的tcpdump落地 pcap 文件Python 侧异步读取这也是「实时」到「准实时」的工程妥协。5.5 现象阈值设 100正常访问也疯狂报告警全是误报原因检测窗口设置太短、阈值太低。TIME_WINDOW5, THRESHOLD100意味着平均每秒 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(dst192.168.1.100)/TCP(dport80, flagsS) sendp(pkt, ifaceeth0, verboseFalse)用TCP(flagsS)只是发握手包、不建立连接正常流量中不可能有这种速率。注入之后观察数据库中的告警记录确认五元组信息被正确解析再检查自动防御是否对该源 IP 执行了拦截。这类验证的价值在于它把「系统能跑」变成「系统有效」——后者才是技术报告里最有分量的证据。误报调优有一个原则宁可延迟触发不要频繁误报。实践中我会把阈值调到基线的 5 至 8 倍同时把确认窗口拉长到 30 秒让系统在攻击持续一段时间后才告警虽然「实时性」牺牲了几十秒但换来的是告警可信度。从那以后我拿到任何检测类项目第一件事不是调 UI、换主题而是把检测规则的可信链路先走通——对着文档死磕部署步骤远不如自己抓到一条真实告警来得踏实。希望这套拆解思路能帮你在毕设和课设上少走几步弯路。本文还有配套的精品资源点击获取