floodlight源码包分析

在分析floodlight源码包之前,先惯例性来个简单介绍,floodlight是一款基于Java语言的开源SDN控制器,遵循Apache2.0软件许可,支持openflow协议。Floodlight与NOX、POX等其他控制器类似,也使用了“层次化”架构来实现控制器的功能,同时提供了丰富的应用,可以直接在网络中部署数据转发、拓扑发现等基本功能。也正是因为floodlight这种模块化的实现方式,让我们分析floodlight源码大大减轻了难度。 ·
在接下来的分析里,我只是重点介绍一下floodlight新版本源码改变的地方。相信有那么一部分初学者floodlight的入门是看北邮老师写的这本书《软件定义网络核心原理与应用实践》,我开始学习floodlight的时候,直接选择了floodlight1.2版本,去看源码的时候,跟这本书上介绍的有点出入,要不是后续floodlight接触多了,还真无从下手,就比如书中介绍源码树下的org.openflow目录就没有,floodlight1.2版本中是调用了openflowJ库,在lib目录下有。以及代码中封装flowmod消息中构造match域的方式,channel接收消息的方式都有一些改变,当然这些改变是针对目前网上已有的floodlight分析文章中的内容。对floodlight整个源码大体分析网上已经有很多文章了,包括具体模块的分析,也有很多文章,但是这些文章都是基于老版本的floodlight,目前新版本都或多或少有改变,所以这篇文章侧重点是分析floodlight1.2版本我接触到的模块与网上分析的模块的不同之处。
获取源码方式:
我们在分析floodlight源码的时候,可以参照floodlight官方网站上提供的架构图来最为切入点。架构图上的各个模块在源码目录下都会有一个以这个模块名为名称的文件夹,在这个文件夹中的Java代码即为这个模块功能的实现代码,所以我们只是分析某个模块功能的时候,只需要找到对应的文件夹,分析里面的Java文件就行了。当然在源码树目录中(src\main\java\net\floodlightcontroller这个目录下)的文件夹名也不是完全对应着架构图上的各个模块名字,比如模块管理模块,就没有与之对应的文件夹,但是有个名字叫core的文件夹,经过我分析里面的Java代码,模块管理模块的具体实现代码就在core文件夹中,其余模块都会有一个与模块名一一对应的文件夹。
另外除了以架构图为切入点外,在分析floodlight的时候,需要提前了解一下Java编程中的一些特性,在这里我只是列出需要注意的点,具体内容可以参考《think in java》或者《疯狂Java讲义》。Java大牛直接跳过,像我这种第一次接触Java语言的菜鸟来说,还是乖乖的先了解一下下面我提到的Java知识,这样可以避免很多坑。
(1) 理解Java语言中的接口和类的概念,以及它们之间的区别;
(2) 理解Java中集合编程知识,重点是list,map;
(3) 理解Java泛型编程知识;
(4) 理解Netty通信框架原理。
有了对上面知识的了解后,在分析代码的过程中,就会轻松很多,在这里我就把我对core文件夹中的代码的一些认识整理出来,当然其中一些分析是参考了网上已有的资料,不过更多的是我经过多次实验,跑报文抓包分析的结果。对core文件夹里代码的分析,我们需要大体知道这个文件夹里的代码主要实现的功能是负责与交换机建立连接,负责管理加载floodlight的各个模块,以及完成报文的接收、发送、分派和整个系统启动。
1、 Main.java文件
Main是floodlight工程的入口方法,它主要完成的任务是:首先导入floodlight的默认配置文件“src/main/resources/floodlightdefault.properties”,接着根据配置文件加载并启动各个模块,具体代码实现如下:
try {
// Setup logger
System.setProperty(“org.restlet.engine.loggerFacadeClass”,
“org.restlet.ext.slf4j.Slf4jLoggerFacade”);

        CmdLineSettings settings = new CmdLineSettings();
        CmdLineParser parser = new CmdLineParser(settings);
        try {
            parser.parseArgument(args);
        } catch (CmdLineException e) {
            parser.printUsage(System.out);
            System.exit(1);
        }

        // Load modules
        FloodlightModuleLoader fml = new FloodlightModuleLoader();
        try {
            IFloodlightModuleContext moduleContext = fml.loadModulesFromConfig(settings.getModuleFile());
            IRestApiService restApi = moduleContext.getServiceImpl(IRestApiService.class);
            restApi.run(); 
        } catch (FloodlightModuleConfigFileNotFoundException e) {
            // we really want to log the message, not the stack trace
            logger.error("Could not read config file: {}", e.getMessage());
            System.exit(1);
        }
        try {
            fml.runModules(); // run the controller module and all modules
        } catch (FloodlightModuleException e) {
            logger.error("Failed to run controller modules", e);
            System.exit(1);
        }
    } catch (Exception e) {
        logger.error("Exception in main", e);
        System.exit(1);
    }
}

