简介这份资源面向计算机、通信工程、自动化等专业的在校学生与教师以及希望进阶SDN技术的企业员工提供一套中小型SDN园区网络构建的完整项目源码与文档说明。项目基于Ubuntu系统与Mininet搭建拓扑涵盖SDN控制器联通、区分子网、NAT外网访问、防火墙与访问控制列表等安全配置并考虑设备冗余与鲁棒性问题适合课程设计、毕业设计或项目初期立项演示。压缩包共24个文件约170KB包含6个conf配置文件、5个py脚本、4个txt说明、4个sh脚本及pyc、jpg、md等辅助文件目录分为主干网络与分支网络模块结构清晰。目前已有153人学习下载。下载后可按README.md指引运行代码均经测试遇到问题可私聊获取远程教学也可在现有基础上修改扩展功能。1. 从一台 Ubuntu 虚拟机说起这套 SDN 园区网络源码到底能跑出什么很多人第一次接触 SDN卡在的不是 OpenFlow 协议本身而是「我到底该拿什么拓扑练手」。真实园区网动辄几十台交换机、划分 VLAN、跑 OSPF、还要接外网实验室里根本凑不齐。这套中小型SDN园区网络构建的思路就很务实在 Ubuntu 上用 Mininet 把拓扑虚拟出来用 SDN 控制器统一下发流表再叠加 NAT、ACL、子网划分这些园区网必备能力。它不是一个纯理论 demo而是把「分支网络 主干网络」两段结构都搭出来了配套code、配置文件、README.md和SDN-NET-main主目录。适合谁计科、通信、自动化的在校生做课设毕设或者刚转 SDN 方向、想找一个能跑通的完整案例的工程师。下面我按自己拆包复现的顺序把拓扑、控制器、NAT、安全策略和排错一条条讲清楚。2. 拓扑与运行环境Mininet 里怎么把分支和主干拼起来2.1 为什么选 Mininet 而不是真机或 EVE-NG园区网络实验有三条路买真机、用 EVE-NG 这类仿真平台、或者 Mininet。真机成本高EVE-NG 更偏传统网络设备镜像跑 SDN 控制器对接 OpenFlow 反而绕。Mininet 的优势是它本身就是为 SDN 教学设计的基于 Linux 网络命名空间一台 Ubuntu 虚拟机就能虚拟出几十个 host 和 switch而且和 Ryu、ONOS、Floodlight 这些控制器对接是原生支持。这套源码选 Mininet 是合理的。它要验证的核心不是「交换机转发性能」而是「控制器能不能正确下发流表、子网能不能隔离、NAT 能不能通外网」。这些在 Mininet 里全都能验证而且改拓扑只要改 Python 脚本几行代码。常见做法是装 Ubuntu 20.04 或 22.04Mininet 直接用 apt 装控制器用 Ryu纯 Python和项目语言一致改起来顺手。2.2 环境搭建从裸 Ubuntu 到能跑 Mininet先确认系统版本和 Python 环境。项目是 Python 写的Mininet 自带 Python2 依赖但现在基本都用 Python3装的时候注意别把两个版本搞混。# 更新源并安装 Mininet 及依赖 sudo apt update sudo apt install -y mininet python3-pip git # 验证 Mininet 是否装好这条命令会起一个最简单的单交换机拓扑 sudo mn --test pingall--test pingall是关键验证步骤它会自动创建 2 个 host 挂 1 个 switch然后互相 ping。如果输出Results: 0% dropped说明 Mininet 本体没问题。如果报Unable to contact the remote controller那是控制器没起不是 Mininet 的锅先别慌。接着装 Ryu 控制器pip3 install ryu # 验证版本Ryu 4.x 以上对 Python3 支持较好 ryu-manager --version这里有个血泪经验pip3 install ryu有时会拉到一个很老的版本和 Python3.10 不兼容报ImportError: cannot import name collections。解决办法是显式指定版本比如pip3 install ryu4.34。装完先跑一个自带示例确认控制器能起来ryu-manager ryu.app.simple_switch_13看到loading app ryu.app.simple_switch_13和instantiating app就说明控制器正常。这一步别跳过很多人后面拓扑起不来其实是控制器根本没跑起来。2.3 拓扑脚本拆解分支网络和主干网络怎么连项目里SDN-NET-main下的拓扑脚本是核心。中小型园区网的典型结构是一个主干核心层下面挂若干分支接入层每个分支有自己的子网。用 Mininet 的 Python API 写出来大概是这样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 CampusTopo(Topo): def build(self): # 主干核心交换机 core self.addSwitch(s1) # 两个分支接入交换机 br1 self.addSwitch(s2) br2 self.addSwitch(s3) # 核心连分支 self.addLink(core, br1) self.addLink(core, br2) # 分支1下挂两台主机网段 10.0.1.0/24 h1 self.addHost(h1, ip10.0.1.1/24) h2 self.addHost(h2, ip10.0.1.2/24) self.addLink(br1, h1) self.addLink(br1, h2) # 分支2下挂两台主机网段 10.0.2.0/24 h3 self.addHost(h3, ip10.0.2.1/24) h4 self.addHost(h4, ip10.0.2.2/24) self.addLink(br2, h3) self.addLink(br2, h4) if __name__ __main__: setLogLevel(info) topo CampusTopo() # 指向本机 Ryu 控制器默认 6633 端口 net Mininet(topotopo, controllerRemoteController, switchOVSKernelSwitch) net.start() CLI(net) net.stop()逻辑说明addSwitch创建 Open vSwitch 实例addLink建立链路addHost时直接指定 IP 和掩码就完成了子网划分。两个分支用不同网段10.0.1.0/24 和 10.0.2.0/24这是后面验证「子网隔离」的基础。RemoteController表示交换机不自己当控制器而是连到外部 Ryu。参数上要注意Ryu 默认监听 6633但新版 OpenFlow 常用 6653如果连不上在RemoteController里加port6653同时启动 Ryu 时确认监听端口一致。启动顺序必须是「先起控制器再起拓扑」反了的话交换机会反复重连日志刷屏。跑起来后进 Mininet CLI先pingall看全通不通。如果分支间能通、分支内也能通说明基础转发没问题。但这时候所有流量都是控制器 flood 下来的还没做子网隔离下一步才是重点。3. 控制器逻辑与子网隔离流表怎么下发才不打架3.1 Ryu 应用的基本骨架Ryu 应用本质是一个继承app_manager.RyuApp的类核心是监听EventOFPPacketIn事件在里面决定怎么处理包。项目里控制器代码要干三件事学习 MAC 地址、按子网下发流表、对跨子网流量做限制或转发。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 CampusController(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(CampusController, 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): ofproto datapath.ofproto parser datapath.ofproto_parser inst [parser.OFPInstructionActions( ofproto.OFPIT_APPLY_ACTIONS, actions)] mod parser.OFPFlowMod(datapathdatapath, prioritypriority, matchmatch, instructionsinst) datapath.send_msg(mod)switch_features_handler在交换机连上控制器时触发下发一条优先级为 0 的默认流表把所有包 punt 给控制器。add_flow是通用下发函数priority越大越优先后面做子网隔离就靠不同优先级叠加。3.2 子网隔离靠 IP 前缀匹配下发 drop 流表园区网里不同部门、不同分支通常要隔离。在 SDN 里实现隔离比传统 VLAN 更直接——控制器识别源和目的 IP跨子网就 drop 或走网关。核心是在packet_in里解析 IP 头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.get_protocol(ipv4.ipv4) # 学习源 MAC 到端口的映射 self.mac_to_port.setdefault(datapath.id, {}) self.mac_to_port[datapath.id][eth.src] in_port # 跨子网流量源和目的不在同一 /24直接丢弃 if ip: src_subnet ip.src.rsplit(., 1)[0] dst_subnet ip.dst.rsplit(., 1)[0] if src_subnet ! dst_subnet: match parser.OFPMatch(eth_type0x0800, ipv4_srcip.src, ipv4_dstip.dst) self.add_flow(datapath, 100, match, []) # 空 actions drop return # 同子网查目的 MAC找到就转发找不到就 flood 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)] # 下发一条高优先级流表后续同流直接硬件转发 if out_port ! ofproto.OFPP_FLOOD: 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)逻辑说明rsplit(., 1)[0]取 IP 前三段做子网判断简单但够用。跨子网时下发一条优先级 100、actions 为空的流表等于永久 drop比每次 packet_in 再判断效率高。同子网则学习 MAC 后下发优先级 10 的转发流表。参数上优先级设计要有层次默认 0、转发 10、隔离 100。如果后面要加 NAT 或 ACL优先级再往上叠别让它们互相覆盖。这里有个容易翻车的点ipv4_src和ipv4_dst匹配要求eth_type0x0800漏了这个字段流表匹配不上隔离就失效了。3.3 验证隔离是否真的生效起好控制器和拓扑后在 Mininet CLI 里测# 同子网应该通 mininet h1 ping -c 2 h2 # 跨子网应该不通被 drop mininet h1 ping -c 2 h3如果 h1 到 h3 还能通先查控制器日志有没有收到 packet_in再sudo ovs-ofctl dump-flows s1看流表里有没有那条 drop 规则。常见原因是优先级写反了或者ipv4_src匹配的 IP 带了掩码导致匹配不上。dump-flows 是排查流表问题的黑匣子养成每次改完控制器就 dump 一遍的习惯。4. NAT 与外网联通让园区网里的主机能出去4.1 SDN 场景下 NAT 放在哪一层传统园区网 NAT 一般做在出口路由器或防火墙上。SDN 里有两个选择一是在控制器里用 OpenFlow 改写 IP 头做 NAT二是把 NAT 交给 Linux 内核的 iptables控制器只负责转发。项目里更可能是后者因为纯 OpenFlow 做 NAT 要处理 IP 和端口改写复杂度高教学场景没必要。常见做法是Mininet 拓扑里加一个代表「外网」的 host 或直接桥接到宿主机网卡然后在核心交换机对应的 Linux 网桥上配 iptables MASQUERADE。这样内网主机出外网时源 IP 被改写回包也能正确回来。4.2 配置 NAT 的具体步骤先确认 Mininet 的网桥名称通常是s1对应的s1-ethX。假设核心交换机 s1 有一个端口连到外网侧# 开启内核转发 sudo sysctl -w net.ipv4.ip_forward1 # 假设外网接口是 eth0内网网段是 10.0.0.0/16 sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/16 -o eth0 -j MASQUERADE sudo iptables -A FORWARD -i eth0 -o s1-eth1 -m state \ --state RELATED,ESTABLISHED -j ACCEPT sudo iptables -A FORWARD -i s1-eth1 -o eth0 -j ACCEPT逻辑说明MASQUERADE做源地址转换FORWARD链放行已建立连接的回包和新发起的出向包。-s 10.0.0.0/16覆盖两个分支子网。参数上-o eth0要换成你宿主机真实的外网网卡名用ip addr查。如果外网侧是 Mininet 里的一个 host那更简单直接在那个 host 上起个 HTTP 服务内网主机 curl 它验证连通。但要注意 Mininet 默认 host 之间是隔离的跨网段访问需要控制器放行对应流表别和前面的隔离规则冲突——隔离规则是 drop 跨子网NAT 场景要放行去往外网网关的流量优先级要理清。4.3 验证外网联通与常见失败点# 在 Mininet host 里测试 mininet h1 ping -c 2 8.8.8.8 mininet h1 curl http://example.comping 不通先分三段查h1 到网关通不通、网关有没有做 NAT、宿主机本身能不能出网。iptables -t nat -L -n -v看 MASQUERADE 规则的包计数有没有涨涨了说明流量到了 NAT 这层没涨说明前面转发就没通。这一步的坑在于 Mininet 重启后网桥名可能变iptables 规则里写死的接口名就失效了建议写成脚本每次启动后重新应用。5. 避坑与排查复现这套源码最容易翻车的五个地方5.1 控制器起了但交换机连不上现象拓扑启动后pingall全丢包控制器日志没有任何 packet_in。原因通常是控制器监听端口和拓扑里RemoteController指定的端口不一致或者控制器根本没起。解决先ryu-manager --version确认能跑再ss -tlnp | grep 6633看端口有没有监听拓扑里显式写RemoteController(127.0.0.1, port6633)别依赖默认值。5.2 子网隔离规则不生效现象跨子网还能 ping 通。原因是流表匹配字段不全比如漏了eth_type0x0800或者优先级低于转发流表被覆盖。解决sudo ovs-ofctl dump-flows s1看有没有那条 drop 规则没有就是没下发成功有但没生效就检查优先级数值隔离规则必须比转发规则高。5.3 Mininet 重启后 iptables 规则丢失现象第一次配好 NAT 能通重启虚拟机后外网又不通了。原因是 iptables 规则没持久化且 Mininet 网桥名可能变化。解决把 NAT 配置写成 shell 脚本在拓扑启动后执行接口名用变量动态获取别写死。5.4 Python 版本冲突导致 Ryu 起不来现象pip3 install ryu后运行报ImportError或SyntaxError。原因是拉到了不兼容 Python3 的老版本。解决显式装ryu4.34或者用虚拟环境隔离依赖别和系统 Python 混用。5.5 拓扑脚本里 IP 和掩码写错导致子网判断失效现象隔离逻辑时灵时不灵。原因是addHost时 IP 写成10.0.1.1/24但控制器里用rsplit取前三段如果掩码不是 /24前三段判断就不准。解决要么统一用 /24要么在控制器里用ipaddress库按真实掩码算网段别图省事用字符串切分。6. 进阶玩法把静态流表换成带状态的 ACL 和流量统计跑通基础版之后这套源码最值得改的地方是安全策略。前面 ACL 是静态 drop实际园区网需要更细的规则比如「允许分支1访问分支2的 80 端口其他全禁」。在 Ryu 里可以结合ipv4_proto和tcp_dst匹配# 只放行 h1 访问 h3 的 80 端口其余跨子网流量 drop match_allow parser.OFPMatch( eth_type0x0800, ipv4_src10.0.1.1, ipv4_dst10.0.2.1, ip_proto6, tcp_dst80) self.add_flow(datapath, 200, match_allow, actions)优先级 200 高于隔离的 100这样特定流量先匹配放行其余跨子网还是被 drop。参数上ip_proto6是 TCPtcp_dst80是目的端口要放行多个端口就下发多条。再进一步是流量统计。Ryu 可以周期性发OFPFlowStatsRequest读回每条流表的包数和字节数用来做简单的流量监控def request_stats(self, datapath): parser datapath.ofproto_parser req parser.OFPFlowStatsRequest(datapath) datapath.send_msg(req)拿到 stats 后按match字段分类就能看出哪个子网流量大、有没有异常。验证方法很简单在 h1 上iperf打流看控制器打印的字节数有没有涨涨了就说明统计链路通了。我自己的习惯是每次改完控制器逻辑先dump-flows确认流表符合预期再pingall加iperf双重验证最后才去看统计。这套顺序能挡掉八成「以为改对了其实没生效」的情况。这套源码的价值不在代码多复杂而在于它把园区网该有的环节都串起来了你可以在它上面加 VLAN 模拟、加 QoS、加多控制器改坏了重跑一遍拓扑就行成本极低。希望帮到你。本文还有配套的精品资源点击获取