zoukankan      html  css  js  c++  java
  • 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里是下发阻止某两个主机通信的流表,而这里下发的流表是为了网络中的其他主机可以通信。

  • 相关阅读:
    Mac上TexStudio无法显示中文字符的问题
    python中import与from方法总结
    Jupter Notebook 使用技法
    python把列表(list)传给函数形参时的问题剖析
    Spyder常用快捷键
    用Tinkercad学arduino之 多喇叭发声
    用Tinkercad学arduino之 播放旋律
    用Tinkercad学arduino之 音调键盘 按键改变音调
    用Tinkercad学arduino之 伺服电机摆动
    用Tinkercad学arduino之 读取电位器模拟输入
  • 原文地址:https://www.cnblogs.com/duanguyuan/p/3704759.html
Copyright © 2011-2022 走看看