
简介Floodlight 是一种当前主流的开源软件定义网络SDN控制器基于 Java 语言实现以稳定、易用和完全开源著称常被用于网络研究、实验平台搭建及创新应用开发。这份资源围绕 Floodlight 的安装部署展开面向希望快速搭建控制器环境的初学者、网络工程师和高校学生帮助其在 Ubuntu 等 Linux 系统中完成从环境评估、准备工作到实际部署的完整流程。压缩包采用 zip 格式体积为 64.72MB内容以安装部署文档为主适合离线查阅并对照实践。目前已有 554 人学习下载可作为 SDN 入门和实操的重要参考。文档不仅给出安装前准备和虚拟机环境搭建建议还阐述了 Ubuntu 下的安装过程、注意事项与常见排错思路读者可据此逐步构建可用的 Floodlight 运行环境为后续网络实验、控制器二次开发或应用层研究提供有力支撑。1. Floodlight是什么SDN控制器里最容易被低估的那一个很多刚接触SDN的同学一听到控制器就只想到OpenDaylight、ONOS这些大而全的框架反而把Floodlight当成一个“老古董”。但真要在自己的机器上把一个OpenFlow网络跑起来、看到流表怎么下发、想要动手改控制器行为Floodlight是最容易上手的那个。它用Java写的模块结构很直白REST API对HTTP请求友好从下载源码到交换机上线花的时间通常不超过半小时。这篇文章写给两类人一类是网络工程师想在测试环境里验证OpenFlow转发逻辑另一类是要做控制器二次开发或找毕业设计切入点的同学需要快速理解一个可运行的控制器的骨架。我会从编译启动讲到自定义模块每一步都给出实际命令和参数希望帮你少走弯路。2. 先跑起来从源码构建到北向接口第一次调用2.1 Floodlight的架构和模块化到底是怎么回事Floodlight的核心是Controller模块它负责维护OpenFlow连接、维护交换机列表、解析Packet-In消息。其他功能全部由模块承担比如DeviceManager维护主机MAC和端口的关系TopologyService维护链路状态RestApiServer把内部服务通过HTTP暴露出来。一个模块可以实现多个接口比如既提供服务给别人调用自己也监听Packet-In事件。模块之间的通信不靠消息总线而是通过获取服务接口来调用。比如你的自定义模块想查交换机信息可以在startUp里getProviderService(IOFSwitchService.class)然后调用getAllSwitchDpid()。这种设计比OSGi轻得多调试时也能直接看对象。Floodlight默认加载哪些模块写在floodlightdefault.properties里。这个文件就是模块清单一行一个全类名或者用逗号分隔同一行的多个模块。启动时框架会按列表顺序依次实例化模块、调用startUp。所以如果你要改默认行为基本思路就是“复制默认模块改一版然后在properties里替换掉默认模块”。2.2 构建与最小启动git clone、mvn编译、启动参数Floodlight本身是Maven项目虽然仓库里也带了eclipse相关工程文件但我建议直接用命令行干净利落。先把代码拉下来git clone https://github.com/floodlight/floodlight.git cd floodlight mvn install -DskipTests-DskipTests会跳过单元测试。这里多说一句Floodlight的测试代码依赖一些网络环境直接跑mvn install可能因为某个测试拉不到资源而失败。所以首次构建我基本都会跳过测试等后面需要验证某个模块行为时再单独跑测试类。构建完成后有两种启动方式。最省事的是用脚本./floodlight.sh这个脚本会检查java和maven环境然后默认加载src/main/resources/floodlightdefault.properties。如果你需要换一份配置用-cf参数指定java -jar target/floodlight.jar -cf /path/to/myfloodlight.properties不管是哪种方式启动后看到Controller startup complete类似日志就算成功。Floodlight默认监听两个端口OpenFlow协议端口是6653REST API端口是8080。这两个端口在配置文件里都能改但我建议初学阶段不要动先固定下来后面排查问题时会少一个变量。2.3 第一次用REST API验证控制器活没活Floodlight的北向接口走的是REST风格所有路径都以/wm开头。启动后先用一条最简单的请求确认REST API活着curl http://localhost:8080/wm/core/controller/switches/json返回[]是正常的因为现在还没有交换机连上来。这个接口是查控制器已知的交换机列表返回空数组不代表控制器挂了。接下来用Mininet在本地建一个虚拟的OpenFlow交换机连到Floodlightsudo mn --controllerremote,ip127.0.0.1,port6653 --switchovs,protocolsOpenFlow13注意--protocolsOpenFlow13这个参数它让Mininet里的OVS使用OpenFlow 1.3协议。Floodlight默认对OpenFlow 1.3的支持是开启的两边版本一致才能握手成功。启动Mininet后再执行一次刚才的 curl就能看到类似dpid:00:00:00:00:00:00:00:01的交换机信息。如果第二次curl还是返回空数组先别急着怀疑Floodlight。在Mininet里执行pingall如果主机之间能通说明控制器已经介入了再查本机防火墙是否屏蔽了6653端口。我自己遇到过最气人的一种情况是Floodlight启动日志里报了一个IOException但进程还挂着REST API也能访问就是没有交换机上线。后来发现是我先启动了Mininet然后才启动FloodlightOVS已经尝试连接旧地址多次失败后进入了等待周期。所以顺序很重要先控制器后网络设备。3. 下发真实转发规则静态流表与动态Packet-Out3.1 OpenFlow流表项的核心字段和Floodlight的匹配逻辑REST API能查到交换机在线之后下一个问题就是怎么让它干活。OpenFlow的本质是“匹配-动作”控制器向交换机下发流表项交换机收到数据包后按流表逐条匹配。流表项里最重要的几个字段是priority、match、actions和timeout。priority决定多条流表项的匹配顺序数值越大越先匹配。match是匹配条件比如指定入端口、源MAC、目的MAC、以太网类型、IPv4五元组等。actions是匹配后要执行的动作常见是output端口号或者drop。timeout分为idle_timeout和hard_timeout前者是空闲多久后删除后者是装上后多久强制删除。Floodlight本身不自动把所有流量都下发成精确流表。它有个默认的Forwarding模块在收到Packet-In后按照自己的算法决定是泛洪、直接转发还是丢包。但这个默认行为对生产场景往往不够比如你想固定让某个主机的流量只走某条链路就需要自己下发流表。3.2 用静态流表API手写一条二层转发规则Floodlight有一个静态流表下发模块叫StaticFlowEntryPusher对应的REST接口是/wm/staticflowentrypusher/json。最小的一条规则是这样curl -X POST -d { switch: 00:00:00:00:00:00:00:01, name: flow1, priority: 400, eth_type: 0x0806, actions: output2 } http://localhost:8080/wm/staticflowentrypusher/json这个JSON里switch是交换机的DPID必须和前面curl /wm/core/controller/switches/json查出来的一致name是这个流表项在Floodlight内存里的唯一标识下发同名规则会报错或覆盖priority设置为400用于保证这条ARP规则不被其他默认规则压住eth_type是0x0806即ARP协议actions是转发动作这里表示把匹配到的ARP包从交换机的端口2发出去。逻辑上为什么要先下一条ARP规则因为很多场景下两台主机之间通信先要知道对方MAC地址发出的是ARP请求。如果只有IP转发规则而没有ARP规则ARP请求就会被控制器处理掉或者直接丢弃结果是ping永远不通但直接看IPv4流表又好像什么都正确。这是SDN实验里最常见的“半通”状态。下发成功后再用同样的方式加一条IPv4转发规则比如eth_type0x0800、ipv4_src10.0.0.1、ipv4_dst10.0.0.2、actionsoutput2。这样从主机1发往主机2的流量有了精确匹配。注意这里匹配字段用了IP地址说明静态流表不仅能做二层的MAC转发也可以做简单的三层路由分流。3.3 看流表命中效果用REST查询和ovs-ofctl对照规则下发之后需要验证它到底有没有进交换机。先查Floodlight里的静态流表记录curl http://localhost:8080/wm/staticflowentrypusher/list/00:00:00:00:00:00:00:01/json返回JSON里能看到你刚才下发的flow1同时还有status: pending或status: installed字段。如果一直是pending说明Floodlight还没把规则推到交换机通常是因为交换机连接断了或者DPID没对上。再看OVS侧实际安装的流表ovs-ofctl -O OpenFlow13 dump-flows br0这里br0是Mininet里默认创建的交换机名。对比两个输出你会发现Floodlight静态流表的actions写法是output2而OVS显示的是OUTPUT:2这是不同层面表示格式的差异。重点是看OVS里是否出现了对应arp或ip的流表项以及n_packets和n_bytes计数器是否增长。如果OVS里没有这条流表说明Floodlight和OVS之间的OpenFlow连接是通的但下发消息被某个环节吞掉了检查一下配置的高优先级规则是否有冲突。还有一种情况OVS里有流表但计数器始终为0说明流量没匹配到。这时候去主机的命名空间里执行ip neigh看ARP表是否正常如果ARP表为空基本就是ARP规则没覆盖到主机发出的ARP请求。4. 自己写一个Floodlight模块监听Packet-In的Java套路4.1 为什么需要自定义模块Floodlight默认行为与场景缺口StaticFlowEntryPusher只能解决“预先知道要转发到哪个端口”的问题。但真实网络里交换机第一次收到一个未知目的MAC的数据包时会上报Packet-In给控制器控制器要根据网络拓扑、主机位置、当前链路状态来决定怎么做。Floodlight默认的Forwarding模块能做基本转发但如果你的场景是“某些MAC必须丢弃”“某些端口不做泛洪”或者“所有Packet-In都要打印日志”就得写自己的模块。常见做法是写一个监听IOFMessageListener的模块。这个接口的核心方法只有一个receive每当有OpenFlow消息进入控制器时它会被调用。你在这个方法里对OFType.PACKET_IN做处理返回Command.CONTINUE或Command.STOP。前者表示让后续监听器继续处理这条消息后者表示你已经处理完了后面监听器不用管。理解CONTINUE和STOP的语义特别重要。很多初学者写的模块不生效就是因为返回了CONTINUE但默认的ForwardingListener在后面又做了一次处理把你的规则覆盖了。如果你希望完全接管就要拦截在更早的管道位置返回STOP。4.2 一个最小模块的骨架实现IFloodlightModule和IOFMessageListener写一个模块需要实现两个接口IFloodlightModule负责模块生命周期IOFMessageListener负责监听OpenFlow消息。下面是最小可运行的骨架package net.floodlightcontroller.myTest; import java.util.Collection; import java.util.Collections; import java.util.Map; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFType; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.restserver.IRestApiService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SimplePacketInModule implements IFloodlightModule, IOFMessageListener { private static final Logger log LoggerFactory.getLogger(SimplePacketInModule.class); private IFloodlightProviderService floodlightProvider; private IRestApiService restApiService; Override public CollectionClass? extends IFloodlightService getModuleServices() { return Collections.emptyList(); } Override public MapClass? extends IFloodlightService, IFloodlightService getServiceImpls() { return Collections.emptyMap(); } Override public CollectionClass? extends IFloodlightService getModuleDependencies() { return Collections.Class? extends IFloodlightServicesingleton(IFloodlightProviderService.class); } Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProvider context.getServiceImpl(IFloodlightProviderService.class); log.info(SimplePacketInModule init); } Override public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); log.info(SimplePacketInModule startUp complete); } Override public String getName() { return SimplePacketInModule; } Override public boolean isCallbackOrderingPrereq(OFType type, String name) { return false; } Override public boolean isCallbackOrderingPostreq(OFType type, String name) { return false; } Override public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { log.info(Received Packet-In from switch {}: {}, sw.getId(), msg); return Command.CONTINUE; } }这段代码的逻辑拆开讲init阶段先取得核心服务IFloodlightProviderService的引用这是后面注册监听器要用的。startUp阶段才是真正的“启动完成”在这里调用addOFMessageListener把当前类注册为PACKET_IN消息的监听者。getModuleDependencies告诉框架当前模块依赖哪些服务框架在加载模块时会先初始化依赖。里面有几个方法必须实现但通常不需要写逻辑比如isCallbackOrderingPrereq和isCallbackOrderingPostreq这两个方法用来声明监听器的回调顺序默认返回false就可以。getName返回的字符串会出现在Floodlight的日志和回调链路里建议取一个容易认的名字。receive方法里我暂时只打日志返回CONTINUE。这样你就能看到交换机上报的每一个Packet-In同时不影响原有转发逻辑。如果你想自己处理可以在receive里解析OFPacketIn消息拿到入端口和Ethernet帧然后调用IOFSwitch.write下发Packet-Out。但这一步需要你对OpenFlow协议消息结构比较熟第一次写模块建议先只监听、只打日志把链路跑通再逐步加深。4.3 把模块接进Floodlight的配置floodlightdefault.properties和模块加载顺序Java代码写完后还要让Floodlight认识它。否则编译不会把它打进控制器。打开src/main/resources/floodlightdefault.properties找到floodlight.modules这一行floodlight.modules net.floodlightcontroller.core.internal.FloodlightProvider,\ net.floodlightcontroller.restserver.RestApiServer,\ net.floodlightcontroller.myTest.SimplePacketInModule注意我的做法是把自定义模块加在一行模块列表的最后。为什么要刻意放到最后因为Floodlight按这个列表顺序实例化模块如果你的模块要依赖前面的服务就必须放在它们之后。比如SimplePacketInModule依赖FloodlightProvider和RestApiServer把它放前面启动时依赖服务还没被初始化直接就会在context.getServiceImpl那一步抛异常。模块自己只能通过Override注解表示是net.floodlightcontroller.myTest包下的类真正决定加载使用的是properties里的全类名。所以类名和包名必须写对一个字母错都会导致ClassNotFoundException但这里有个坑Floodlight启动时如果找不到某个模块不会立即退出而是打印一条警告日志继续跑。看起来控制器活着可你的逻辑就是不执行。所以配置完模块一定要看启动日志里有没有Module not found类似的警告。重新编译也很简单在工程根目录执行mvn install -DskipTests然后重启Floodlight。启动日志里如果出现SimplePacketInModule startUp complete说明模块已经加载。这时在Mininet里pingall日志里就会打印每一条Packet-In的交换机ID和消息内容。5. Floodlight常见问题排查五个血泪踩坑现场5.1 控制器连接不上端口、协议和防火墙现象Mininet启动时提示无法连接remote controllerpingall全部丢包Floodlight日志里也看不到交换机加线通知。原因最常见的三种。一是Floodlight监听端口不是6653你在配置里改了端口但Mininet里还写老端口二是系统防火墙屏蔽了6653但本地回环接口有时也会受防火墙规则影响三是Mininet里的OVS默认用OpenFlow 1.0去连接而Floodlight配置只启用了OpenFlow 1.3协议协商失败。解决先确认Floodlight监听端口用netstat -an | grep 6653如果没监听去看日志里到底绑定的是哪个端口。然后在Mininet里用--controllerremote,ip127.0.0.1,port6653显式指定的同时加--switchovs,protocolsOpenFlow13。防火墙方面测试阶段可以直接sudo ufw disable或者只放行6653但生产环境不要这样干按最小原则开端口。5.2 REST API路径或请求格式返回异常现象curl 请求返回404 Not Found或者400 Bad Request但REST接口文档里明明有这个路径。原因Floodlight的REST API路径前缀是/wm漏掉这个前缀会404。另外更多时候是JSON格式问题比如actions字段写成output:2Floodlight的解析器严格按output2的格式读取格式错误直接返回400。解决规范路径和请求头。路径是http://controller-ip:8080/wm/staticflowentrypusher/json务必带/wm。POST请求加上-H Content-Type: application/json且JSON双引号不要被shell转义出错。我习惯把JSON写到独立文件里用curl -X POST -d flow.json方式提交避免终端转义带来的玄学问题。5.3 静态流表下发成功但Ping不通ARP拦截和优先级现象/wm/staticflowentrypusher/list里显示规则installedOVS里也能看到对应流表但主机之间就是ping不通。原因这是SDN实验里翻车率最高的问题。只下发了IPv4转发规则没有下发ARP规则。控制器收到ARP请求后如果在自己的转发模块里处理掉但你的自定义模块又拦截或丢弃了主机的ARP表就一直学不到MAC地址。另一个常见原因是优先级不够Floodlight默认规则里可能有一条drop-all优先级和你的规则相同或更高导致你的规则永不匹配。解决ARP规则和IP规则一起下eth_type分别写0x0806和0x0800。优先级统一设置一个高于默认规则的值比如400。同时用ovs-ofctl dump-flows查看所有规则确认没有一条priority0, actionsdrop压在你上面。如果发现默认drop可以把你的静态规则priority改成500。5.4 自定义模块不生效配置没加载或类名拼错现象重启Floodlight后日志里没有任何自定义模块的打印getName()里设置的名字也不出现在日志中连报错都没有。原因大概率是floodlight.modules里没有写你的类或者写了但类名拼错。最气人的是Floodlight对找不到模块不报警它只会在加载列表里跳过。另外还有一个原因是你改的properties文件没有被加载比如你用了-cf指定了一个新文件但文件里没有floodlight.modules这条配置。解决启动时加一个-Djava.util.logging.config.file或看日志时用grep -i module筛一下。我通常会在startUp方法里写一行强日志输出因为模块是否加载成功日志是最直接的证据。如果日志里没有就先检查properties文件是否被加载。可以用ps aux | grep floodlight看启动命令激活参数确认-cf指向的确实是你的文件。5.5 试试就逝世Java环境与依赖冲突现象mvn install时报一大堆编译错误或者启动Floodlight时直接NoClassDefFoundError/NoSuchMethodError。原因Floodlight的代码历史比较长对JDK版本比较敏感。我现在手头的经验是JDK 8最稳JDK 11在部分老分支会出现javax.xml.bind缺失JDK 17直接编译失败。Maven依赖也可能因为网络问题下载了损坏的jar包。解决统一用JDK 8安装后设置JAVA_HOME。编译失败时先rm -rf ~/.m2/repository后重试虽然慢但能解决大部分损坏依赖问题。如果项目里有gradle或ant文件优先用项目自带的构建说明里的工具链不要自己换新版本。6. 让Floodlight真正可用拓扑发现、链路故障和性能参数把规则和模块都跑通之后Floodlight还差一步才算真正可用自动感知链路变化。Floodlight默认通过LLDP做拓扑发现交换机每隔一段时间上报链路信息控制器把链路关系存在TopologyService里。你可以在REST接口里看链路状态curl http://localhost:8080/wm/topology/links/json返回的JSON里会列出每条链路的源交换机DPID、源端口、目的交换机DPID和目的端口。如果链路断了条目会从列表里消失。这个机制在处理链路故障时很有用但要注意LLDP报文是控制器主动注入到交换机再通过数据通道泛洪的如果你的交换机把LLDP当成普通数据包需要检查是否配置了丢弃规则。性能调优方面我通常关注两个点。第一是JVM内存堆大小默认启动脚本给的不一定够大拓扑场景下可以显式指定java -Xms512m -Xmx2g -jar target/floodlight.jar第二是Java启动参数里加入-Djava.net.preferIPv4Stacktrue避免在某些双栈环境里控制器监听IPv6地址而交换机的IPv4连接请求落空。我自己的习惯是每次改完代码先启动控制器看日志再启动Mininet然后再curl一次REST确认交换机在线。三步都通过才进行下一步避免把环境问题和新代码问题混在一起浪费时间。希望这篇笔记能帮你绕开我踩过的那些坑顺利把Floodlight用起来。本文还有配套的精品资源点击获取