2、 FloodlightModuleLoadler.java
FloodlightModuleLoader这个类主要是模块导入、模块加载、模块初始化和启动的具体功能代码实现。
loadModulesFromConfig(string fName)方法:这个方法的功能是从指定的配置文件中导入模块。方法参数是配置文件的文件路径,方法返回值是一个模块上下文集合。
addModule()方法:这个方法的作用是把把模块对象放入名叫moduleList的集合中,具体代码实现如下:
protected void addModule(Map,
IFloodlightModule> moduleMap,
Collection moduleList,
IFloodlightModule module) {
Collection> servs =
moduleServiceMap.get(module);
if (servs != null) {
for (Class<? extends IFloodlightService> c : servs)
moduleMap.put(c, module);
}
moduleList.add(module);
}
initModules()方法:这个方法的作用是遍历moduleSet集合,接着执行遍历得到的模块中的init方法,完成此模块的初始化任务。代码如下
for (IFloodlightModule module : moduleSet) {
// init the module
if (logger.isDebugEnabled()) {
logger.debug(“Initializing “ +
module.getClass().getCanonicalName());
}
module.init(floodlightModuleContext);
}
startupModules()方法:这个方法的作用是遍历moduleSet集合,接着执行遍历得到的模块中的startUp方法,startUp方法根据具体模块的需求添加相应功能,比如这个模块需要监听packet_in消息,则需调用addOFMessageListener(OFType type,IOFMessageLIstener listener)方法向floodlightProvider注册此模块监听packet_in消息;另外如果此模块需要提供REST API接口供上层app调用的话,还需要通过addRestletRoutable()向restAPI注册REST 接口。代码实现如下。
protected void startupModules(Collection moduleSet)
throws FloodlightModuleException {
for (IFloodlightModule m : moduleSet) {
if (logger.isDebugEnabled()) {
logger.debug(“Starting “ + m.getClass().getCanonicalName());
}
m.startUp(floodlightModuleContext);
}
}
3、 Controller.java
在这里我主要分析Controller类对于消息的派发过程。关键方法为handleMessage()。
当OFSwitchHandshakeHandler类收到来自OFChannelHandler类传递过来的消息时,就会触发处理方法handleMessage(),而handleMessage()的具体实现即在controller类中实现。
核心内容就是遍历listenners集合,把消息传递给listener对象的回调函数receive。(这些listener,即通过方法addMessageListener()注册监听的各个模块对象)。关键部分代码如下。
for (IOFMessageListener listener : listeners) {
pktinProcTimeService.recordStartTimeComp(listener);
cmd = listener.receive(sw, m, bc);
pktinProcTimeService.recordEndTimeComp(listener);

    if (Command.STOP.equals(cmd)) {
        break;
    }
}

4、 OFChannelHandler.java
OFChannelHandler类实现的功能是处理交换机的连接过程以及完成最下层的消息接收、发送,以及实现把收到的消息分派给更上层的控制器模块。Nettty通信建立完成后,通过channel通道, floodlight控制器收到openflow交换发给控制器的二进制消息buffer,接着控制器会调用OFMessageDecoder类进行解码,把二进制消息解析成openflow消息格式,并存放到消息集合msgList中。此时方法channelRead0()会循环遍历这个msgList集合,把消息对象送给state.processOFMessage()方法进行处理。具体实现代码如下:
public void channelRead0(ChannelHandlerContext ctx, Iterable msgList) throws Exception {
for (OFMessage ofm : msgList) {
try {
// Do the actual packet processing
state.processOFMessage(ofm);
}
catch (Exception ex) {
// We are the last handler in the stream, so run the
// exception through the channel again by passing in
// ctx.getChannel().
ctx.fireExceptionCaught(ex);
}
}
}
同样floodlight发送给openflow交换机的消息最终会调用channel.wirteAndFlush()方法写入channel通道,通过socket发送给openflow交换机。代码调用如下
private void write(OFMessage m) {
channel.writeAndFlush(Collections.singletonList(m));
}