拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Ryu与Mininet的SDN流量监控与流表控制实现

简介基于SDN架构的网络流量监控与控制Python源码是一份面向毕业设计、期末大作业及课程设计场景的高质量项目。项目围绕软件定义网络中的流量采集、监控策略与动态控制展开代码结构清晰并配有详细注释适合有一定Python基础但缺乏项目经验的学生直接参考学习。资源包共含2000个文件以1838个Python源码文件为主体辅以C/C扩展模块、JSON/JS/CSS等前端与配置资源、TXT说明文档及PDF参考资料压缩包大小约112.58MB部署环境后可快速运行验证目前已有384人学习浏览。作为个人手打、导师认可的98分高分项目该资源完整呈现了从网络数据获取、流量分析到控制策略下发的实现思路同时附带的C扩展文件与依赖配置可帮助读者理解SDN控制器的底层交互机制。下载后只需简单部署即可使用既能满足高分验收要求也可作为二次开发与功能扩展的起点。1. 为什么流量监控要交给SDN控制器而不是交换机毕业设计答辩现场最容易被追问的一个问题是你的监控数据从哪里来的。传统方案里运维脚本用SNMP去轮询交换机的ifInOctets计数器轮询间隔短了交换机CPU扛不住间隔长了流量曲线根本看不出毛刺。而SDN架构下这个问题天然被简化——OpenFlow协议本身就把交换机的转发面和控制面拆开了控制器向交换机发一条ofp_port_stats_request交换机就把端口收发的字节数和包数直接报告上来。你拿的是同一份计数器但获取成本和上报精度完全变了。这份Python源码走的正是这条路用Mininet在单机上搭建虚拟网络Ryu控制器监听OpenFlow交换机的事件把端口流量统计聚合后写入SQLite再基于统计结果下发流表实现丢包、限速和重路由。项目的含金量不在于用了多高级的算法而在于把SDN南向接口的标准能力串成了一条完整的闭环链路。适合三类人看做毕设需要一套能演示的原创系统的学生想快速上手Ryu如何解析Packet-in和下发流表的后端工程师以及被传统网络监控的轮询效率折磨过的运维。2. SDN控制面与数据面的交互模型从Packet-in到流表下发2.1 OpenFlow消息类型和Ryu的事件循环在开始读代码之前得先理解Ryu这个框架替我们消化掉了什么。Ryu基于asyncio构建事件循环交换机通过TCP 6633端口连接到控制器后控制器会为每个连接维护一个Datapath对象。所有OpenFlow协议消息都会被封装成Ryu事件比如packet_in事件、port_desc_stats_reply事件、flow_stats_reply事件。项目的核心逻辑分布在几个Python文件里其中一部分是网络事件处理另一部分是策略决策。如果你解压源码包看到一堆greenlet.c、corecext.c、event.c这样的C文件不用慌那是Ryu依赖的gevent运行库的编译产物gevent通过greenlet实现协程间的切换让Ryu可以在单个线程内并发处理所有交换机的连接。from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import MAIN_DISPATCHER, set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib.packet import packet, ethernet, ipv4 class FlowMonitor(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(FlowMonitor, self).__init__(*args, **kwargs) self.datapaths {} set_ev_cls(ofp_event.EventOFPStateChange, MAIN_DISPATCHER) def _state_change_handler(self, ev): datapath ev.datapath if ev.state MAIN_DISPATCHER: self.datapaths[datapath.id] datapath elif ev.state None: self.datapaths.pop(datapath.id, None)这段代码注册了一个状态监听器交换机握手完成后会进入MAIN_DISPATCHER状态控制器只有在这个状态下才能下发流表。datapaths字典用来维护在线交换机的id和连接对象。很多新手在写SDN监控时忽略了这个状态判断导致流表下发时机不对报出Bad request: OFPBRC_BAD_TYPE错误。2.2 虚拟网络拓扑的搭建与验证监控和控制都依赖网络环境项目默认用Mininet创建拓扑。常见的毕业设计拓扑是一个简单树形结构一个核心交换机、两个汇聚交换机、每台汇聚下挂两到三台主机。用命令行启动时需要注意Ryu控制器要先运行再启动Mininet否则交换机连接控制器会失败。# 启动Ryu控制器监听6633端口 ryu-manager --ofp-tcp-listen-port 6633 traffic_monitor.py # 启动Mininet创建ovs交换机并连接控制器 sudo mn --topotree, depth2, fanout2 --controllerremote, ip127.0.0.1, port6633 --switchovsk, protocolsOpenFlow13参数说明--topotree, depth2, fanout2创建两层拓扑每层两个子节点共三台交换机。--controllerremote指定连接外部控制器而不是Mininet内置的控制器。protocolsOpenFlow13指定使用OpenFlow 1.3协议Ryu默认支持该版本如果省略某些版本的Mininet会用OpenFlow 1.0导致版本协商失败。启动后进入Mininet命令行pingall验证全网连通性然后去控制器侧查看是否有交换机注册上来的日志。如果连接正常Ryu会打印从交换机收到的hello消息和Features Reply信息其中包含了交换机的dp_id、缓冲区大小和端口列表。连接不上时先检查6633端口是否被占用再确认Mininet网络命名空间的IP配置。3. 流量监控实现端口统计轮询与速率计算拆解3.1 周期任务里如何通过协程实现异步轮询流量监控的原理并不复杂但实现上有个关键点轮询一定要异步。如果监控模块用time.sleep阻塞在主线程里Ryu的事件循环就被卡住了Switch Port Status变化、Packet-in消息全部得不到响应。项目的做法是启动一个greenlet协程每隔固定间隔发送端口统计请求。import time from ryu.lib import hub class TrafficMonitor(app_manager.RyuApp): def __init__(self, *args, **kwargs): super(TrafficMonitor, self).__init__(*args, **kwargs) self.monitor_thread hub.spawn(self._monitor) def _monitor(self): while True: for dp in self.datapaths.values(): self._request_port_stats(dp) hub.sleep(2) def _request_port_stats(self, datapath): ofproto datapath.ofproto parser datapath.ofproto_parser req parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY) datapath.send_msg(req)hub.spawn创建的协程会在Ryu的event loop中调度hub.sleep不会阻塞其他协程的执行。这里轮询间隔设的是2秒如果网络里有大量交换机间隔要相应调大否则控制器的消息处理能力会成为瓶颈。OFPPortStatsRequest的第三个参数设为OFPP_ANY表示请求所有端口的统计信息。交换机收到请求后会回复EventOFPPortStatsReply事件。事件里携带的body是一个列表每个元素对应一个端口的统计信息包括rx_bytes、tx_bytes、rx_packets、tx_packets、rx_errors等字段。3.2 速率计算和SQLite持久化的边界处理拿到两次轮询的计数器差值除以时间间隔就能算出端口速率但这里有一个必须处理的边界交换机计数器是uint64类型溢出后会清零重新计数。如果某次采样的值比上一次小直接按差值计算会得到一个巨大的负数这种情况要么丢弃本次样本要么做溢出补偿。from ryu.controller import ofp_event from ryu.controller.handler import set_ev_cls import sqlite3 import time class TrafficMonitor(app_manager.RyuApp): def __init__(self, *args, **kwargs): super(TrafficMonitor, self).__init__(*args, **kwargs) self.prev_stats {} self.db_conn sqlite3.connect(traffic.db, check_same_threadFalse) self._init_db() def _init_db(self): with self.db_conn: self.db_conn.execute(CREATE TABLE IF NOT EXISTS port_stats ( dpid INTEGER, port_no INTEGER, rx_bytes INTEGER, tx_bytes INTEGER, rx_rate REAL, tx_rate REAL, timestamp REAL)) set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) def _port_stats_reply_handler(self, ev): dpid ev.msg.datapath.id cur_time time.time() prev self.prev_stats.get(dpid, {}) for stat in ev.msg.body: prev_key prev.get(stat.port_no) if prev_key: rx_rate self._calc_rate(prev_key[0], stat.rx_bytes, cur_time - prev_key[2]) tx_rate self._calc_rate(prev_key[1], stat.tx_bytes, cur_time - prev_key[2]) self._store(dpid, stat.port_no, stat.rx_bytes, stat.tx_bytes, rx_rate, tx_rate, cur_time) self.prev_stats[dpid] { s.port_no: (s.rx_bytes, s.tx_bytes, cur_time) for s in ev.msg.body }_calc_rate方法内部用abs(new_val - old_val) / interval计算速率当new_val old_val时先加上2 ** 64做溢出补偿再计算。数据库连接设置了check_same_threadFalse因为Ryu的回调函数和主事件循环可能运行在不同的系统线程中。3.3 从端口维度深入到业务维度的监控视角端口速率只是一个粗粒度维度真正答辩时要能回答“哪台主机在占用带宽”这类问题。要拿到这个数据依赖的是上一条消息——EventOFPPacketIn。当交换机的流表没有匹配到某个数据包时会把包封装在Packet-in消息中上报给控制器。控制器解析包头提取MAC地址、IP地址、端口号就能把流量细分到主机粒度。set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): msg ev.msg datapath msg.datapath pkt packet.Packet(msg.data) eth pkt.get_protocol(ethernet.ethernet) ip_pkt pkt.get_protocol(ipv4.ipv4) if eth and ip_pkt: self._record_flow(eth.src, eth.dst, ip_pkt.src, ip_pkt.dst, msg.total_len)不过要注意只有触发未命中流表的报文才会产生Packet-in。一旦你后续下发了默认转发流表大部分流量直接走数据平面控制器就看不到Packet-in了。解决思路是在交换机上配置流量镜像端口把一份流量副本发给控制器分析——这是OpenFlow 1.3中OFPACT_OUTPUT到OFPP_CONTROLLER的标准用法。4. 流量控制实现动态流表下发与自适应频率控制4.1 基于阈值检测的丢包和限速策略监控系统只统计不控制没有实际意义。这个项目里控制策略的核心是根据上一周期统计的端口速率决定是否对特定主机的流量下发新的流表规则。最常见的控制动作是丢包——当某个端口速率超过阈值时对该端口的所有流量下发drop动作。from ryu.ofproto import ether, ofproto_v1_3 from ryu.lib.packet import ethernet, ipv4 def _drop_all_ports(self, datapath, port_no): ofproto datapath.ofproto parser datapath.ofproto_parser match parser.OFPMatch(in_portport_no) instructions [ parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, []) ] mod parser.OFPFlowMod( datapathdatapath, priority100, matchmatch, instructionsinstructions, idle_timeout0, hard_timeout0, flagsofproto.OFPFF_SEND_FLOW_REM ) datapath.send_msg(mod)这里的空actions列表在OpenFlow 1.3中表示drop。priority100是为了确保规则优先级高于默认的转发规则idle_timeout0表示这条规则永不因空闲而超时hard_timeout0表示永不因时限而删除。如果想要规则只生效一段时间比如60秒后自动恢复流量把hard_timeout设为60即可。限速比丢包更温和通常用交换机队列实现。先要在Mininet的交换机上创建队列再在流表的actions里指定输出到某个队列。# 在s1交换机上创建队列限速2Mbps sudo ovs-vsctl -- --idqos create qos typelinux-htb other_config:max-rate2000000 \ -- --idqueue0 create queue other_config:min-rate1000000 other_config:max-rate2000000 \ -- set port s1-eth1 qosqosmax-rate的单位是bps所以2000000对应2Mbps。队列创建完成后控制器的流表action就不能再直接输出到端口而要通过OFPQueueGetConfigReply拿到队列id然后使用OFPQueueProp组装出带有queue_id的输出动作。4.2 自适应频率控制背后的P-I-D思想固定频率的轮询和策略下发有个明显缺陷流量波动时要么反应慢了导致过载时间过长要么过于频繁地开启和关闭限制策略产生抖动。项目里用了一个简单但答辩时很出彩的做法——根据速率的偏差动态调整监控和决策的频率类比PID控制中的比例项和积分项。class AdaptiveController: def __init__(self, kp0.6, ki0.15, target_rate1000000): self.kp kp self.ki ki self.target_rate target_rate self.integral 0 def decide_interval(self, current_rate): error (current_rate - self.target_rate) / self.target_rate self.integral error base_interval 2.0 adjust self.kp * error self.ki * max(self.integral, -5) interval max(0.5, min(10.0, base_interval - adjust)) return intervalkp控制当前偏差的响应强度偏差越大轮询间隔缩小得越多系统反应越快。ki累积历史偏差应对持续过载的情况——如果流量长时间超过阈值即使当前偏差变小积分项也会促使控制器继续加大控制力度。max(self.integral, -5)是抗积分饱和的限制防止长期低负载下积分项深陷负值。当你把这段PID控制逻辑引入监控模块后轮询间隔不再是一个固定值而是随着流量的偏离程度在0.5到10秒之间变化。答辩时可以从数据库里拉出两次决策的间隔序列做成散点图直观展示“自适应频率控制”的实际效果。4.3 流表下发失败时的排查路径下发流表不生效是SDN控制类项目最典型的坑。第一优先级检查的是流表优先级——OpenFlow 1.3交换机在匹配时选择优先级最高的规则如果已有的table-miss规则优先级是0你下发的规则优先级是100正常情况下会被优先匹配但如果代码里构造OFPMatch时把匹配字段写错了比如把eth_type字段写成了ip_proto规则根本不会命中。第二个坑是action列表为空和OFPIT_CLEAR_ACTIONS指令的区别。空列表是drop而OFPIT_CLEAR_ACTIONS是把已有的action set清空但不阻止流水线继续执行。如果指令里只写了CLEAR_ACTIONS而没写APPLY_ACTIONS报文会被丢弃但流表的可能性计数不会增加。用ovs-ofctl dump-flows s1检查交换机实际收到的流表内容比在控制器侧打日志排查快得多。看到flow表里出现你下发规则的十六进制表示说明下发链路没有问题如果没出现就去查控制器的send_msg调用是否被异常捕获吞掉了。5. 可视化监控面板把统计和决策用Flask撑起来5.1 REST API设计思路和前端轮询机制毕设系统要拿得出手至少得有一个能展示实时曲线的Web页面不能只靠终端里print日志。项目里Web模块采用Flask提供数据接口前端每2秒调用一次接口拿最新的端口速率数据。from flask import Flask, jsonify, request import sqlite3 app Flask(__name__) DB_PATH traffic.db def query_db(query, args()): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row cur conn.execute(query, args) rows cur.fetchall() conn.close() return [dict(row) for row in rows] app.route(/api/ports/int:dpid, methods[GET]) def get_port_stats(dpid): limit int(request.args.get(limit, 60)) rows query_db( SELECT port_no, rx_rate, tx_rate, timestamp FROM port_stats WHERE dpid ? ORDER BY timestamp DESC LIMIT ?, (dpid, limit) ) return jsonify(rows) app.route(/api/flows/int:dpid, methods[GET]) def get_flow_rules(dpid): rows query_db(SELECT match, action, priority FROM active_flows WHERE dpid ?, (dpid,)) return jsonify(rows) if __name__ __main__: app.run(host0.0.0.0, port8080, debugFalse)前端页面用ECharts绘制曲线setInterval每2秒请求一次/api/ports/1把返回的rx_rate序列追加到图表数据里同时裁剪掉超出窗口的旧数据保持一个滚动效果。这个方案的优点是实现成本低不需要引入WebSocket和异步框架缺点是HTTP轮询的频率受限于REST接口的处理能力一般2到5秒一次比较合理。5.2 拓扑可视化把链路状态画出来除了速率曲线拓扑图在答辩演示中能起到加分效果。控制器通过交换机的状态事件维护一张全局拓扑表然后通过另一个REST接口输出节点和边的结构化数据。前端用vis.js网络图组件把拓扑渲染出来链路的颜色根据当前速率的负载程度变化超过阈值的链路显示为红色。{ nodes: [ {id: 1, label: s1, type: switch}, {id: 2, label: h1, type: host} ], links: [ {source: 1, target: 2, port: 1, rx_rate: 800} ] }控制器侧需要监听EventOFPPortStatus封装OFPPortStatusRequest来同步端口状态。当一条链路断掉时端口状态变为DOWNREST接口返回的links数组里对应的边就消失了前端图会自动重绘。这一套联动逻辑在毕设项目里算是完整度比较高的。6. 链路过载模拟与验证技巧6.1 用iperf打满带宽后观察流表变化项目部署完成后必须自己先验证一遍控制动作是真实生效的而不是只在数据库里记了一条记录。验证控制策略的标准做法是从h1向h2打流量观察速率曲线和流表变化。# 在h2上启动服务端 mininet xterm h2 # 在h1上生成TCP流量持续30秒 mininet h1 iperf -c 10.0.0.2 -t 30 -i 1初始阶段两条主机之间的流量会触发Packet-in消息控制器先下发普通的转发规则。持续打流量超过阈值后观察ovs-ofctl dump-flows s1可以看到新增了一条priority100, in_port1 actionsdrop的规则而之前的转发规则优先级是10。此时h1上的iperf输出会显示带宽从几百Mbps掉到接近0说明丢包规则已生效。当流量停止后如果限速规则设置了idle_timeout60秒内没有新流量匹配该规则交换机会自动删除它。删除时如果控制器请求了OFPFF_SEND_FLOW_REM还会收到FlowRemoved通知数据库里会记录这次规则的生命周期。这一整套证据链完整展示了“监控-决策-下发-自动回收”的闭环状态机。6.2 SQLite的写入锁和轮询间隔的权衡SQLite在并发写入时会对整个数据库文件加锁Ryu的协程回调、Flask的请求线程同时插入数据时可能频繁抛出database is locked异常。把数据库连接改为“单写多读”模式写入请求统一路由到一个队列由单一协程串行落库读取方直接用只读连接查询能把这个问题的概率降到最低。另一个坑是速率计算的时间差。如果上次统计的prev_time和当前时间的差值远大于轮询间隔比如协程阻塞了500毫秒直接用这个差值计算速率会造成瞬时尖峰。处理方式是每次采样时记录实际时间戳计算时以真实时间差为分母同时丢弃时间差超过10秒的样本。轮询间隔的设置直接影响控制策略的响应速度。间隔设成1秒控制响应快但控制器和交换机的消息开销大设成5秒负载小但流量突增时可能在5秒内打满出口带宽。项目里用之前那套自适应频率来控制轮询间隔既兼顾了常态下的轻负载也保证了过载时的快速响应逻辑。答辩时拿这一条作为技术亮点展开比单纯说“我做了阈值告警”要有说服力得多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门