
简介这份压缩包是西安交通大学计算机专业软件定义网络课程的实验作业合集面向正在学习SDN课程的高校学生与网络方向技术人员用于掌握Ryu控制器、OpenFlow协议及网络编程实践。包内共73个文件涵盖Python源码、实验指导PDF、Markdown报告、拓扑示意图及shell脚本等其中20个py文件对应各实验的控制器逻辑png/jpg图片用于记录拓扑结构和运行结果md与pdf提供实验说明与报告模板整体压缩包约24.17MB。实验内容覆盖fat-tree拓扑构建、最短路径转发、自学习交换机、广播风暴处理及网络感知路由等典型SDN场景配有西安交大2020年实验补丁和Mininet环境辅助脚本。已有68人学习下载适合作为课程实验参考或SDN入门综合练习素材。1. 西安交大软件定义网络课的lab作业.zip先把它当工程交付物而不是作业拿到「西安交大计算机软件定义网络课的lab作业.zip」这份压缩包的学生十有八九已经在群里被警告过先跑通再交。软件定义网络SDN的lab作业听起来是一份普通课程作业背后藏着的却是一整套工程链路——从Mininet网络仿真环境到OpenFlow协议再到控制器应用逻辑最后还要过验收脚本或口头答辩。很多人在这份zip上翻车不是因为不懂SDN概念而是不知道该怎么把「压缩包里的代码」变成「能在自己机器上复现的实验结果」。这份zip通常是一份课程交付件里面有拓扑定义脚本、控制器代码、README和验收要求。你会在这份作业里接触第一个SDN事实——交换机默认不转发一切转发行为都由控制器通过OpenFlow协议下发流表来定义。读懂这份lab作业并把它跑通意味着你已经把「控制器下发流表、交换机按表转发」这个最小闭环装进了脑子里。接下来我从解压验包、环境搭起、跑通最小用例到避坑一层层拆开讲。2. 解压前先验包用zipinfo和unzip -t把「zip伪加密」和EOCD错误挡在门外2.1 先验证压缩包完整性zipinfo搭配unzip -t才是合格动作拿到lab作业.zip最常见的错误操作是直接双击解压。Windows自带解压工具能处理一般情况但遇到zip伪加密这类问题直接解压得到的文件可能全是乱码或者在解压时弹出「压缩包已损坏」。实践中我一般先跑两个命令一个看结构、一个测完整性# 先看压缩包内部清单不实际解压 zipinfo -l lab.zip # 无损测试压缩包完整性与crc校验 unzip -t lab.zip /dev/null echo CRC OK || echo CRC FAILEDzipinfo -l会列出压缩包内文件清单和压缩前后大小。如果输出里出现大量文件名带^M或文件名字节变形基本可以判断压缩包被Windows工具二次打包过解压后换行符会出问题。unzip -t是zip测试模式逐个文件做CRC32校验。这里有个细节如果你的unzip版本较旧遇到zip64单文件超过4GB或伪加密flag会直接报could not find eocd。EOCDEnd of Central Directory记录位于zip文件末尾标准EOCD固定部分22字节文件被截断、上传不完整、或者被某些中转软件改写后unzip就找不到EOCD。此时别急着用修复工具硬解回到课程平台重新下载原始zip再比较下载文件大小和zipinfo显示的解压后总大小能对上再继续。提示伪加密的zip可能解压出来一堆乱码文件尤其常见于课程论坛二次打包。验包这一步不做后面所有调试都在一个坏地基上。2.2 把作业目录拆成四类文档、拓扑、控制器、测试脚本解压后别急着打开代码文件。常见SDN课程lab作业的目录结构大致是这样的lab/ ├── README.md # 实验指导书写明验收要求 ├── topo.py # mininet拓扑定义脚本 ├── controller.py # ryu控制器应用或pox/onos代码 ├── test/ │ ├── test_forwarding.py # 自动化测试脚本 │ └── ping_expect.txt # 期望输出 └── output/ # 放实验截图或日志的目录我建议按四个维度分筐文档README、指导书、拓扑topo.py、控制器controller.py、测试脚本test/。为什么要分因为SDN lab的验收通常按「拓扑正确、控制器逻辑正确、运行结果可复用」三块打分把这三块混在一起读很容易在调一个地方的时候忘了另一个地方。分筐之后要做的第一件事是先读README再动代码。这个顺序很多人反着来结果在topo.py里改了半天最后发现作业要求的是「不得修改控制器只改拓扑」。分筐还有一个实际好处你可以快速判断这份lab作业的控制器框架。如果是Ryu文件里会有set_ev_cls装饰器如果是POX会有launcher入口如果是ONOS则是一整个Java工程。从西安交大这门课的一般经验看整体偏Python风格Ryu出现的概率较大。当然具体以你zip里的README为准。2.3 环境自检python、mininet、ryu三者的版本要互相匹配在跑拓扑之前用下面这段命令把三层环境全部自检一遍# 1) 检查python版本SDN课程lab常见的是python3.6~3.10 python3 --version # 2) 检查mininet是否可用需要root权限 sudo mn --version # 3) 检查ovs是否支持你需要的openflow版本 sudo ovs-vsctl --version | head -1 sudo ovs-ofctl --version这里有个容易被忽略的兼容性细节mininet的版本决定它能创建的OVS交换机版本OVS版本决定它支持OpenFlow 1.0、1.3还是1.5。而Ryu侧默认只监听OpenFlow 1.3的消息取决于代码里设置的OFP_VERSIONS。如果mininet创建的交换机只支持OpenFlow 1.0而控制器只监听1.3两边握手阶段就会失败表现就是你会在Ryu日志里看到version negotiation failed或者干脆只有hello没有后续消息。如果你是在Ubuntu类系统上从零搭环境常见做法是sudo apt-get install mininet openvswitch-switch sudo pip3 install ryu安装完成后sudo ovs-vsctl show能正常返回ovs-ofctl --version显示的版本范围能覆盖1.3环境基本就通了一半。至于Ryu的版本我一般不用pip强行装最新版而是按lab作业README给的版本号来装。如果README没写就用pip3 install ryu装默认版然后python3 -c import ryu.base.app_manager确认能导入。2.4 最小复现路径一个命令把lab环境拉起来环境自检通过后先不要直接跑作业里的自动化测试而是做一个最小复现手动启动mininet拓扑手动启动ryu然后用一条ping命令验证SDN链路通不通。我常用的顺序是# 终端1以远程控制器模式启动mininet连到本地6653端口 sudo mn --custom topo.py --topo mytopo \ --controllerremote,ip127.0.0.1,port6653 \ --switch ovs,protocolsOpenFlow13 # 终端2启动ryu控制器应用 sudo ryu-manager --ofp-tcp-listen-port 6653 controller.pymininet里--topo mytopo对应topo.py里定义的拓扑名--switch ovs,protocolsOpenFlow13是为了让OVS明确走OpenFlow 1.3协议避免版本协商失败。Ryu侧--ofp-tcp-listen-port 6653要跟mininet的port一致很多lab踩坑都是因为默认6633老版本和6653新版本混用。两条命令起来后在mininet的CLI里执行mininet h1 ping h2如果通了说明lab作业的最小闭环已经成立拓扑能起、控制器能连、流表能下发。如果没通不要先怀疑代码先用sudo ovs-ofctl -O OpenFlow13 dump-flows s1看交换机里是否有流表再看ryu日志里有没有出现packet_in。这一步就通了后面的调试会顺很多。3. lab作业的核心机制从PACKET_IN到FLOW_MOD流表是这样落地的3.1 OpenFlow匹配-动作模型交换机的转发行为由流表定义SDN课程里最重要的概念是两个转发平面与控制平面分离以及控制器对转发行为的集中定义。lab作业里你写的控制器代码最终会转换成OpenFlow流表项下发到交换机交换机再按流表项处理每个到来的包。流表项的核心结构是「匹配字段 指令」。以OpenFlow 1.3为例一条流表项大致是match: in_port1, eth_type0x0800, ipv4_dst10.0.0.2 actions: output:2 priority: 10优先级priority、匹配字段match、动作actions是三个关键参数。lab作业里最常见的任务过程是把每一条到达交换机的数据包先在控制器里「决策」好再通过FLOW_MOD下发交换机后续对相同匹配的包直接按表转发不再打扰控制器。这也是很多lab验收测试脚本会要求「第二次ping包时不能有PACKET_IN」的原因——如果控制器每次都处理说明你流表没装对或没装全。3.2 读懂Ryu控制器回调packet_in和flow_mod是两台机器在对话Ryu控制器应用的骨架是继承ryu.base.app_manager.RyuApp的类核心入口是事件回调。以最简单的「二层自学习交换机」为例代码一般长这样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 class L2Switch(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(L2Switch, self).__init__(*args, **kwargs) self.mac_to_port {} set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): msg ev.msg dp msg.datapath in_port msg.match[in_port] pkt packet.Packet(msg.data) eth pkt.get_protocol(ethernet.ethernet) # 学习源MAC与端口映射 self.mac_to_port[eth.src] in_port # 查MAC表命中则下发FLOW_MOD未命中则洪泛 if eth.dst in self.mac_to_port: out_port self.mac_to_port[eth.dst] parser dp.ofproto_parser match parser.OFPMatch(in_portin_port, eth_dsteth.dst) actions [parser.OFPActionOutput(out_port)] inst [parser.OFPInstructionActions( dp.ofproto.OFPIT_APPLY_ACTIONS, actions)] mod parser.OFPFlowMod( datapathdp, priority10, matchmatch, instructionsinst) dp.send_msg(mod) else: out_port dp.ofproto.OFPP_FLOOD # 用PACKET_OUT告诉交换机怎么处理当前这个包 actions [dp.ofproto_parser.OFPActionOutput(out_port)] out dp.ofproto_parser.OFPPacketOut( datapathdp, buffer_idmsg.buffer_id, in_portin_port, actionsactions) dp.send_msg(out)这段代码的运行逻辑要拆开看。交换机收到一个未知目的MAC的包匹配不到流表就把数据包封装成PACKET_IN发给控制器携带buffer_id和in_port。控制器收到后在mac_to_port里学习源MAC然后查目的MAC查到就在_install_flow逻辑里设置OFPFlowMod以后相似包由交换机本地转发查不到就洪泛FLOOD通过OFPPacketOut让交换机把这个包从所有非入端口发出去。这里有三个教学点一是set_ev_cls装饰器把OpenFlow事件绑定到回调函数二是OFPMatch里能写多个匹配字段写少了流表过宽写多了匹配太严三是OFPPacketOut针对当前这一个包做「一次性指令」而OFPFlowMod是「持久化安装的规则」。lab验收时最常问的一道题就是PacketIn和FlowMod的区别本质就是「临时包处理」和「长期流表规则」的区别。3.3 用dpctl/ovs-ofctl验证流表下发结果控制器写了拓扑搭了如何确认流表真的下发成功答案是直接看交换机侧状态。mininet自带命令# 在mininet CLI里执行 mininet dpctl dump-flows或者到系统层用ovs-ofctlsudo ovs-ofctl -O OpenFlow13 dump-flows s1两种命令都是为了查看OVS桥s1上的流表。输出里你会看到类似这样的内容cookie0x0, duration15.32s, table0, n_packets2, n_bytes196, priority10,ip,in_port1,nw_dst10.0.0.2 actionsoutput:2这行记录告诉你这条流表匹配了从1号口进入的IP包目的IP是10.0.0.2动作是从2号口丢出去且已命中两次。n_packets和n_bytes两个计数器在做lab验收时很有用——如果流表已安装但n_packets一直是0说明匹配条件写错了或流量根本没走到这台交换机。提示ovs-ofctl默认显示OpenFlow 1.0格式控制器下发的是1.3流表时不加-O OpenFlow13会显示「流表不存在」。这是SDN课程里最常见的「看见了看不见」的伪故障。3.4 从pingall到tcpdump验证数据面而不只是控制面控制器侧能看到日志交换机侧能看到流表但真正决定作业能不能过的是「数据面能不能通」。mininet提供了一个快速验证命令mininet pingall它会从每个host去ping其他所有host并汇总成功失败结果。如果你的拓扑是线性或树形第一次运行时可能因为流表还没学全而出现丢包这是正常的。更细的验证方式是抓包在mininet外部用tcpdump抓交换机与主机之间的接口sudo tcpdump -i s1-eth1 -w s1_eth1.pcap icmp抓包结果用wireshark打开你会看到ICMP请求、应答以及可能伴随的ARP交互。对比控制器日志你会发现第一次ping时ARP请求触发过PACKET_IN。这个pcap文件就是你lab作业报告里最好的「实验结果证据」。很多同学只会截图ping命令回显但讲师更愿意看到pcap或tcpdump输出这才是数据面已验证的硬证据。4. lab作业避坑指南四个最容易让作业交不上去的坑4.1 坑一Mininet启动失败报错cannot find bridge s1现象执行sudo mn时卡住或报错随后出现类似cannot find bridge s1。原因通常是上一次实验关闭时OVS交换机没被清理干净残留的命名空间和网桥把mininet搞懵了另一种原因是在多个终端并行启动sudo mn两次实验互相抢系统资源。解决先彻底清理再重新启动。sudo mn -c sudo pkill -f ryu-manager sudo ovs-vsctl show # 确认没有残留bridge如果ovs-vsctl show里还能看到旧的s1桥手动删除sudo ovs-vsctl del-br s1。这套组合是我遇到lab环境崩掉后的万能后悔药。顺便说一句sudo mn -c会清理mininet创建的命名空间和OVS桥但它不会杀ryu进程所以pkill那步别省。4.2 坑二控制器握手成功交换机里却始终没有流表现象ryu-manager日志显示与datapath握手完成但mininet里执行dpctl dump-flows输出为空甚至一直没有packet_in消息上报。原因多半出在协议版本不匹配。比如mininet启动时用了--switch ovs,protocolsOpenFlow10而ryu代码里设置OFP_VERSIONS [ofproto_v1_3.OFP_VERSION]握手之后交换机和控制器各说各话flow_mod消息虽然发了但交换机协议栈无法解析。另一种低频原因OVS桥配置了多控制器ryu只连上了其中一个而流表下到了另一个控制器的连接上。解决统一协议版本并检查mininet启动参数。最稳的方式是显式指定sudo mn --switch ovs,protocolsOpenFlow13 \ --controllerremote,ip127.0.0.1,port6653同时确认ryu侧OFP_VERSIONS只保留OpenFlow13。不要同时声明多个版本某些OVS版本在多版本协商时行为很怪。4.3 坑三第一次ping通了第二次ping却超时现象在mininet CLI里执行h1 ping h2第一次有reply但延迟很高第二次开始全部超时。中断后重ping又恢复循环往复。原因控制器学习MAC地址后想下发FLOW_MOD但流表里没配超时参数或buffer_id用错导致后续包没走流表又回到PACKET_IN。由于mac_to_port表已经学会了出端口控制器不再发FLOW_MOD只发PACKET_OUT但此刻交换机的包已经被丢弃了。体感就是「通一次断一次」。解决给FLOW_MOD加明确的priority和超时时间mod parser.OFPFlowMod( datapathdp, priority10, matchmatch, instructionsinst, idle_timeout60, hard_timeout0) dp.send_msg(mod)idle_timeout60表示60秒内没有匹配流量就自动删除hard_timeout0表示不设硬超时。对lab作业验收足够但如果助教测试脚本在长时间空闲后又立刻ping可能出现超时删表导致的假性丢包改成idle_timeout60一般能避开。4.4 坑四提交的zip包在助教那边解压失败或代码全变乱码现象自己电脑上解压完能正常跑但把原zip交上去后助教那边提示could not find eocd或者解压出来的Python文件带^M、中文注释全部乱码。原因课程平台对zip再上传可能重新压缩中间环节破坏二进制完整性也可能是你在网盘里「预览」过zip网盘自动生成一个伪zip还有一类是Windows「发送到压缩文件夹」生成的zip携带DOS/Unix换行信息解压到Linux后所有行尾都带\r。解决不要用Windows自带压缩工具统一用7z或zip命令重新打包并在提交前做一次干净检查cd lab/ zip -r lab_final.zip . -x *__pycache__* -x *.pcap -x *.log unzip -t lab_final.zip提交前再做一遍unzip -t确保助教解压时不翻车。这个习惯帮我避开了至少两次实验课被要求重新提交的尴尬。如果zip包下载时被下载工具改写了本地文件头可以zip -FF damaged.zip --out repaired.zip尝试修复但修复结果不一定可靠最稳妥的办法还是回到源头重新下载。5. 把lab作业从「能跑」改到「跑出彩」三条可控的进阶路径5.1 路径一把静态拓扑改成可参数化拓扑基础lab作业里topo.py往往是写死的星型或线性拓扑class MyTopo(Topo): def build(self): s1 self.addSwitch(s1) s2 self.addSwitch(s2) h1 self.addHost(h1) h2 self.addHost(h2) self.addLink(s1, s2) self.addLink(h1, s1) self.addLink(h2, s2)这种拓扑演示功能可以但扩展性一般。进阶做法是让build方法接收参数class ParamTopo(Topo): def __init__(self, fanout2, **opts): Topo.__init__(self, **opts) s1 self.addSwitch(s1) for i in range(fanout): h self.addHost(fh{i1}) self.addLink(h, s1) topos {paramtopo: (lambda: ParamTopo(fanout4))}对应mininet启动命令sudo mn --custom topo_param.py --topo paramtopo,fanout4 \ --controllerremote,ip127.0.0.1,port6653注意--topo paramtopo,fanout4这种传参写法mininet会把它拆成拓扑类名和关键字参数fanout4。这个改动很小但验收时可以当「扩展性」亮点讲。5.2 路径二把「洪泛转发」改成「最短路径转发」lab作业的控制器如果只是二层自学习洪泛行为在较大拓扑里会造成大量重复报文。进阶功能是加一个简单的链路发现模块。在Ryu里可通过监听EventOFPPacketIn识别LLDP帧来记录邻居关系set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def lldp_packet_in(self, ev): msg ev.msg data msg.data # 判断是否LLDP帧LLDP目的MAC固定为01:80:c2:00:00:0e if data[0:6] b\x01\x80\xc2\x00\x00\x0e: self.logger.info(LLDP from sw%s port%s, msg.datapath.id, msg.match[in_port])只做链路记录不参与转发决策代码量不大但能证明你理解拓扑发现的基本思路。更完整的路径计算可以引入networkx先构建交换机邻接表再用shortest_path算路径逐跳下发FLOW_MOD。这部分代码放进lab作业里是明显的加分项。5.3 路径三加一个「控制器侧的网络监控」模块很多lab作业只要求转发但如果实验报告能展示「控制器定期收集端口统计并绘图」完成度直接上升一个台阶。Ryu提供了EventOFPPortStatsReply事件你可以周期性发送OFPPortStatsRequest把每个端口的rx_bytes、tx_bytes记录成时间序列from ryu.lib import hub def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) hub.spawn(self._monitor) def _monitor(self): while True: hub.sleep(5) for dp in self.datapaths.values(): req dp.ofproto_parser.OFPPortStatsRequest( datapathdp, port_nodp.ofproto.OFPP_ANY) dp.send_msg(req)这里用到了hub.spawn是Ryu基于greenlet的协作式调度。注意self.datapaths需要在EventOFPStateChange回调里维护添加新datapath、移除旧datapath否则反复重建拓扑后会有残留连接。监控模块的价值不在代码多少而在于展示你对「控制平面周期任务」的理解。配合数据收集可以在报告里放一张用matplotlib画出的带宽时序图。5.4 验证改动正确性回归测试脚本与诚实记录改动三类代码后最怕的是原有功能被改挂。我一般在每个lab目录下放一个run.sh把启动、测试、清理串起来#!/bin/bash set -e echo 清理旧环境 sudo mn -c || true echo 启动控制器 sudo ryu-manager --ofp-tcp-listen-port 6653 controller.py ryu.log 21 sleep 3 echo 启动拓扑并做连通性测试 sudo mn --custom topo.py --topo paramtopo \ --controllerremote,ip127.0.0.1,port6653 \ --switch ovs,protocolsOpenFlow13 --testpingall echo 清理 sudo mn -cset -e保证某一步失败时立即退出避免在一个坏环境里继续测试。--testpingall让mininet自动执行连通性测试并退出适合写进脚本做回归如果你想要交互式CLI去掉--testpingall即可。每次改完代码都重新跑一遍回归成本低到可以忽略。6. 最后一课把lab作业zip收成你的SDN调试工具箱跑通lab作业只是开始真正值得做的是把这次作业沉淀成一套可复用的SDN调试工具箱尤其是把「排查思路」固化成文本记录。我的习惯是每次遇到一个坑就在项目里新建一个debug_notes.md记录现象、原因、解决命令。比如第4章那几个坑每条最多五行。等你做完两三个lab这份笔记就成了面试时能拿出手的软实力。另一个值得学的小技巧是给作业做「双快照」一份original/目录放解压出来未改动的原版代码另一份workspace/目录放自己的改动。这样提交时如果发现某处改出问题可以随时对照原版找差距而不是从头再解压一次。组合上第4章讲的zip -r lab_final.zip重打包流程整个交付过程就完整闭环了。我自己的血泪经验是SDN课程lab作业最难的往往不是SDN本身而是环境安装、版本匹配以及「我以为控制器已下发流表但实际没有」的隐蔽故障。把这套调试方法写成笔记比背概念更不容易在验收现场翻车。希望帮到你。本文还有配套的精品资源点击获取