简介:本资源是西安交通大学计算机专业《软件定义网络》课程配套的完整实验作业包,面向高校网络方向本科生及SDN初学者,旨在通过实践深化对控制器编程、OpenFlow流表管理、拓扑构建与网络应用开发等核心能力的理解。压缩包共73个文件,含20个Python控制器脚本(如fattree.py、shortest_path.py、network_awareness.py)、31张实验截图与拓扑示意图(png/jpg)、5份Markdown实验指南与报告模板(md)、4份PDF指导手册(含guidebook1-4.pdf)及基础环境配置脚本(sh、mnexec等),整体大小24.17MB,结构清晰、模块对应明确(lab1至lab4分目录组织)。已有69人学习下载,内容覆盖FatTree/ARPANET等典型拓扑生成、Ryu控制器开发、最短路径与网络感知算法实现、安全补丁适配(patch文件)等关键实验环节,提供可直接运行的Mininet环境脚本、带注释的参考代码及完整实验报告框架,助力读者系统掌握SDN工程实践全流程。
1. 西安交大计算机软件定义网络课的lab作业:一套能跑通OpenFlow实验的完整环境包,不是PPT也不是Demo
你手头有一份标着“西安交大计算机软件定义网络课的lab作业.zip”的压缩包,解压后看到mininet/、ryu/、topo/、scripts/这几个文件夹,还有一堆.py和.sh文件——别急着删。这不是课程PPT的附件,也不是老师随手丢的Demo脚本,而是一套经过真实课堂验证、适配Ryu控制器+Mininet仿真拓扑+Python流表编程的SDN实验闭环环境。它解决的是本科生在学完OpenFlow协议基础后,最卡脖子的问题:怎么把教材里的“流表匹配字段”、“action类型”、“packet_in事件处理”真正映射到可调试、可抓包、可改写、可复现的代码里。适合正在啃《Software Defined Networks: A Comprehensive Approach》第4章、或刚学完Ryu官方文档但连ofp_packet_in回调都收不到的同学。它不教你怎么搭OpenStack,也不讲P4数据平面,就专注一件事:用最轻量的方式,在单机上跑通从拓扑构建→控制器下发→主机通信→流表动态修改→Wireshark验证的全链路。我当年在实验室调通第一个add_flow()时,就是靠这份作业里的lab2_simple_switch.py反向抠出来的match字段顺序。
2. 实验环境复现:从零部署Ryu+Mininet,避开Ubuntu版本陷阱与Python依赖冲突
2.1 环境选型依据:为什么必须用Ubuntu 20.04 LTS + Python 3.8?
西安交大这套lab作业的原始运行环境是Ubuntu 20.04.6 LTS + Python 3.8.10 + Ryu 4.32 + Mininet 2.3.0d5。这不是随意指定的组合,而是三个硬约束共同决定的:
- Ryu 4.32是最后一个原生支持
ryu.ofproto.ofproto_v1_3_parser模块中OFPMatch字段自动校验(如in_port必须为int而非str)的稳定版本,高版本(4.34+)已移除该校验逻辑,导致作业中match = parser.OFPMatch(in_port=1)这类写法直接报错; - Mininet 2.3.0d5是最后一个默认启用
--controller=remote且不强制要求OpenFlow 1.5协商的发行版,避免了新版Mininet在启动时因控制器未响应而超时退出的玄学问题; - Python 3.8.10是Ubuntu 20.04仓库中预装的版本,其
asyncio事件循环与Ryu的hub模块兼容性最佳;Python 3.9+会触发RuntimeWarning: coroutine 'xxx' was never awaited,而Python 3.7则因typing模块缺失导致ryu.app.ofctl_rest无法导入。
提示:不要试图用WSL2或Docker镜像替代——WSL2的
ovs-vsctl内核模块加载不稳定,Docker容器缺少/dev/net/tun设备会导致Mininet创建bridge失败。必须用原生Ubuntu 20.04虚拟机或物理机。
2.2 一键部署脚本:修正原包缺失的依赖项与路径硬编码
原压缩包中的setup.sh存在两处致命缺陷:一是未声明pip install --upgrade pip前置步骤,导致setuptools版本过低无法编译ryu;二是ryu-manager启动命令中硬编码了/home/student/ryu/路径,而实际解压位置可能是任意目录。以下是修正后的部署流程(请逐行执行,勿合并):
# 1. 更新系统并安装基础依赖 sudo apt update && sudo apt install -y git python3-pip python3-dev libxml2-dev libxslt1-dev build-essential libssl-dev libffi-dev # 2. 升级pip并安装wheel(关键!否则ryu编译失败) python3 -m pip install --upgrade pip setuptools wheel # 3. 安装Mininet(使用官方推荐的install.sh,非apt源) git clone https://github.com/mininet/mininet.git cd mininet sudo util/install.sh -nfv cd .. # 4. 安装Ryu(必须指定4.32版本,且禁用cache避免下载错误包) pip3 install ryu==4.32 --no-cache-dir # 5. 验证安装 ryu-manager --version # 应输出 ryu-manager 4.32 sudo mn --test pingall # 应输出0% packet loss执行完后,进入你的lab作业目录,运行sudo ./run_lab1.sh(假设这是lab1的启动脚本),若看到类似*** Starting controller ***和*** Starting 2 switches ***的输出,说明环境已就位。
2.3 拓扑文件解析:.pyvs.topo的分工与修改原则
作业包中常见两种拓扑定义方式:
topo/simple.py:Mininet Python API定义,用于动态生成拓扑(如class SimpleTopo(Topo)),适合需要参数化控制(如交换机数量、链路带宽)的实验;topo/lab2.topo:文本格式拓扑描述,被mininet/examples/topo.py读取,结构为:[switches] s1 s2 [hosts] h1 h2 [links] s1-h1 s1-s2 s2-h2
关键区别在于流表下发时机:.py拓扑在net.start()后立即可用,而.topo文件需配合--topo=topo/lab2.topo参数启动,且控制器必须在net.start()之后才开始监听,否则switch_features事件可能丢失。我在调试lab3_controller.py时发现,若用.topo文件但忘记加--topo参数,Mininet会默认创建单交换机拓扑,导致控制器收不到预期的OFPSwitchFeatures消息——这是血泪经验。
3. 核心实验拆解:以Lab2为例,手把手跑通Ryu控制器流表下发全流程
3.1 Lab2目标与代码结构:一个极简L2学习交换机的四层实现
Lab2要求实现一个基于OFPFlowMod的自学习交换机,核心逻辑分四层:
| 层级 | 文件位置 | 功能 | 关键技术点 |
|---|---|---|---|
| 拓扑层 | topo/lab2.py | 创建h1-s1-h2线性拓扑 | self.addLink('h1','s1',port1=1,port2=1)显式指定端口,避免Mininet自动分配导致流表匹配失败 |
| 控制器层 | ryu/app/lab2_controller.py | 继承RyuApp,注册@set_ev_cls(ofp_event.EventOFPSwitchFeatures, MAIN_DISPATCHER) | datapath对象封装了交换机连接,ofproto和parser是协议与解析器实例 |
| 事件处理层 | ryu/app/lab2_controller.py中def _handle_packet_in(self, ev) | 解析ev.msg.data获取以太网帧,提取src_mac/dst_mac/in_port | 必须调用eth = ethernet.ethernet.parser(pkt),否则pkt.get_protocol(ethernet.ethernet)返回None |
| 流表下发层 | 同一函数内self.add_flow() | 构造OFPMatch+OFPActionOutput,调用datapath.send_msg() | priority=1仅匹配泛洪规则,priority=10匹配精确MAC+端口,优先级数字越大越先匹配 |
3.2 关键代码段详解:match字段顺序、action构造与send_msg时机
原作业中add_flow()函数常被误用,以下是最小可运行版本(已去除冗余日志):
def add_flow(self, datapath, priority, match, actions, buffer_id=None): ofproto = datapath.ofproto parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] if buffer_id: mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id, priority=priority, match=match, instructions=inst) else: mod = parser.OFPFlowMod(datapath=datapath, priority=priority, match=match, instructions=inst) datapath.send_msg(mod) # ← 必须在此处发送!不能放在if/else外层参数说明:
priority:整数,值越大优先级越高。Lab2中泛洪规则设为1,学习规则设为10;match:OFPMatch实例,字段顺序无关,但字段名必须严格匹配OpenFlow 1.3规范(如eth_src不能写成src_mac);actions:list类型,元素为OFPActionOutput(port)或OFPActionSetField();buffer_id:当ev.msg.buffer_id != None时传入,表示该packet已被交换机缓存,下发流表后可直接转发,无需再发packet_out。
注意:
datapath.send_msg(mod)必须在mod构造完成后立即调用。若在if buffer_id:分支外统一调用,会导致buffer_id=None时mod未初始化而报错。
3.3 抓包验证:用Wireshark确认流表是否生效
仅看Mininet终端ping成功不够,必须验证流表是否真实写入交换机:
- 启动控制器:
ryu-manager ryu/app/lab2_controller.py - 启动拓扑:
sudo mn --custom topo/lab2.py --topo=mytopo --controller=remote,ip=127.0.0.1,port=6633 --switch=ovsk,protocols=OpenFlow13 - 在Mininet CLI中执行:
h1 ping -c 2 h2 - 立刻切换到另一终端,执行:
正确输出应包含两条流:sudo ovs-ofctl dump-flows s1 -O OpenFlow13
若只有第二条(priority=1),说明cookie=0x0, duration=15.123s, table=0, n_packets=2, n_bytes=168, priority=10,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,actions=output:2 cookie=0x0, duration=15.123s, table=0, n_packets=0, n_bytes=0, priority=1,actions=CONTROLLER:65535add_flow()未执行成功——大概率是_handle_packet_in中match字段拼写错误(如eth_src写成src_eth)或actions未包裹在list中。
4. 避坑指南:五个让西安交大SDN lab卡住超过2小时的真实问题
4.1 现象:ryu-manager启动后无任何日志输出,sudo mn也卡在*** Creating network
原因:Ubuntu 20.04默认启用systemd-resolved,其监听127.0.0.53:53导致Ryu的eventletDNS解析阻塞。
解决:临时关闭DNS服务sudo systemctl stop systemd-resolved,或在/etc/resolv.conf中将nameserver改为8.8.8.8。
4.2 现象:h1 ping h2显示Destination Host Unreachable,但ovs-ofctl show s1显示端口正常
原因:Mininet启动时未指定--protocols=OpenFlow13,交换机默认使用OF1.0,而Ryu控制器只监听OF1.3。
解决:启动命令必须显式添加--protocols=OpenFlow13,或在topo/lab2.py中为OVSSwitch类添加protocols='OpenFlow13'参数。
4.3 现象:_handle_packet_in被触发,但ev.msg.data为空字节串b''
原因:ev.msg是OFPPacketIn消息对象,data字段仅在buffer_id=0xffffffff(即packet未被缓存)时有效;若buffer_id有效,则data为空,需通过ev.msg.buffer_id向交换机请求重发。
解决:在_handle_packet_in开头加判断:
if ev.msg.buffer_id == 0xffffffff: pkt = packet.Packet(ev.msg.data) else: # 需发送OFPGetConfigRequest获取buffered packet,lab2中可忽略此分支 return4.4 现象:流表下发成功,但h1发给h2的ARP请求始终得不到响应
原因:Lab2拓扑中h1和h2不在同一子网(默认10.0.0.0/24),h1先发ARP问10.0.0.2的MAC,但控制器未处理ARP广播,导致h2收不到。
解决:在_handle_packet_in中增加ARP透传规则:
if eth.ethertype == ether_types.ETH_TYPE_ARP: actions = [parser.OFPActionOutput(ofproto.OFPP_FLOOD)] self.add_flow(datapath, 2, match, actions) # priority=2,高于泛洪规则 out = parser.OFPPacketOut(datapath=datapath, buffer_id=buffer_id, in_port=in_port, actions=actions, data=data) datapath.send_msg(out) return4.5 现象:修改lab2_controller.py后重启ryu-manager,旧流表仍存在,新规则不生效
原因:OpenFlow交换机流表不会因控制器断连而清空,ryu-manager重启后未发送OFPFlowMod删除旧流表。
解决:每次修改控制器前,先清空交换机流表:
sudo ovs-ofctl del-flows s1 # 删除所有流 # 或指定table:sudo ovs-ofctl del-flows s1 table=05. 进阶技巧:用Ryu REST API动态注入流表,绕过Python代码重写瓶颈
当实验进入Lab4(QoS策略)或Lab5(防火墙规则)阶段,频繁修改Python控制器代码、重启服务、等待Mininet重建拓扑会极大拖慢迭代速度。此时应转向Ryu的ofctl_rest应用,通过HTTP接口实时下发流表——这才是工业级SDN调试的常态。
5.1 启用REST API:三步激活ofctl_rest
原作业包通常未启用该模块,需手动修改:
- 在
ryu/app/lab2_controller.py顶部添加:from ryu.app import rest_nw from ryu.app import ofctl_rest - 在
@set_ev_cls装饰器下方添加:@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath = ev.msg.datapath # 原有逻辑... # 新增:启动REST API self._start_rest_api() - 启动控制器时追加模块:
ryu-manager ryu/app/lab2_controller.py ryu.app.ofctl_rest
启动后访问http://127.0.0.1:8080/stats/switches应返回[1](交换机DPID),证明API就绪。
5.2 流表注入实战:用curl下发一条限速1Mbps的流
假设要限制h1→h2的TCP流量(端口80)不超过1Mbps,传统做法需在控制器中写OFPActionSetQueue并配置QoS队列——但用REST API可秒级完成:
# 1. 先查交换机DPID(假设为1) curl -X GET http://127.0.0.1:8080/stats/switches # 2. 查端口映射(确定h1连s1的端口是1,h2连s1的端口是2) curl -X GET http://127.0.0.1:8080/stats/portdesc/1 # 3. 下发限速流表(OpenFlow1.3语法) curl -X POST -d '{ "dpid": 1, "cookie": 0, "cookie_mask": 0, "table_id": 0, "priority": 100, "flags": 1, "match": { "in_port": 1, "eth_type": 2048, "ip_proto": 6, "tcp_dst": 80 }, "actions": [ {"type":"QUEUE", "port":2, "queue_id":1} ] }' http://127.0.0.1:8080/stats/flowentry/add注意:
QUEUEaction要求交换机已配置队列,否则报错。实际中先用ovs-vsctl set port s1-eth2 qos=@newqos -- --id=@newqos create qos type=linux-htb other-config:max-rate=1000000设置端口队列。
5.3 故障自检表:REST API常见返回码与含义
| HTTP状态码 | 响应体示例 | 排查方向 |
|---|---|---|
200 OK | {"result":"success"} | 成功,检查ovs-ofctl dump-flows s1确认流存在 |
400 Bad Request | {"error":"Invalid dpid"} | DPID格式错误(应为十进制整数,非十六进制字符串) |
404 Not Found | {"error":"Switch not found"} | 交换机未连接或DPID错误,用stats/switches确认 |
500 Internal Server Error | {"error":"OFPFlowMod is invalid"} | match字段名拼写错误(如tp_dst写成tcp_dst),或actions格式非法 |
从那以后我每次改流表逻辑,都强制走一遍curl -X GET http://127.0.0.1:8080/stats/flowentry/1查当前流表,再对比ovs-ofctl dump-flows s1输出——双源验证比盯着Python日志猜强十倍。希望帮到你。
本文还有配套的精品资源,点击获取