简介:Floodlight 是一款基于 Java 语言的开源 SDN 控制器,以稳定性、易用性和完全开源著称,适合网络研究者、开发者及 SDN 爱好者用于搭建和学习软件定义网络。资源为 zip 压缩包,约 64.72MB,共包含 0 个文件,文件类型明细暂无数据,因此难以具体说明类型分布。目前已有 554 人学习浏览,说明其在 SDN 入门与部署场景中具备一定参考价值。包内主要内容围绕 Floodlight 的安装部署展开,涵盖环境准备、依赖配置、启动流程等关键环节,尤其针对 Ubuntu 系统给出了较为完整的操作说明。读者可借助该资源快速理解控制器在 SDN 架构中的角色,掌握从零开始部署 Floodlight 的基本方法,为后续开展网络实验、控制器二次开发或 SDN 应用创新打下基础。适合初次接触 SDN 控制器、希望以 Floodlight 为起点进行动手实践的读者参考使用。
1. Floodlight 控制器:为什么研究 SDN 的人都绕不开这个 Java 写的控制器
Floodlight 这个词在 SDN 实验语境里出现频率极高,但很多人第一次跑通它并不是因为文档读得顺,而是因为踩了足够多的坑。它本质是一个基于 Java 的 OpenFlow 控制器,由 Big Switch Networks 开源,代码托管在 GitHub。和 Ryu 的 Python 风格、ONOS 的重型分布式设计相比,Floodlight 的长处是模块化清晰:核心服务、应用模块、REST API 边界分明,拿来学控制器原理、做二层转发实验或者验证 OpenFlow 流程都非常合适。如果你正在写网络方向的课程设计,或者要搭一个能快速验证控制器行为的仿真环境,从 Floodlight 起步是比从零造控制器更务实的选择。
2. Floodlight 的架构与模块化:模块加载与事件链的底层逻辑
2.1 模块框架:IModule、FloodlightModuleContext 与服务依赖
拿到 Floodlight 源码后不要急着编译,先花十分钟把目录结构认一遍。src/main/java下的net.floodlightcontroller包大致分两块:core子包放的是控制器核心服务,比如OFSwitchManager管交换机连接、TopologyManager管拓扑发现、RestApiServer管 HTTP 服务;core外面这些子包是各类功能模块,比如forwarding、firewall、staticentry。模块之间不直接依赖具体类,而是依赖服务接口,这是 Floodlight 能保持模块化的关键。
所有模块都必须实现IFloodlightModule接口。这个接口定义了几个核心方法:getModuleServices()返回本模块对外提供的服务实现类,getServiceImpls()返回服务实例,getModuleDependencies()声明需要哪些其他服务,init()和startUp()分别是初始化和启动钩子。模块实例统一存放在FloodlightModuleContext上下文里,别的模块拿到这个上下文后,通过context.getServiceImpl(IFooService.class)就能获得某个服务的实例,而不必关心它来自哪个模块。
这里有个新手容易绕晕的点:getModuleServices()和getModuleDependencies()方向完全相反。前者是“我能给别人提供什么”,后者是“我需要别人提供什么才能跑”。如果你的模块要处理 PacketIn 消息,就必须依赖IFloodlightProviderService(或较新版本里的IOFMessageListenerService),否则拿不到消息源。拿一个最精简的模块骨架来说:
public class MyModule implements IFloodlightModule { @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { return Collections.emptyList(); } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { return Collections.emptyMap(); } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { return Collections.singletonList(IFloodlightProviderService.class); } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { log.info("MyModule init"); } @Override public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { log.info("MyModule startUp"); } }这段代码的逻辑是:声明这个模块不提供任何对外服务(前两个方法返回空集合),但依赖IFloodlightProviderService(第三个方法)。init里适合做配置读取,startUp里适合做监听器注册或启动定时任务。参数上注意getModuleDependencies()返回的集合不能是 null,否则框架加载模块清单时会抛空指针,报错信息通常还很隐蔽,显示成NullPointerException at ModuleLoader,不少人在写第一个模块时在这里翻过车。
2.2 消息处理链:IOFMessageListener 与 Command 语义
Floodlight 把对交换机消息的处理做成了监听器链。想处理 PacketIn、PortStatus、FlowRemoved 这类 OpenFlow 消息,模块要实现IOFMessageListener接口,并在startUp里注册到消息监听服务。之后每个从交换机来的消息都会按注册顺序经过监听器,直到某个监听器返回Command.STOP。Command.CONTINUE表示继续传给下一个监听器,Command.STOP表示本模块已经处理完毕。
@Override public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { switch (msg.getType()) { case PACKET_IN: // 处理数据包上送 return Command.CONTINUE; default: return Command.CONTINUE; } }逻辑说明:IOFSwitch sw是消息来源交换机对象,可以通过它发 FlowMod 或获取端口信息;OFMessage msg是原始 OpenFlow 消息;FloodlightContext cntx是跨模块传数据的上下文容器。返回CONTINUE是最安全的写法,如果你确认这个模块已经做了完整决策,可以返回STOP阻止后面的模块重复处理。
这里的关键参数是返回的Command值。为什么不建议所有模块都返回STOP?因为链路发现模块需要看到所有 PacketIn 来记录端口和交换机的关系,防火墙模块可能要在转发决策前检查安全策略。如果你在链路发现模块之前就STOP,它收不到消息,拓扑图就永远画不出链路。理解这条链的顺序,后面排查“交换机已连上但拓扑不对”时会快很多。
2.3 REST API 与默认配置:两个必须掌握的入口
Floodlight 把运维入口也做成了模块。默认情况下,控制器启动后会在 8080 端口起 REST API,同时提供一个简单的 Web UI(/ui/)。常用端点如下:
| 端点 | 作用 | 返回内容示例 |
|---|---|---|
/wm/core/controller/switches/json | 查看已连接的交换机 | DPID、IPv4 地址列表 |
/wm/core/switch/all/port/json | 查看所有交换机端口 | 端口号、状态、速率 |
/wm/topology/links/json | 获取当前链路发现结果 | 源/目的 DPID 与端口 |
/wm/staticflowentrypusher/json | 下发静态流表 | 是否成功写入 |
/wm/firewall/rules/json | 查看防火墙规则 | 规则列表 |
排查问题时我会按顺序请求这几个端点:先看交换机在不在,再看端口状态,再看链路。这三个请求能覆盖八成“为什么不通”类问题。
配置方面,floodlightdefault.properties是主要的手动编辑入口。比较关键的参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
openflow.port | 6653 | OpenFlow 监听端口 |
openflow.bind.address | 0.0.0.0 | 控制器的监听地址 |
net.floodlightcontroller.restserver.RestApiServer.port | 8080 | REST API 端口 |
topology.llpdu-interval | 2000 | LLDP 探测间隔,单位毫秒 |
floodlight.modules | 全部默认模块 | 要加载的模块类名列表,逗号分隔 |
参数名在不同版本里可能略有出入,我一般不硬背,用grep -r "llpdu-interval" src/main/resources/搜一下再改。改这些参数不需要重新编译,重启控制器就会生效,这给实验迭代省了不少时间。第一次做实验建议只调floodlight.modules,把不用的模块去掉,其余保持默认,减少变量。
3. 把 Floodlight 跑起来:从编译到 Mininet 联调再到流表验证
3.1 环境准备与编译:先把地基打稳
Floodlight 用 Java 写,构建工具在推进过程中从 Ant 迁移到了 Gradle,现在主仓库默认用 Gradle 构建。安装环境时,我建议装 JDK 11 而不是 JDK 8。JDK 8 在跑新版 Gradle 时经常遇到版本不匹配的告警,与其折腾,不如直接上 JDK 11。如果系统里已经有多个 JDK,记得先确认java -version指向的是你期望的那个,否则后面编译出的 jar 可能跑在错误版本上。
# 克隆主仓库 git clone https://github.com/floodlight/floodlight.git cd floodlight # 编译并跳过单元测试 ./gradlew build -x test这段命令的逻辑:git clone拿到最新主分支代码;./gradlew build会自动下载 Gradle wrapper 指定的版本以及全部依赖,-x test跳过测试是为了抢时间。首次编译因为要拉依赖会比较慢,几分钟到十几分钟都可能,取决于网络。编译完成后,在build/libs/目录下会生成floodlight.jar,这就是可以直接运行的控制器。
如果在编译时遇到依赖下载失败,常见原因有三个:一是网络环境访问 Maven Central 不稳定,换成国内镜像源后在build.gradle里改repositories块;二是磁盘空间不够,Gradle 缓存默认放在~/.gradle,有时候会占好几个 GB;三是之前编译残留的旧产物冲突,此时执行./gradlew clean再重新 build。这些坑我基本都踩过,属于环境问题里最常见的一波。
3.2 启动控制器与检查监听状态
启动控制器比较简单,直接运行 jar:
java -jar build/libs/floodlight.jar正常启动后,日志里会依次出现模块加载、REST API 启动和 OpenFlow 监听端口就绪的输出。如果看到Exception堆栈,先看是不是端口被占用:
ss -lnp | grep -E "6653|8080"这条命令是查看 6653 和 8080 端口有没有进程监听。如果 6653 被其他进程占了(比如另一个控制器实例),Floodlight 会绑定失败;8080 被占用则会导致 REST API 起不来。启动时也可以把日志输出到文件,方便滚动查看:
java -jar build/libs/floodlight.jar > floodlight.log 2>&1 &后台运行并写日志,排查问题时不要只盯控制台,用tail -f floodlight.log能看到完整的时间线。这个习惯我一直保持,尤其是调试自定义模块时,日志里的时间戳能帮你确认事件发生的先后顺序。
启动成功后,用 curl 验证 REST API:
curl -s http://127.0.0.1:8080/wm/core/controller/switches/json如果返回的是空数组,说明交换机还没连上来,这是正常的,控制器刚启动时没有任何交换机连接,需要继续下一步的 Mininet 联调。
3.3 Mininet 联调:把虚拟拓扑接到控制器上
Mininet 是 Floodlight 最常见的实验搭档。建立拓扑时,关键是通过 OpenFlow 协议让虚拟交换机主动连到控制器的监听端口。一个典型的单交换机三主机命令:
sudo mn --controller=remote,ip=127.0.0.1,port=6653 --topo=single,3 --switch=ovsk,protocols=OpenFlow13参数说明:--controller=remote表示远程控制器模式,ip和port指定控制器的地址和 OpenFlow 端口;--topo=single,3表示一台交换机、三台主机;--switch=ovsk选择 Open vSwitch 作为虚拟交换机,protocols=OpenFlow13指定 OpenFlow 1.3。
启动完成后,在 Mininet CLI 执行net查看拓扑是否正确生成。然后在控制器日志里应该能看到新的 DPID 出现,或者再次请求/wm/core/controller/switches/json,数组里出现交换机信息。如果迟迟看不到交换机,一个快速检查手段是抓控制平面的包:
sudo tcpdump -i lo -p tcp port 6653注意 Mininet 如果跑在本机,流量走 loopback 接口,所以要监听lo。抓包时先看到 OFPT_HELLO,随后是 OFPT_FEATURES_REQUEST/REPLY,这两步代表握手成功。如果只看到 SYN 包没有后续,说明 OpenFlow 版本协商出了问题,多半是protocols=OpenFlow13和你拉取的 Floodlight 版本只支持 OpenFlow 1.0 不匹配。这时要么去掉protocols参数让 OVS 默认协商,要么换一个支持 OpenFlow 1.3 的分支。
3.4 验证转发行为:看流表而不是只靠 ping
拓扑起来后,不要急着说“通了”,要分三步验证。第一步在 Mininet 里执行pingall,确认主机间能通;第二步到控制器看拓扑图和链路;第三步进入 OVS 侧查看流表:
sudo ovs-ofctl -O OpenFlow13 dump-flows s1正常执行后,你会看到类似下面的输出:
cookie=0x0, duration=1.2s, table=0, n_packets=5, n_bytes=350, priority=10,dl_dst=00:00:00:00:00:02 actions=output:2这条流表条目的含义是:匹配目的 MAC 为00:00:00:00:00:02的数据包,从端口 2 转发出去。n_packets=5表示已有 5 个包命中流表,这能直观反映控制器是否真正生效。很多实验里 ping 能通但流表毫无变化,这种情况要警惕流量其实走了控制器慢路径,而不是数据平面的快速转发。
慢路径在拓扑小、流量低时不容易暴露问题,但一旦开始跑 iperf 打流量或模拟大量主机,控制器会瞬间成为瓶颈。验证时我一般会在 pingall 之后补一步 iperf:
# 在 Mininet 里执行 h1 iperf -s & h2 iperf -c h1 -t 10跑完后再去看流表条目里的n_packets数值,应该会有明显增长。如果数值只涨了一点点而带宽很低,说明大部分流量没走流表,仍然由控制器处理,这是你开始调整 FlowMod 下发策略或检查防火墙模块的信号。
4. 排查避坑:连不上、ping 不通、UI 空白的五个高频问题
4.1 现象:控制器日志看不到交换机连接,Mininet 报控制器无响应
现象很典型:Mininet 启动后终端输出Remote controller not responding,Floodlight 日志里完全没有连接记录。原因一般是两个:一是控制器的监听地址或端口与 Mininet 指定不一致;二是控制器本身没起来,比如端口被占用。
解决步骤:先用ss -lnp | grep 6653确认 Floodlight 是否在监听 6653;再确认 Mininet 里--controller=remote,ip=127.0.0.1,port=6653的 ip 和 port 是否与控制器实际监听地址匹配。如果 Floodlight 所在机器和 Mininet 不是同一台机器,还要检查openflow.bind.address是否绑定到对外地址,默认0.0.0.0没问题,但如果你改成127.0.0.1,外部机器就连不上。建议每次实验前固化一个 checklist:端口、地址、协议版本,三个都对了再往下查。
4.2 现象:交换机已连接,拓扑也发现,但 pingall 全部不通
这个问题的原因通常比第一个隐蔽。交换机握手成功且 LLDP 链路也展示出来,说明控制面和拓扑发现都正常,但数据包就是不通。最可能的原因是防火墙模块在起作用。
Floodlight 防火墙模块默认是否启用取决于版本配置,如果它进入启用状态且没有规则,所有 PacketIn 都会在监听器链中被丢弃。现象就是日志里能看到每个 PacketIn 都进来了,但转发模块没有处理。有一种说法认为这是防火墙模块的“白名单安全设计”,但从实验角度看,默认不放规则等于默认断网。
解决方式是添加一条全放行规则,注意 DPID 要换成实际交换机 ID:
curl -X POST -H 'Content-Type: application/json' \ -d '{"dpid": "00:00:00:00:00:00:00:01", "priority": "32767", "actions": "allow"}' \ http://127.0.0.1:8080/wm/firewall/rules/json这个 curl 命令的逻辑是:向防火墙模块注册一条高优先级规则。优先级 32767 是 Floodlight 防火墙规则里最高档,actions=allow表示放行,DPID 限定为当前交换机。添加后再次执行pingall,如果通了,根因就是防火墙默认拦截。如果实验里不打算测安全策略,更省事的做法是在floodlightdefault.properties的模块列表里去掉net.floodlightcontroller.firewall.Firewall,重启后防火墙模块不加载。
4.3 现象:控制器能连上,但 Web UI 拓扑图一直空白
这是典型的“API 好使、UI 拉胯”问题。现象是访问/ui/时页面能打开,但拓扑图不渲染,或者静态资源报 404。原因是 Floodlight 的前端资源打包进 jar 的路径在新版 Gradle 构建中可能不一致,静态资源没有被正确包含。
排查思路是绕过 UI,直接用 REST API 验证底层数据。请求/wm/topology/links/json,如果返回链路数组,说明后端拓扑数据没问题,问题出在静态资源加载。修复方式有两种:一是检查src/main/resources/web目录是否存在,并把该目录与 jar 放在同级位置,让 Jetty 能直接读外部静态资源;二是干脆不用 UI,自己写脚本读取 REST API 画图。我后来做实验基本都选第二种,不是 UI 不好,而是它涉及的前端资源问题在版本迭代中反复出现,与其花时间修复,不如让数据说话。
4.4 现象:流表条目疯涨,交换机 CPU 和延迟明显升高
这个现象在高密度拓扑和短连接场景下非常常见。流表条目数持续增加,交换机开始丢包,延迟抖动明显。根因一般是控制器下发的 FlowMod 缺少空闲超时,或超时时间设置太长,导致每条流长期驻留。模拟 IDC 东西向流量时,短连接多但流表不回收,问题尤其突出。
解决方式是在转发模块的 FlowMod 构建代码里显式设置idle_timeout和hard_timeout。常见做法是把空闲超时设为 60 秒,硬超时设为 300 秒,具体值要看流量模型。如果所有流都是几秒内的短连接,超时太大会让表项快速堆积;超时太小则每个包都触发 PacketIn 冲控制器。没有一组万能参数,但原则是让流表条目数和控制器负载取得平衡。
除参数外,还要确认交换机侧有没有流表溢出。OVS 的dump-flows输出里的n_packets和n_bytes字段能帮助判断流表是否还被命中。如果大量条目n_packets=0,说明它们已经不再使用,只是因为没到超时才占着空间,此时缩短超时能明显改善。
4.5 现象:控制器 CPU 居高不下,日志狂刷 LLDP 相关消息
链路发现是 Floodlight 的默认功能,它会周期性向交换机所有端口发 LLDP 包。控制器 CPU 过高的一个隐蔽原因是拓扑里有环或某端口异常,导致 LLDP 消息循环扩散。现象是日志里持续出现拓扑管理相关的调试输出,控制器整体响应变慢。
排查时先看拓扑有没有自环:用ovs-ofctl show s1检查每个交换机端口状态,重点关注是否有端口连到了不该连的设备。如果拓扑本身是环状,默认配置下 Floodlight 对环路拓扑处理能力有限,表现就是 CPU 飙升。另一种情况是端口没接对,比如把控制器连接端口也纳入了 LLDP 发现范围。
解决方式有两个方向:一是修正物理或虚拟拓扑,消除不必要的环;二是调大 LLDP 探测间隔。配置项是topology.llpdu-interval,单位毫秒,默认值一般是 2000。可以调到 5000 或 10000,观察 CPU 变化。调大之后链路发现变慢,但大多数实验拓扑不会频繁变化,这个代价可以接受。我自己的习惯是把探测间隔调到 3000 毫秒起步,然后看 CPU 基线,如果仍高再查环路。
5. 从跑通到二次开发:写一个 PacketIn 计数器模块,固定成调试习惯
5.1 模块骨架和注册
给控制器加自定义逻辑,最常用的是实现IOFMessageListener监听 PacketIn。按 DPID 统计 PacketIn 数量的核心代码就一段:
@Override public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { if (msg.getType() == OFType.PACKET_IN) { counter.compute(sw.getId(), (k, v) -> v == null ? 1 : v + 1); } return Command.CONTINUE; }counter用ConcurrentHashMap<Long, Long>,因为receive会被并发调用,compute的写法是原地自增,返回Command.CONTINUE则不切断后端模块的转发逻辑。注册项加进floodlightdefault.properties的floodlight.modules列表,重启后日志出现类的全限定名即加载成功。
5.2 调试三板斧和验证习惯
模块上线后,我的验证习惯固定为三板斧。第一,日志加节流,每 1000 条 PacketIn 才打印一次,避免刷屏。第二,用定时任务每 30 秒输出一次计数器大小,观察趋势。第三,加一个 REST 端点:
@RestRpc(service = "packetinstats") public Map<Long, Long> stats() { return counter; }之后curl http://127.0.0.1:8080/wm/packetinstats/stats/json就能拿到实时计数。从那以后我每次改完模块都强制走一遍“编译 → 重启 → pingall → 查 REST 端点”,确认状态而不是猜状态,踩坑次数明显下降。希望帮到你。
本文还有配套的精品资源,点击获取