简介这份资源是面向计算机相关专业学生与项目实战学习者的高分毕业设计完整包主题为基于SDN的DDoS攻击检测与防御系统适合正在准备毕设、课程设计或期末大作业的同学参考与复现。项目经导师指导并通过评审评审分99分代码完整可运行对新手较为友好。压缩包为zip格式整体约136.38MB包含源码、报告及全部配套资料覆盖系统实现、实验验证与文档撰写等环节便于读者对照理解SDN环境下的攻击检测与防御思路。目前已有171人学习下载可作为毕设选题与实战练习的参考样本。读者可从中获取完整项目结构、可运行代码、设计报告与相关资料用于快速搭建实验环境、梳理实现流程并完成自己的设计任务。1. 基于SDN的DDoS攻击检测与防御系统从控制器到数据面的最小闭环DDoS攻击实验里最让人头疼的不是发包而是“看不见”。传统网络里流量进了交换机就像进了黑匣子你只能靠镜像口或者NetFlow采样去猜。SDN把控制面和数据面拆开之后控制器手里握着全网拓扑和流表统计这给DDoS检测提供了一个天然的数据源。这个标题讲的就是用SDN控制器周期性拉取流表字节数、包数算出熵值或速率突变判定DDoS攻击后下发流表把恶意流量丢掉或限速。它适合做网络安全方向毕设的学生也适合想理解SDN北向接口怎么落地的运维。整套东西不依赖昂贵硬件Mininet加Ryu或ONOS就能在笔记本上跑通。下面按“先跑通再调参”的顺序拆开讲。2. 环境搭建与拓扑设计MininetRyu跑通第一个SDN网络2.1 为什么选Ryu而不是ONOS做检测原型做DDoS检测原型控制器选型直接决定你后面写代码的痛苦程度。ONOS功能全、集群能力强但它的北向接口偏重意图和路径计算想拿到每台交换机的逐流统计要绕好几层REST API对毕设周期来说太重。Ryu是Python写的轻量控制器ryu.app.ofctl_rest直接暴露流表查询接口event.EventOFPPacketIn能拿到Packet-In事件写一个检测模块就是继承app_manager.RyuApp的事。常见做法是Ryu负责采集和判定Mininet负责造拓扑和发包两者通过OpenFlow 1.3通信。选Ryu还有一个现实原因它的simple_switch_13.py是现成的二层转发逻辑你只需要在它的基础上加统计轮询和流表下发不用从零处理ARP和MAC学习。ONOS当然也能做但学习曲线会吃掉你至少一周。我一般建议毕设用Ryu把精力留给检测算法本身。2.2 Mininet自定义拓扑与控制器连接命令先装依赖。Ubuntu 20.04或22.04下Mininet用apt装Ryu用pip装。注意Ryu对Python版本敏感Python 3.8以上建议装ryu4.34。# 安装Mininet和Ryu sudo apt update sudo apt install -y mininet python3-pip pip3 install ryu4.34 eventlet0.30.2 # 验证版本 mn --version ryu-manager --version拓扑不要用默认的mn --topotree那个控制器连接是自动的你不好控制。写一个自定义Python拓扑脚本明确指定控制器IP和端口方便后面抓包和调试。# topo_ddos.py from mininet.topo import Topo from mininet.net import Mininet from mininet.node import RemoteController, OVSKernelSwitch from mininet.cli import CLI from mininet.log import setLogLevel class DDoSTopo(Topo): def build(self): # 一台核心交换机三台接入交换机每台接两台主机 s1 self.addSwitch(s1) s2 self.addSwitch(s2) s3 self.addSwitch(s3) s4 self.addSwitch(s4) self.addLink(s1, s2) self.addLink(s1, s3) self.addLink(s1, s4) # 攻击者h1受害者h2正常用户h3-h6 h1 self.addHost(h1, ip10.0.0.1/24) h2 self.addHost(h2, ip10.0.0.2/24) h3 self.addHost(h3, ip10.0.0.3/24) h4 self.addHost(h4, ip10.0.0.4/24) h5 self.addHost(h5, ip10.0.0.5/24) h6 self.addHost(h6, ip10.0.0.6/24) self.addLink(h1, s2) self.addLink(h2, s2) self.addLink(h3, s3) self.addLink(h4, s3) self.addLink(h5, s4) self.addLink(h6, s4) if __name__ __main__: setLogLevel(info) topo DDoSTopo() net Mininet(topotopo, controllerRemoteController, switchOVSKernelSwitch) net.start() CLI(net) net.stop()这段拓扑的关键参数RemoteController默认连127.0.0.1:6653如果你Ryu跑在别的机器上要在net.start()前加net.addController(c0, controllerRemoteController, ip你的IP, port6653)。OVSKernelSwitch用内核态Open vSwitch比用户态快但流表统计精度一样。启动顺序必须是先起Ryu再起Mininet否则交换机连不上控制器ovs-vsctl show里is_connected会是false。# 终端1启动Ryu加载自带simple_switch和统计模块 ryu-manager ryu.app.simple_switch_13 ryu.app.ofctl_rest --observe-links # 终端2启动拓扑 sudo python3 topo_ddos.py跑通后pingall应该全通。如果pingall有丢包先查ovs-vsctl show的控制器连接状态再查Ryu日志里有没有EventOFPSwitchFeatures。这一步是后面所有检测的基础拓扑不通就别往下走。2.3 用ovs-ofctl验证流表与统计信息Mininet起来之后流表是空的因为simple_switch_13只在收到Packet-In时才下发流表。你先h1 ping h2然后到另一个终端执行sudo ovs-ofctl -O OpenFlow13 dump-flows s1 sudo ovs-ofctl -O OpenFlow13 dump-ports s1dump-flows里能看到n_packets和n_bytes这就是检测的数据源。dump-ports看端口级统计适合做端口速率突变检测。注意simple_switch_13下发的流表默认idle_timeout是0也就是永久生效这会导致统计一直累积检测时要用差值而不是绝对值。我一般会在检测模块里自己维护上一次的统计快照每次轮询算增量。提示ovs-ofctl的-O OpenFlow13不能省Mininet默认可能协商成1.0流表格式不一样。3. 检测模块实现从流表统计到熵值判定3.1 基于包速率的阈值检测代码最直接的检测思路是轮询流表算每个目的IP的包速率超过阈值就告警。下面是一个Ryu应用的核心逻辑继承RyuApp用hub.spawn起一个后台协程每2秒轮询一次。# ddos_detect.py from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib import hub from ryu.app.ofctl.api import get_datapath import time class DDoSDetect(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(DDoSDetect, self).__init__(*args, **kwargs) self.datapaths {} self.flow_stats {} # 上一次统计快照 self.threshold_pps 500 # 包速率阈值单位包/秒 self.monitor_thread hub.spawn(self._monitor) set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath ev.msg.datapath self.datapaths[datapath.id] datapath # 下发table-miss流表把未知包送给控制器 ofproto datapath.ofproto parser datapath.ofproto_parser match parser.OFPMatch() actions [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] inst [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] mod parser.OFPFlowMod(datapathdatapath, priority0, matchmatch, instructionsinst) datapath.send_msg(mod) def _monitor(self): while True: for dp in self.datapaths.values(): self._request_stats(dp) hub.sleep(2) # 轮询间隔2秒 def _request_stats(self, datapath): parser datapath.ofproto_parser req parser.OFPFlowStatsRequest(datapath) datapath.send_msg(req) set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) def flow_stats_reply_handler(self, ev): datapath ev.msg.datapath now time.time() for stat in ev.msg.body: if stat.priority 0: continue # 跳过table-miss key (datapath.id, stat.match.get(ipv4_dst), stat.match.get(ipv4_src)) pkt_count stat.packet_count if key in self.flow_stats: prev_pkt, prev_time self.flow_stats[key] delta_pkt pkt_count - prev_pkt delta_t now - prev_time if delta_t 0: pps delta_pkt / delta_t if pps self.threshold_pps: self.logger.warning( DDoS suspected: dst%s src%s pps%.1f, key[1], key[2], pps) self._install_drop_flow(datapath, stat.match) self.flow_stats[key] (pkt_count, now) def _install_drop_flow(self, datapath, match): # 下发高优先级丢弃流表阻断攻击流量 ofproto datapath.ofproto parser datapath.ofproto_parser mod parser.OFPFlowMod( datapathdatapath, priority100, matchmatch, instructions[], # 空指令即丢弃 hard_timeout30) # 30秒后自动删除避免误杀 datapath.send_msg(mod)逻辑说明_monitor每2秒对每个交换机发一次OFPFlowStatsRequestflow_stats_reply_handler里用当前包数减去上次包数除以时间差得到pps。超过threshold_pps就调_install_drop_flow下发一条priority100、空instructions的流表OpenFlow里空指令就是丢弃。hard_timeout30是后悔药防止正常突发流量被永久阻断。参数说明threshold_pps是核心参数500是实验室量级真实环境要按业务基线调。轮询间隔2秒是精度和控制器负载的折中调到0.5秒能更快发现攻击但Ryu的CPU会上去。hard_timeout建议设30到60秒太短攻击会反复触发太长误杀影响大。3.2 用信息熵降低误报源IP熵的计算与阈值纯速率阈值在正常突发流量下会翻车比如你h3用iperf打一个Gbps流pps可能就超500。更稳的做法是算源IP的信息熵。DDoS攻击通常用大量伪造源IP熵值会异常升高而正常流量源IP集中熵值低。公式是H -Σ(pi * log2(pi))pi是某个源IP的包数占比。# 在flow_stats_reply_handler里增加熵计算 import math from collections import defaultdict def _calc_entropy(self, datapath): src_pkt defaultdict(int) total 0 for (dpid, dst, src), (pkt, _) in self.flow_stats.items(): if dpid datapath.id and dst 10.0.0.2: # 只统计发往受害者的流量 src_pkt[src] pkt total pkt if total 0: return 0.0 entropy 0.0 for cnt in src_pkt.values(): p cnt / total entropy - p * math.log2(p) return entropy熵值本身没有绝对阈值要看你拓扑里正常流量的熵。我一般先跑5分钟正常流量记录熵的均值和方差把阈值设成均值加3倍标准差。攻击时熵会明显偏离。注意熵计算要限定目的IP否则全网流量混在一起攻击特征被稀释。3.3 防御动作下发流表丢弃与限速的两种策略丢弃流表适合确认的攻击源直接instructions[]。但如果你不确定是不是误报用meter限速更温和。OpenFlow 1.3支持meter表先建meter再在流表里引用。# 创建meter限速1Mbps def _install_meter(self, datapath, meter_id1, rate1000): ofproto datapath.ofproto parser datapath.ofproto_parser bands [parser.OFPMeterBandDrop(raterate, burst_size100)] mod parser.OFPMeterMod(datapathdatapath, commandofproto.OFPMC_ADD, flagsofproto.OFPMF_KBPS, meter_idmeter_id, bandsbands) datapath.send_msg(mod)然后在流表里加instructions[parser.OFPInstructionMeter(meter_id, ofproto.OFPIT_METER)]。限速的好处是攻击流量被压到1Mbps正常用户还能用。缺点是meter表在OVS里实现有差异有些版本OFPMF_KBPS不生效要测。我一般先丢后限确认攻击源再丢。4. 避坑与排查DDoS检测实验里最容易翻车的5个点4.1 流表统计不更新或全为0现象dump-flows里n_packets一直是0或者Ryu收到的FlowStatsReply里packet_count不变。原因通常是流表idle_timeout设了非0值流表到期被删统计归零或者你查的是table-miss流表那条流表不统计转发包。解决检测用的流表idle_timeout设0并且跳过priority0的流表。另外确认ovs-vsctl show里is_connected是true控制器没连上时交换机不会上报统计。4.2 阈值设太低导致iperf正常流量被误杀现象跑iperf打流检测模块立刻告警并下发丢弃流表正常业务中断。原因threshold_pps设了100而iperf小包打流轻松超1000pps。解决先用iperf -u -b 10M跑基线看正常pps峰值阈值设峰值的2到3倍。或者改用熵值判定熵对突发流量不敏感。血泪经验是别在演示前才调阈值提前跑半小时基线。4.3 Ryu控制器CPU跑满导致轮询延迟现象轮询间隔设0.5秒后Ryu进程CPU 100%FlowStatsReply延迟好几秒检测滞后。原因每次OFPFlowStatsRequest会遍历所有流表流表多了之后开销大。解决轮询间隔别低于1秒在OFPFlowStatsRequest里加match过滤只查目的IP是受害者的流表或者用OFPFlowStatsRequest的table_id限定表。毕设规模下2秒间隔足够。4.4 攻击流量走table-miss不触发检测现象h1用hping3发包但检测模块没反应。原因h1到h2的第一包触发Packet-Insimple_switch_13下发了转发流表后续包走那条流表统计在涨。但如果你的检测模块没继承simple_switch_13或者table-miss流表没下发包全送控制器交换机流表里没统计。解决确保检测模块和转发模块在同一个Ryu进程里或者检测模块自己下发table-miss和转发流表。常见做法是ryu-manager ddos_detect.py ryu.app.simple_switch_13一起加载。4.5 hard_timeout到期后攻击流量恢复现象丢弃流表30秒后自动删除攻击流量又上来了检测模块再次告警循环往复。原因hard_timeout是单次阻断攻击持续时流表删了就恢复。解决在检测模块里维护一个黑名单对同一源IP重复告警时下发idle_timeout0的永久流表或者把hard_timeout设长到300秒。但永久流表有误杀风险建议加一个手动清除接口比如Ryu的REST API。5. 进阶技巧用REST API做动态阈值与可视化验证检测模块跑通之后下一步是让阈值可调、结果可见。Ryu的ofctl_rest提供了REST接口你可以用curl动态改阈值不用重启控制器。下面这个技巧是我做毕设演示时最常用的把阈值存在一个JSON文件里检测模块每次轮询前读一次改文件即生效。# 在DDoSDetect类里增加 import json def _load_threshold(self): try: with open(/tmp/ddos_threshold.json, r) as f: cfg json.load(f) self.threshold_pps cfg.get(pps, 500) except Exception: pass # 文件不存在时用默认值 # 在_monitor循环里调用 def _monitor(self): while True: self._load_threshold() for dp in self.datapaths.values(): self._request_stats(dp) hub.sleep(2)改阈值只需echo {pps: 800} /tmp/ddos_threshold.json。验证时用hping3发包同时开一个终端跑watch -n 1 ovs-ofctl -O OpenFlow13 dump-flows s1 | grep drop看丢弃流表有没有下发。更直观的做法是把告警写到一个日志文件用tail -f看实时输出。验证项命令预期结果拓扑连通pingall0% dropped流表统计ovs-ofctl -O OpenFlow13 dump-flows s1n_packets递增攻击触发hping3 -S --flood -V 10.0.0.2Ryu日志出现DDoS suspected防御生效ovs-ofctl -O OpenFlow13 dump-flows s1 | grep priority100有drop流表阈值动态改echo {pps:100} /tmp/ddos_threshold.json告警频率变化最后说一个我踩过的坑hping3发包时如果没加-S默认发的是空包OpenFlow匹配不到ipv4_dst检测模块拿到的match是空的。一定要用hping3 -S --flood -V 10.0.0.2-V开详细输出确认包发出去了。另外Mininet里h1和h2在同一交换机下流量不经过s1检测模块如果只监控s1会漏掉。我一般把检测模块挂到所有交换机上或者把攻击者和受害者分到不同交换机下。这个方案值不值得做如果你要做网络安全毕设SDNDDoS是能同时展示控制面编程和数据面防御的少数方向之一MininetRyu的复现成本低调参空间大答辩时能演示实时阻断比纯理论分析有说服力。希望帮到你。本文还有配套的精品资源点击获取
