Floodlight 防火墙是如何起作用的

前言

用mininet + floodlight搭建好环境之后,运行flooglight,然后在mininet中建立网络拓扑,建好之后,pingall,所有host之间可以ping通。

然后向控制器floodlight中添加防火墙规则,比如

curl -X POST -d '{"src-ip": "10.0.0.1/32", "dst-ip": "10.0.0.2/32","action":"DENY","priority":"10"}' http://localhost:8080/wm/firewall/rules/json

,然后10.0.0.1与10.0.0.2之间就不能ping通了。

那么,这条规则是如何起作用的呢?

注:为简单起见,文中贴的代码片段是在Floodloght源代码文件基础上删减后的。

首先定位到控制数据包转发的类net.floodlightcontroller.forwarding.Forwarding,从该类的processPacketInMessage方法入手。当交换机收到不知道怎么处理的数据包时(即新来的数据包与交换机里现存的所有flow-entry都不匹配),就以packetIn消息的形式将该数据包传给controller。以下方法就是处理packetIn消息的代码:

public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {
// If a decision has been made we obey it // otherwise we just forward if (decision != null) {switch(decision.getRoutingAction()) { case NONE: // don't do anything return Command.CONTINUE;case FORWARD: doForwardFlow(sw, pi, cntx, false); return Command.CONTINUE; case MULTICAST: // treat as broadcast doFlood(sw, pi, cntx); return Command.CONTINUE; case DROP: doDropFlow(sw, pi, decision, cntx); return Command.CONTINUE; default: } } else { doForwardFlow(sw, pi, cntx, false); } return Command.CONTINUE; }

上述代码表明,decision决定了一个数据包是被forward还是被drop。特别的,当decision为null时,对该packet进行forward或者flood。

所以接下来看哪里调用了processPacketInMessage方法。找到net.floodlightcontroller.routing.ForwardingBase这个类,该类的receive方法是唯一一个调用了processPacketInMessage方法的函数,以下是代码:

@Override
    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
        switch (msg.getType()) {
            case PACKET_IN:
                IRoutingDecision decision = null;
                if (cntx != null)
                     decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
return this.processPacketInMessage(sw, (OFPacketIn) msg, decision, cntx); default: break; } return Command.CONTINUE; }

上述代码表明decision的值来源于cntx。cntx是一个对象,创建它的类是FloodlightContext,源代码里的注释表示“This is a context object where floodlight listeners can register and later retrieve context information associated with an event”。调用receive的方法有三个,如下:

net.floodlightcontroller.core.internal.Controller.handleMessage(IOFSwitch, OFMessage, FloodlightContext)
net.floodlightcontroller.core.internal.Controller.handleOutgoingMessage(IOFSwitch, OFMessage, FloodlightContext)
net.floodlightcontroller.core.internal.OFSwitchImpl.deliverStatisticsReply(OFMessage)

而cntx的意思大概也明白了:是一个floodlight运行的上下文环境,cntx从floodlight一开始运行的时候就收集一些信息,floodlight里的监听器相关连的事件,如果事件发生,监听器可以从当前的cntx中检索需要的值。可见这个cntx是一个处于线程池中动态更新的对象。

再往下的跟踪超出我的理解范围了(有兴趣的可以自己去跟以下,会跟到net.floodlightcontroller.core.internal.Controller这个类)。可以这么理解:当你向控制器中添加防火墙规则时候,监听器会检测到新事件的产生(添加防火墙规则时,eclipse控制台的突然大量输出可以说明这一点),并将相关信息注册到cntx。

返回来看看processPacketInMessage方法里调用的doDropFlow和doForwardFlow方法。这两个方法也在net.floodlightcontroller.forwarding.Forwarding这个类里。先看doDropFlow方法,代码如下:

protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {
        // initialize match structure and populate it using the packet
        OFMatch match = new OFMatch();
        match.loadFromPacket(pi.getPacketData(), pi.getInPort());
        if (decision.getWildcards() != null) {
            match.setWildcards(decision.getWildcards());
        }
        
        // Create flow-mod based on packet-in and src-switch
        OFFlowMod fm = (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD);
        List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to drop
        long cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
        
        fm.setCookie(cookie)
          .setHardTimeout((short) 0)
          .setIdleTimeout((short) 5)
          .setBufferId(OFPacketOut.BUFFER_ID_NONE)
          .setMatch(match)
          .setActions(actions)
          .setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH);

        try {
            messageDamper.write(sw, fm, cntx);
        } catch (IOException e) {
            log.error("Failure writing drop flow mod", e);
        }
    }

上述代码里核心的一句是messageDamper.write(sw, fm, cntx);这行代码表示往交换机sw里下发流表策略。sw是将这个packet传送给controller的交换机。而下发的这些流表就能实现防火墙规则的功能,禁止10.0.0.1与10.0.0.2之间通信。留意fm和cntx,这两个变量决定了下发的流表是什么样的。

再看doForwardFlow方法。doForwardFlow代码复杂一些,把前人的分析copy过来:

参考自http://lamoop.com/post/2013-11-28/40060265578

首先声明一个OFMatch对象,并将packet-in中的相关信息加载到该对象中:

OFMatch match = new OFMatch(); match.loadFromPacket(pi.getPacketData(), pi.getInPort());

然后通过packet-in消息获取目标和源设备(idevice),即以下代码:

IDevice dstDevice =IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_DST_DEVICE);
if (dstDevice != null) {
IDevice srcDevice =
IDeviceService.fcStore.
get(cntx, IDeviceService.CONTEXT_SRC_DEVICE);

再然后定位发生packet-in消息的switch所属于的island的标志

Long srcIsland = topology.getL2DomainId(sw.getId());

接下来,判断目标和源设备是否处于同一个island,是则继续,否则执行doFlood方法(类似广播),代码如下:

// Validate that we have a destination known on the same island
// Validate that the source and destination are not on the same switchport
boolean on_same_island = false;
boolean on_same_if = false;
for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) {
    long dstSwDpid = dstDap.getSwitchDPID();
    Long dstIsland = topology.getL2DomainId(dstSwDpid);
    if ((dstIsland != null) && dstIsland.equals(srcIsland)) {
        on_same_island = true;
        if ((sw.getId() == dstSwDpid) &&
            (pi.getInPort() == dstDap.getPort())) {
            on_same_if = true;
        }
        break;
    }
}

if (!on_same_island) {
    // Flood since we don't know the dst device
    if (log.isTraceEnabled()) {
        log.trace("No first hop island found for destination " + 
                  "device {}, Action = flooding", dstDevice);
    }
    doFlood(sw, pi, cntx);
    return;
}

如果在同一个island中,则通过getAttachmentPoints方法获取目标和源设备相连的交换机的端口信息

SwitchPort[] srcDaps = srcDevice.getAttachmentPoints();
Arrays.sort(srcDaps, clusterIdComparator);
SwitchPort[] dstDaps = dstDevice.getAttachmentPoints();
Arrays.sort(dstDaps, clusterIdComparator);

获取到的结果是两个排序的端口数组,数组的元素类似:

SwitchPort [switchDPID=7, port=2, errorStatus=null]

再接下来进入一个while循环,循环的终止条件是取完上一步得到的两个数组中的元素,目标是找到属于同一个island的分布在两个数组中的元素。循环内部先通过getL2DomainId方法判断,所选取的与目标和源相连接的交换机是否在同一个island,如果不在,则根据两个island标志的大小选择性的选取srcDaps或dstDaps中的其它元素,继续比较,之所以可以这么做,是因为两个数组是经过排序的,而且key就是各个交换机所属的island标志,代码为while循环最后的if-else语句:

} else if (srcVsDest < 0) {
    iSrcDaps++;
} else {
    iDstDaps++;
}

如果找到两个island相同的元素,会调用routingEngine的getRoute方法获取两个端口之间的路由,这才是我们真正关心的流程。这里并未继续跟踪getRoute是如何获取两个交换机端口之间的最短路径(官网提到获取的路由是最短路径),其获取的结果类似:

[[id=00:00:00:00:00:00:00:07, port=2], [id=00:00:00:00:00:00:00:07, port=3],
[id=00:00:00:00:00:00:00:05, port=2], [id=00:00:00:00:00:00:00:05, port=3],
[id=00:00:00:00:00:00:00:01, port=2], [id=00:00:00:00:00:00:00:01, port=1],
[id=00:00:00:00:00:00:00:02, port=3], [id=00:00:00:00:00:00:00:02, port=1],
[id=00:00:00:00:00:00:00:03, port=3], [id=00:00:00:00:00:00:00:03, port=1]]

接下来利用最初生成的OFMatch对象信息定义一个规则:

wildcard_hints = ((Integer) sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS))
.intValue()
& ~OFMatch.OFPFW_IN_PORT
& ~OFMatch.OFPFW_DL_VLAN
& ~OFMatch.OFPFW_DL_SRC
& ~OFMatch.OFPFW_DL_DST
& ~OFMatch.OFPFW_NW_SRC_MASK
& ~OFMatch.OFPFW_NW_DST_MASK;

最后调用pushRoute方法,下发路由策略,即flow信息。

pushRoute(route, match, wildcard_hints, pi, sw.getId(), cookie,
cntx, requestFlowRemovedNotifn, false,
OFFlowMod.OFPFC_ADD);

继续追踪pushRoute函数,其在ForwardingBase.java中实现,其实就是循环上面route获取到的最短路径,在每个交换机上添加一条flow,包含inport和outport,当然上面提到的wildcard_hints也会通过setMatch方法设置到没条flow中。

pushRoute函数里也有messageDamper.write(sw, fm, cntx);这行代码。作用与doDropFlow方法里的这行代码一样:往交换机里下发流表策略。doDropFlow里是下发阻止某两个主机通信的流表,而这里下发的流表是为了网络中的其他主机可以通信。

原文地址:https://www.cnblogs.com/duanguyuan/p/3704759.html