资讯动态

Mininet 模拟 SDN 实验:OpenFlow 流表下发与控制器实战

发布时间:2026/10/9 10:31:28 来源:尧图企业网站定制
简介这份PDF面向高校研究生与网络方向学习者聚焦软件定义网络实验课程的教学设计难题如实验科目匮乏、硬件交换设备昂贵且难以规模化部署、环境灵活性不足、初学者上手门槛高等。作者结合Mininet轻量级模拟平台配合POX、Kinetic、Pyretic等控制器系统阐述SDN网络环境搭建、特定拓扑绘制、网络分割、防火墙编写等实验科目的设计思路并给出基础型、验证型、综合型三类共11个实验的模块化组织方案兼顾不同层次学生的个性化培养需求。资源包为单个PDF文件约174KB内容涵盖问题分析、设计方法、Mininet特性介绍与教学效果说明结构完整、条理清晰。目前已有104人学习适合SDN入门者、课程设计者及实验教学人员参考可帮助读者快速理解模拟环境下的实验体系搭建逻辑为后续动手实践与课程开发提供直接借鉴。1. 从一张 PDF 标题说起Mininet 模拟 SDN 到底能跑出什么名堂很多人第一次看到「基于 Mininet 模拟环境的软件定义网络实验课程设计」这个标题脑子里蹦出来的画面是装个虚拟机、敲几行命令、截几张图、交一份报告。真按这个思路做最后大概率是拓扑能起来但一问「控制器怎么下发流表」「链路断了流量怎么绕」就答不上来。Mininet 的价值不在于「模拟一个网络」而在于它把 SDN 里最核心的那条链路——控制器通过 OpenFlow 协议操控数据平面——压缩到一台笔记本上就能反复验证。你可以在里面跑通一个自定义转发逻辑抓包看到 Packet-In 消息改一条流表看吞吐变化这些在真实交换机上要排队申请资源的事在 Mininet 里几秒钟就能重来一次。这篇笔记面向的是要拿 Mininet 做 SDN 课程设计的人也适合想低成本验证 OpenFlow 应用的在职工程师。接下来我会按「环境怎么搭、拓扑怎么建、控制器怎么接、流表怎么调、坑怎么躲」的顺序把一套能复现的实验路径讲清楚。2. 环境搭建与版本选型为什么我劝你别用最新版2.1 Mininet 安装方式对比与选择理由Mininet 的安装方式直接决定了你后面调试的难度。常见做法有三种源码安装、apt 安装、以及官方提供的虚拟机镜像。源码安装最灵活能改内核模块参数但依赖 Python 版本和 Open vSwitch 的编译新手容易卡在configure阶段。apt 安装最省事apt install mininet一行搞定但 Ubuntu 仓库里的版本往往偏旧OpenFlow 协议支持可能停在 1.0 或 1.3 的早期实现。官方虚拟机镜像开箱即用但镜像里的工具链版本被锁死你想升级 Ryu 或 ONOS 时会遇到依赖冲突。我一般会推荐课程设计用 apt 安装 手动升级 Open vSwitch理由是这样你能看到版本号的变化知道哪个组件在起作用。如果你只是要快速验证一个控制器逻辑直接用官方镜像省掉环境折腾的时间。下面是一套在 Ubuntu 20.04 上验证过的安装流程。# 更新源并安装 Mininet 核心包 sudo apt update sudo apt install -y mininet mininet-doc # 检查 Mininet 版本确认是否可用 mn --version # 安装 Open vSwitch 用户态工具用于查看流表 sudo apt install -y openvswitch-switch openvswitch-common # 验证 OVS 版本OpenFlow 1.3 需要 OVS 2.3 以上 ovs-vsctl --version这段脚本的逻辑是先装 Mininet 本体再装 OVS 工具。mn --version输出的是 Mininet 的版本号不是 OpenFlow 版本。ovs-vsctl --version输出的第二行才是 OVS 版本它决定了你能否使用 OpenFlow 1.3 的group和meter特性。参数上唯一需要注意的是-y不能省否则安装过程会卡在交互确认。如果你在虚拟机里跑建议给至少 2GB 内存和 2 个 CPU 核心否则启动 10 个以上交换机时 OVS 进程会互相抢资源。2.2 控制器选型Ryu、ONOS、Floodlight 怎么挑控制器是 SDN 实验的大脑。Ryu 是 Python 写的代码量少适合自己改逻辑比如写一个带最短路径计算的自定义控制器。ONOS 是 Java 写的集群能力强适合做大规模拓扑实验但启动慢配置文件多。Floodlight 也是 Java 系REST API 比较全适合做北向接口实验。课程设计里最常被选的是 Ryu因为它的simple_switch_13.py例子几乎就是 OpenFlow 1.3 的教科书实现你可以在它基础上加自己的转发规则。选 Ryu 的另一个理由是调试方便。你可以在控制器代码里直接print出收到的 Packet-In 消息看到match字段和actions字段的原始结构。ONOS 的日志分散在多个 Karaf 日志文件里新手找一条流表下发记录要翻半天。下面是用 pip 安装 Ryu 的命令注意 Python 版本。# Ryu 对 Python 3.6 以上支持较好Ubuntu 20.04 默认 Python 3.8 sudo apt install -y python3-pip pip3 install ryu # 验证安装查看 Ryu 版本和可用模块 ryu --version ryu-manager --help | head -20pip3 install ryu会同时装上 eventlet 和 oslo.config 等依赖。ryu --version输出的是 Ryu 框架版本不是 OpenFlow 版本。ryu-manager --help列出的是所有可加载的 Ryu 应用模块比如ryu.app.simple_switch_13就是最常用的那个。如果你在安装时遇到eventlet超时换用国内 pip 源或者指定eventlet0.30.2这个较稳定的版本。3. 拓扑构建与 OpenFlow 通道打通从mn命令到流表下发3.1 用mn命令快速拉起一个可调试的拓扑Mininet 自带的mn命令能直接生成常见拓扑比如--topotree,2生成两层的树形拓扑--toposingle,4生成一个交换机挂四台主机。但课程设计往往需要自定义拓扑比如三个交换机环形连接或者一个交换机下挂不同网段的主机。这时候用 Python 脚本调 Mininet 的 API 更灵活。下面是一个自定义拓扑脚本创建一个线性三交换机拓扑每个交换机下挂一台主机并指定远程控制器地址。# custom_topo.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 Linear3Topo(Topo): def build(self): # 创建三台交换机 s1 self.addSwitch(s1) s2 self.addSwitch(s2) s3 self.addSwitch(s3) # 创建三台主机分别指定 IP 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) # 交换机之间线性连接 self.addLink(s1, s2) self.addLink(s2, s3) # 主机挂到对应交换机 self.addLink(h1, s1) self.addLink(h2, s2) self.addLink(h3, s3) if __name__ __main__: setLogLevel(info) topo Linear3Topo() # 指定远程控制器Ryu 默认监听 6633 和 6653 net Mininet(topotopo, controllerRemoteController, switchOVSKernelSwitch) net.start() CLI(net) # 进入交互式命令行方便手动测试 net.stop()这段代码的关键点有三个。第一RemoteController告诉 Mininet 不要启动自带的ovs-controller而是去连接外部控制器。第二OVSKernelSwitch表示使用内核态的 Open vSwitch性能比用户态好但需要 OVS 内核模块已加载。第三CLI(net)让你进入一个类似mininet的提示符可以在里面执行h1 ping h2、sh ovs-ofctl dump-flows s1等命令。参数上ip10.0.0.1/24里的/24不能省否则主机不会自动配置路由。运行这个脚本之前先在一个终端启动 Ryu 控制器# 启动 Ryu 的简单交换机应用监听 6653 端口 ryu-manager ryu.app.simple_switch_13然后在另一个终端运行sudo python3 custom_topo.py。注意必须用sudo因为 Mininet 要创建网络命名空间和虚拟网卡。启动后你会看到s1、s2、s3依次连接控制器每个交换机上会打印connected状态。3.2 验证 OpenFlow 通道ovs-ofctl与dump-flows的用法拓扑起来之后第一件事是确认交换机和控制器的 OpenFlow 通道真的通了。在 Mininet 的 CLI 里执行sh ovs-ofctl show s1你会看到类似下面的输出OFPT_FEATURES_REPLY (xid0x2): dpid:0000000000000001 n_tables:254, n_buffers:256 capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP actions: output enqueue set_vlan_vid set_vlan_pcp strip_vlan mod_dl_src mod_dl_dst mod_nw_src mod_nw_dst mod_tp_src mod_tp_dst 1(s1-eth1): addr:... config: 0 state: 0 2(s1-eth2): addr:... config: 0 state: 0dpid是交换机的唯一标识n_tables:254表示支持多级流表。如果这里显示dpid全零或者端口列表为空说明交换机没有正确连接到 OVS 数据库。常见原因是 OVS 服务没启动或者ovs-vsctl show里看不到网桥。接着执行sh ovs-ofctl dump-flows s1你会看到控制器下发的默认流表。Ryu 的simple_switch_13会下发一条table0, priority0, actionsCONTROLLER的兜底规则意思是所有未知流量都上送控制器。当你执行h1 ping h2时控制器会收到 Packet-In然后计算路径并下发新的流表项。你可以再次dump-flows看到新增的priority1规则匹配ip,nw_src10.0.0.1,nw_dst10.0.0.2动作是output:2。这里有一个容易翻车的点ovs-ofctl命令必须在 Mininet 的 CLI 里用sh前缀执行或者先sudo su进入 root 再执行。直接在你的普通终端里敲ovs-ofctl dump-flows s1会报s1不存在因为s1是 Mininet 创建的虚拟网桥只在 Mininet 的命名空间里可见。4. 流表编程与转发逻辑验证让数据包按你的规则走4.1 写一个自定义 Ryu 应用从 Packet-In 到 Flow-ModRyu 的simple_switch_13只做了最基本的 MAC 学习转发。课程设计里通常要求你实现更复杂的逻辑比如基于 IP 的转发、基于端口的阻断、或者带 VLAN 标签的隔离。下面是一个自定义 Ryu 应用实现「只允许 h1 和 h2 通信h3 的流量全部丢弃」的访问控制。# acl_switch.py from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib.packet import packet, ethernet, ipv4 class ACLSwitch(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(ACLSwitch, self).__init__(*args, **kwargs) self.mac_to_port {} set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath ev.msg.datapath ofproto datapath.ofproto parser datapath.ofproto_parser # 默认流表所有未知流量上送控制器 match parser.OFPMatch() actions [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath, 0, match, actions) def add_flow(self, datapath, priority, match, actions, buffer_idNone): ofproto datapath.ofproto parser datapath.ofproto_parser inst [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] if buffer_id: mod parser.OFPFlowMod(datapathdatapath, buffer_idbuffer_id, prioritypriority, matchmatch, instructionsinst) else: mod parser.OFPFlowMod(datapathdatapath, prioritypriority, matchmatch, instructionsinst) datapath.send_msg(mod) set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): msg ev.msg datapath msg.datapath ofproto datapath.ofproto parser datapath.ofproto_parser in_port msg.match[in_port] pkt packet.Packet(msg.data) eth pkt.get_protocol(ethernet.ethernet) ip_pkt pkt.get_protocol(ipv4.ipv4) # 只处理 IPv4 包其他丢弃 if not ip_pkt: return src_ip ip_pkt.src dst_ip ip_pkt.dst # 访问控制h3 的 IP 是 10.0.0.3禁止它参与通信 if src_ip 10.0.0.3 or dst_ip 10.0.0.3: # 下发一条丢弃流表优先级高于默认流表 match parser.OFPMatch(eth_type0x0800, ipv4_srcsrc_ip, ipv4_dstdst_ip) self.add_flow(datapath, 100, match, []) # 空动作表示丢弃 return # 正常转发学习源 MAC 和端口 self.mac_to_port.setdefault(datapath.id, {}) self.mac_to_port[datapath.id][eth.src] in_port if eth.dst in self.mac_to_port[datapath.id]: out_port self.mac_to_port[datapath.id][eth.dst] else: out_port ofproto.OFPP_FLOOD actions [parser.OFPActionOutput(out_port)] match parser.OFPMatch(in_portin_port, eth_dsteth.dst) self.add_flow(datapath, 10, match, actions) # 把当前包也转发出去 out parser.OFPPacketOut(datapathdatapath, buffer_idmsg.buffer_id, in_portin_port, actionsactions, datamsg.data) datapath.send_msg(out)这段代码的核心逻辑在packet_in_handler里。当控制器收到 Packet-In 时先解析出 IP 层如果源或目的 IP 是10.0.0.3就下发一条空动作的流表优先级设为 100这样后续匹配到的包直接在交换机里被丢弃不会再上送控制器。对于正常流量用mac_to_port做 MAC 学习然后下发priority10的转发流表。参数上priority越大越优先OFPP_FLOOD表示泛洪OFPCML_NO_BUFFER表示不缓存整个包只上送包头。启动这个应用用ryu-manager acl_switch.py然后重新运行拓扑脚本。在 Mininet CLI 里执行h1 ping h2应该通h1 ping h3应该不通。你可以用sh ovs-ofctl dump-flows s1看到两条流表一条priority100的丢弃规则一条priority10的转发规则。4.2 用iperf和ping验证转发性能与连通性流表下发之后需要验证实际转发效果。ping只能告诉你通不通iperf能告诉你带宽和丢包。在 Mininet CLI 里先让 h2 启动 iperf 服务端h2 iperf -s 然后在 h1 上跑客户端h1 iperf -c 10.0.0.2 -t 10 -i 1你会看到每秒的带宽报告。如果带宽远低于预期比如只有几 Mbps常见原因是 OVS 走了用户态转发或者流表没有正确命中导致每个包都上送控制器。检查方法是sh ovs-ofctl dump-flows s1看有没有n_packets计数在增长。如果所有包都走priority0的默认流表说明你的自定义流表没有匹配上需要检查match字段里的eth_type和ipv4_src是否写对。另一个验证手段是sh ovs-ofctl dump-ports s1看每个端口的rx和tx字节数。如果rx有增长但tx为零说明包被丢弃了对应你下的丢弃流表。这个命令在排查「为什么 ping 不通」时特别有用能快速定位是入口没收到还是出口没发出去。5. 避坑与排查Mininet 实验里最容易翻车的五个地方5.1 现象mn启动时报Unable to contact the remote controller原因Ryu 控制器没有启动或者监听端口不是 Mininet 默认连接的 6653。Mininet 的RemoteController默认连127.0.0.1:6653而 Ryu 默认监听6633和6653两个端口但如果你用--ofp-tcp-listen-port改了端口两边就对不上。解决先确认 Ryu 进程在跑ps aux | grep ryu能看到ryu-manager。然后确认端口netstat -tlnp | grep 6653应该显示LISTEN。如果端口不对在拓扑脚本里指定controllerRemoteController(c0, ip127.0.0.1, port6653)。5.2 现象h1 ping h2显示Destination Host Unreachable原因主机 IP 配错了网段或者 ARP 请求没有被正确转发。Mininet 里主机默认在同一个子网但如果你的addHost里写了ip10.0.0.1/24而另一台是ip10.0.1.2/24它们就不在同一网段ARP 广播会被交换机丢弃。解决用h1 ifconfig和h2 ifconfig确认 IP 和掩码。如果网段不同要么改 IP要么在控制器里加路由逻辑。另外检查sh ovs-ofctl dump-flows s1里有没有 ARP 相关的流表Ryu 的simple_switch_13会把 ARP 广播泛洪但如果你的自定义应用只处理 IPv4ARP 包会被丢弃导致 ping 不通。5.3 现象ovs-ofctl dump-flows输出为空原因交换机没有连接到控制器或者 OVS 数据库没有正确配置。Mininet 启动时会自动创建网桥并设置fail-modesecure但如果 OVS 服务在 Mininet 启动后才启动网桥可能没有连上控制器。解决在 Mininet CLI 里执行sh ovs-vsctl show看每个网桥的Controller字段是否指向tcp:127.0.0.1:6653。如果是空的手动加上sh ovs-vsctl set-controller s1 tcp:127.0.0.1:6653。然后sh ovs-ofctl dump-flows s1应该能看到默认流表。5.4 现象iperf带宽只有几 Mbps远低于链路容量原因流表没有命中每个包都上送控制器控制器处理速度成为瓶颈。或者 OVS 使用了用户态 datapath没有走内核模块。解决先sh ovs-ofctl dump-flows s1看n_packets计数。如果默认流表的计数很大说明自定义流表没生效。检查match字段是否包含了in_port和eth_dst这两个字段在 MAC 学习转发里是必须的。另外确认ovs-vsctl get Open_vSwitch . datapath_type返回的是system而不是netdevnetdev是用户态性能差很多。5.5 现象重启 Mininet 后流表丢失需要重新下发原因Mininet 每次启动都会创建新的 OVS 网桥旧的流表随网桥删除而消失。这是正常行为不是 bug。解决如果你需要持久化流表可以在拓扑脚本里用net.start()之后手动调用ovs-ofctl add-flow命令或者把流表下发逻辑写进 Ryu 应用的switch_features_handler里这样每次交换机连接时自动下发。不要试图用ovs-vsctl save保存流表Mininet 的网桥是临时创建的保存了也恢复不了。6. 进阶技巧用mininet的link命令模拟链路故障与恢复课程设计里如果只做静态转发很难体现 SDN 的「可编程」优势。一个能加分的实验是模拟链路中断观察控制器如何重新计算路径并下发新流表。Mininet 提供了link命令可以在运行时改变链路状态。在 Mininet CLI 里先确认拓扑是线性的h1-s1-s2-s3-h3。执行h1 ping h3应该通。然后执行link s2 s3 down这条命令会把 s2 和 s3 之间的链路断开。此时h1 ping h3应该超时。如果你用的控制器有最短路径计算逻辑比如 Ryu 的simple_switch_13不带但你可以自己加控制器会收到端口状态变化消息然后重新计算路径。对于线性拓扑s2 和 s3 之间只有一条链路断了就没有备用路径所以 ping 不通是正常的。但你可以观察sh ovs-ofctl dump-flows s2里流表的变化看控制器有没有下发新的丢弃规则或者修改动作。如果你想验证路径恢复执行link s2 s3 up链路恢复后控制器应该重新下发转发流表h1 ping h3恢复连通。这个过程中你可以用sh ovs-ofctl dump-flows s2前后对比看到流表的n_packets计数从增长到停止再到增长。更进阶的做法是写一个带 Dijkstra 算法的 Ryu 应用维护全局拓扑图当收到EventOFPPortStatus消息时更新图并重新计算所有主机对之间的路径。这个逻辑大概需要 200 行 Python 代码核心是监听EventOFPPortStatus和EventOFPSwitchFeatures用networkx库做图计算。我一般会建议先跑通静态转发再加动态路径计算否则调试时会分不清是拓扑问题还是算法问题。最后说一个我踩过的坑Mininet 的link down命令只是把端口状态设为down但 OVS 流表里已有的output动作不会自动失效。如果控制器没有及时下发新流表数据包会被发到一个已经 down 的端口然后被内核丢弃。你会在ovs-ofctl dump-ports里看到tx计数在涨但rx在另一端不涨。这时候需要检查控制器有没有处理EventOFPPortStatus以及流表的idle_timeout是否设置得太长。我习惯把转发流表的idle_timeout设为 30 秒这样即使控制器没反应过来旧流表也会自动过期避免流量黑洞。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