SDN控制器Floodlight源码学习(六)--链路发现模块(LinkDiscovery)

前面的文章我对Floodlight的模块加载、控制器与交换机通信、控制器分发数据包进行了探索,今天开始继续Floodlight源码探索之旅,今天研究下面这两个包,也就是控制器对链路的发现:

net.floodlightcontroller.linkdiscovery
net.floodlightcontroller.linkdiscovery.internal

我们知道Floodlight是通过LLDP和BDDP协议来进行数据链路的发现,从源码中可以看出BDDP只是将帧的目标MAC地址改为广播地址,而LLDP则使用组播地址01:80:c2:00:00:0e。在学习源码之前还需要看看LLDP的基础知识,这篇感觉讲的很详细:

http://blog.csdn.net/goodluckwhh/article/details/10948065

好了,闲话不多说,我们先看看控制器是如何使用LLDP和BDDP来发现链路的,为了说清楚,我使用引用Floodlight官网的描述,并配上图加上自己的理解,希望大家指正:

Description

The link discovery service is responsible for discovering
and maintaining the status of links in the OpenFlow network. How it
works

The link discovery services uses both LLDPs and broadcast packets (aka
BDDPs) to detect links. The LLDP destination MAC is 01:80:c2:00:00:0e
and the BDDP destination MAC is ff:ff:ff:ff:ff:ff (broadcast address).
The ether type for LLDPs and BDDPs is 0x88cc and 0x8999. There are two
assumptions made in order for the topology to be learned properly.

Any switch (including OpenFlow switches) will consume a link-local
packet (LLDP). Honors layer 2 broadcasts.

Links can either be “direct” or “broadcast”. A direct link will be
established if an LLDP is sent out one port and the same LLDP is
received on another port. This implies that the ports are directly
connected. A broadcast link is created if a BDDP is sent out a port
and received on another. This implies that there is another layer 2
switch not under the control of the controller between these two
ports.

下面两图分别说明了LLDP和BDDP发现链路的原理:
SDN控制器Floodlight源码学习(六)--链路发现模块(LinkDiscovery)_第1张图片
SDN控制器Floodlight源码学习(六)--链路发现模块(LinkDiscovery)_第2张图片

从上图可以看出,第一种情况,控制器发出LLDP后,并通过PACK_IN消息收到了相同的LLDP,则可以确定链路为直连。若LLDP发出后,没有收到相同的LLDP,则发送BDDP,如果收到了相同的BDDP,则确定链路中间有非OF设备。如果连BDDP也没收到,则认为端口处于网络边缘.

不知道说清楚没有,我们开始看代码,Floodlight链路发现的代码主要在下面这个类当中。

net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager.java

我们先看看LLDP和BDDP生成的代码:

@Override
    public OFPacketOut generateLLDPMessage(IOFSwitch iofSwitch, OFPort port, 
            boolean isStandard, boolean isReverse) {

        OFPortDesc ofpPort = iofSwitch.getPort(port);

        if (log.isTraceEnabled()) {
            log.trace("Sending LLDP packet out of swich: {}, port: {}, reverse: {}",
                new Object[] {iofSwitch.getId().toString(), port.toString(), Boolean.toString(isReverse)});
        }

        // using "nearest customer bridge" MAC address for broadest possible
        // propagation
        // through provider and TPMR bridges (see IEEE 802.1AB-2009 and
        // 802.1Q-2011),
        // in particular the Linux bridge which behaves mostly like a provider
        // bridge
        byte[] chassisId = new byte[] { 4, 0, 0, 0, 0, 0, 0 }; // filled in
        // later
        byte[] portId = new byte[] { 2, 0, 0 }; // filled in later
        byte[] ttlValue = new byte[] { 0, 0x78 };
        // OpenFlow OUI - 00-26-E1-00
        byte[] dpidTLVValue = new byte[] { 0x0, 0x26, (byte) 0xe1, 0, 0, 0,
                0, 0, 0, 0, 0, 0 };
        LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127)
                .setLength((short) dpidTLVValue.length)
                .setValue(dpidTLVValue);

        byte[] dpidArray = new byte[8];
        ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray);
        ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2);

        DatapathId dpid = iofSwitch.getId();
        dpidBB.putLong(dpid.getLong());
        // set the chassis id's value to last 6 bytes of dpid
        System.arraycopy(dpidArray, 2, chassisId, 1, 6);
        // set the optional tlv to the full dpid
        System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8);

        // TODO: Consider remove this block of code.
        // It's evil to overwrite port object. The the old code always
        // overwrote mac address, we now only overwrite zero macs and
        // log a warning, mostly for paranoia.
        byte[] srcMac = ofpPort.getHwAddr().getBytes();
        byte[] zeroMac = { 0, 0, 0, 0, 0, 0 };
        if (Arrays.equals(srcMac, zeroMac)) {
            log.warn("Port {}/{} has zero hardware address"
                    + "overwrite with lower 6 bytes of dpid",
                    dpid.toString(), ofpPort.getPortNo().getPortNumber());
            System.arraycopy(dpidArray, 2, srcMac, 0, 6);
        }

        // set the portId to the outgoing port
        portBB.putShort(port.getShortPortNumber());

        LLDP lldp = new LLDP();
        lldp.setChassisId(new LLDPTLV().setType((byte) 1)
                .setLength((short) chassisId.length)
                .setValue(chassisId));
        lldp.setPortId(new LLDPTLV().setType((byte) 2)
                .setLength((short) portId.length)
                .setValue(portId));
        lldp.setTtl(new LLDPTLV().setType((byte) 3)
                .setLength((short) ttlValue.length)
                .setValue(ttlValue));
        lldp.getOptionalTLVList().add(dpidTLV);

        // Add the controller identifier to the TLV value.
        lldp.getOptionalTLVList().add(controllerTLV);
        if (isReverse) {
            lldp.getOptionalTLVList().add(reverseTLV);
        } else {
            lldp.getOptionalTLVList().add(forwardTLV);
        }

        /* 
         * Introduce a new TLV for med-granularity link latency detection.
         * If same controller, can assume system clock is the same, but
         * cannot guarantee processing time or account for network congestion.
         * 
         * Need to include our OpenFlow OUI - 00-26-E1-01 (note 01; 00 is DPID); 
         * save last 8 bytes for long (time in ms). 
         * 
         * Note Long.SIZE is in bits (64).
         */
        long time = System.currentTimeMillis();
        long swLatency = iofSwitch.getLatency().getValue();
        if (log.isTraceEnabled()) {
            log.trace("SETTING LLDP LATENCY TLV: Current Time {}; {} control plane latency {}; sum {}", new Object[] { time, iofSwitch.getId(), swLatency, time + swLatency });
        }
        byte[] timestampTLVValue = ByteBuffer.allocate(Long.SIZE / 8 + 4)
                .put((byte) 0x00)
                .put((byte) 0x26)
                .put((byte) 0xe1)
                .put((byte) 0x01) /* 0x01 is what we'll use to differentiate DPID (0x00) from time (0x01) */
                .putLong(time + swLatency /* account for our switch's one-way latency */)
                .array();

        LLDPTLV timestampTLV = new LLDPTLV()
        .setType((byte) 127)
        .setLength((short) timestampTLVValue.length)
        .setValue(timestampTLVValue);

        /* Now add TLV to our LLDP packet */
        lldp.getOptionalTLVList().add(timestampTLV);

        //封装以太网帧,但是需要判断是发送LDDP还是BDDP
        Ethernet ethernet;
        if (isStandard) {
            ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHwAddr())
                    .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING)
                    .setEtherType(EthType.LLDP);
            ethernet.setPayload(lldp);
        } else {
            BSN bsn = new BSN(BSN.BSN_TYPE_BDDP);
            bsn.setPayload(lldp);

            ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHwAddr())
                    .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING)
                    .setEtherType(EthType.of(Ethernet.TYPE_BSN & 0xffff)); /* treat as unsigned */
            ethernet.setPayload(bsn);
        }

        //将LLDP信息封装到packetout信息中,并这只action
        // serialize and wrap in a packet out
        byte[] data = ethernet.serialize();
        OFPacketOut.Builder pob = iofSwitch.getOFFactory().buildPacketOut()
        .setBufferId(OFBufferId.NO_BUFFER)
        .setActions(getDiscoveryActions(iofSwitch, port))
        .setData(data);
        OFMessageUtils.setInPort(pob, OFPort.CONTROLLER);

        log.debug("{}", pob.build());
        return pob.build();
    }

这就是控制生成LLDP和BDDP的代码,最后封装成PACK_OUT消息。让我们通过图片形象的看看LLDP的格式:
SDN控制器Floodlight源码学习(六)--链路发现模块(LinkDiscovery)_第3张图片
SDN控制器Floodlight源码学习(六)--链路发现模块(LinkDiscovery)_第4张图片
其中ClassisId TLV,Port TLV,TTL TLV是LLDP规定的必须的三个TLV,而后面的TLV则是由控制器生成的,其中Dpid TLV主要存放交换机的dpid和outgoing portId,Controller TLV存在当前控制器的id,timestamp TLV存在控制器发出LLDP的系统时间,用于测量发出LDDP到收到LLDP的延时。
从上面代码还可以看出,LLDP和BDDP区别在于以太网帧中目的地址的不同,LLDP使用的组播地址,BDDP使用广播地址。
好了我们再说说LinkDiscoveryManager维护的四个队列:

protected LinkedBlockingQueue quarantineQueue;
    protected LinkedBlockingQueue maintenanceQueue;
    protected LinkedBlockingQueue toRemoveFromQuarantineQueue;
    protected LinkedBlockingQueue toRemoveFromMaintenanceQueue;

我先说说这四个队列的作用,便于读后面代码得理解.
quarantineQueue:放入这个队列中的端口,会被遍历并发送BDDP消息。
maintenanceQueue:控制器在向每个端口发送了LLDP后,会把端口放入这个队列,之后会从这个队列中取出来发送BDDP。
toRemoveFromQuarantineQueue:从quarantineQueue移出的端口放入这个队列.
toRemoveFromMaintenanceQueue:从maintenanceQueue移出的端口放入这个队列.
这样说可能有些不好理解,我们先有个映像,后面看了代码就好理解了,这四个队列主要是为了控制哪些端口发送LLDP,哪些发送BDDP。可以这样理解:先发送LLDP,如果收到相同的LLDP,则不再发送BDDP,若没有收到LLDP,则发送BDDP。
下面我们先看看LLDP的发送:
LLDP的发送分为两个方面,第一个方面是链路发现模块启动后,通过线程周期的轮训所有的交换机的所有Enable端口进行发送。第二个方面是在新来的交换机亦或者交换机端口改变时进行发送.(后两个通过注册IOFSwitchListener实现)
我们先看第一方面:

// To be started by the first switch connection
        discoveryTask = new SingletonTask(ses, new Runnable() {
            @Override
            public void run() {
                try {
                    if (role == null || role == HARole.ACTIVE) { /* don't send if we just transitioned to STANDBY */
                        discoverLinks();
                    }
                } catch (StorageException e) {
                    shutdownService.terminate("Storage exception in LLDP send timer. Terminating process " + e, 0);
                } catch (Exception e) {
                    log.error("Exception in LLDP send timer.", e);
                } finally {
                    if (!shuttingDown) {
                        // null role implies HA mode is not enabled.
                        if (role == null || role == HARole.ACTIVE) {
                            log.trace("Rescheduling discovery task as role = {}",
                                    role);
                            discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL,
                                    TimeUnit.SECONDS);
                        } else {
                            log.trace("Stopped LLDP rescheduling due to role = {}.",
                                    role);
                        }
                    }
                }
            }
        });
        protected void discoverLinks() {

        // timeout known links.
        timeoutLinks();

        // increment LLDP clock
        lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL;

        if (lldpClock == 0) {
            if (log.isTraceEnabled())
                log.trace("Sending LLDP out on all ports.");
            discoverOnAllPorts();
        }
    }
    protected void discoverOnAllPorts() {
        log.info("Sending LLDP packets out of all the enabled ports");
        // Send standard LLDPs
        for (DatapathId sw : switchService.getAllSwitchDpids()) {
            IOFSwitch iofSwitch = switchService.getSwitch(sw);
            if (iofSwitch == null) continue;
            if (!iofSwitch.isActive()) continue; /* can't do anything if the switch is SLAVE */
            Collection c = iofSwitch.getEnabledPortNumbers();
            if (c != null) {
                for (OFPort ofp : c) {
                    if (isLinkDiscoverySuppressed(sw, ofp)) {           
                        continue;
                    }
                    log.trace("Enabled port: {}", ofp);
                    sendDiscoveryMessage(sw, ofp, true, false);

                    // If the switch port is not already in the maintenance
                    // queue, add it.
                    NodePortTuple npt = new NodePortTuple(sw, ofp);
                    addToMaintenanceQueue(npt);
                }
            }
        }
    }
protected boolean sendDiscoveryMessage(DatapathId sw, OFPort port,
            boolean isStandard, boolean isReverse) {

        // Takes care of all checks including null pointer checks.
        if (!isOutgoingDiscoveryAllowed(sw, port, isStandard, isReverse)) {
            return false;
        }

        IOFSwitch iofSwitch = switchService.getSwitch(sw);
        if (iofSwitch == null) { // fix dereference violations in case race conditions
            return false;
        }

        return iofSwitch.write(generateLLDPMessage(iofSwitch, port, isStandard, isReverse));
    }

上面的代码是我截取的,看一清晰的看出调用关系:
任务线程中–>discoverLinks–>discoverOnAllPorts–>sendDiscoveryMessage
可以看到,在对所有交换机合法的所有端口发送了LLDP消息后,将端口信息放入maintenanceQueue。

接下来我们看看第二方面:

@Override
    public void switchActivated(DatapathId switchId) {
        IOFSwitch sw = switchService.getSwitch(switchId);
        if (sw == null)       //fix dereference violation in case race conditions
            return;
        if (sw.getEnabledPortNumbers() != null) {
            for (OFPort p : sw.getEnabledPortNumbers()) {
                processNewPort(sw.getId(), p);
            }
        }
        LDUpdate update = new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_UPDATED);
        updates.add(update);
    }
@Override
    public void switchPortChanged(DatapathId switchId,
            OFPortDesc port,
            PortChangeType type) {

        switch (type) {
        case UP:
            processNewPort(switchId, port.getPortNo());
            break;
        case DELETE: case DOWN:
            handlePortDown(switchId, port.getPortNo());
            break;
        case OTHER_UPDATE: case ADD:
            // This is something other than port add or delete.
            // Topology does not worry about this.
            // If for some reason the port features change, which
            // we may have to react.
            break;
        }
    }
    private void processNewPort(DatapathId sw, OFPort p) {
        if (isLinkDiscoverySuppressed(sw, p)) {
            // Do nothing as link discovery is suppressed.
            return;
        }

        IOFSwitch iofSwitch = switchService.getSwitch(sw);
        if (iofSwitch == null) {
            return;
        }

        NodePortTuple npt = new NodePortTuple(sw, p);
        discover(sw, p);
        addToQuarantineQueue(npt);
    }
    protected void discover(DatapathId sw, OFPort port) {
        sendDiscoveryMessage(sw, port, true, false);
    }

从上面也可以清晰看出调用关系:
switchActivated–>processNewPort–>discover–>sendDiscoveryMessage
switchPortChanged–>processNewPort–>discover–>sendDiscoveryMessage
同时值得看到的是,在发送LLDP消息后,把端口信息放入QuarantineQueue中.

接下来我们继续看,在发送了LLDP后,控制器是怎么样来处理收到的PACK_IN消息,并获取链路信息的:

private Command handleLldp(LLDP lldp, DatapathId sw, OFPort inPort,
            boolean isStandard, FloodlightContext cntx) {
        // If LLDP is suppressed on this port, ignore received packet as well
        IOFSwitch iofSwitch = switchService.getSwitch(sw);

        log.debug("Received LLDP packet on sw {}, port {}", sw, inPort);

        //判断从这个端口来的LLDP是否被抑制
        if (!isIncomingDiscoveryAllowed(sw, inPort, isStandard))
            return Command.STOP;

        // If this is a malformed LLDP exit
        if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) {
            return Command.STOP;
        }

        //获取当前控制的id
        long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong();
        long otherId = 0;
        boolean myLLDP = false;
        Boolean isReverse = null;

        ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue());
        portBB.position(1);

        OFPort remotePort = OFPort.of(portBB.getShort());
        IOFSwitch remoteSwitch = null;
        long timestamp = 0;

        //遍历LLDP的可变部分
        // Verify this LLDP packet matches what we're looking for
        for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) {
            //找到转发这个LLDP的交换机
            if (lldptlv.getType() == 127 && lldptlv.getLength() == 12
                    && lldptlv.getValue()[0] == 0x0
                    && lldptlv.getValue()[1] == 0x26
                    && lldptlv.getValue()[2] == (byte) 0xe1
                    && lldptlv.getValue()[3] == 0x0) {
                ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue());
                remoteSwitch = switchService.getSwitch(DatapathId.of(dpidBB.getLong(4)));
            } else if (lldptlv.getType() == 127 && lldptlv.getLength() == 12
                    && lldptlv.getValue()[0] == 0x0
                    && lldptlv.getValue()[1] == 0x26
                    && lldptlv.getValue()[2] == (byte) 0xe1
                    && lldptlv.getValue()[3] == 0x01) { /* 0x01 for timestamp */
                ByteBuffer tsBB = ByteBuffer.wrap(lldptlv.getValue()); /* skip OpenFlow OUI (4 bytes above) */
                long swLatency = iofSwitch.getLatency().getValue();
                timestamp = tsBB.getLong(4); /* include the RX switch latency to "subtract" it */
                if (log.isTraceEnabled()) {
                    log.trace("RECEIVED LLDP LATENCY TLV: Got timestamp of {}; Switch {} latency of {}", new Object[] { timestamp, iofSwitch.getId(), iofSwitch.getLatency().getValue() }); 
                }
                timestamp = timestamp + swLatency;
                //获取发送这个LLDP的controller的 id
            } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8) {
                otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong();
                if (myId == otherId) myLLDP = true;
            } else if (lldptlv.getType() == TLV_DIRECTION_TYPE
                    && lldptlv.getLength() == TLV_DIRECTION_LENGTH) {
                if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0])
                    isReverse = false;
                else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0])
                    isReverse = true;
            }
        }

        //如果收到的lldp不是由本controller发出的话 
        if (myLLDP == false) {
            // This is not the LLDP sent by this controller.
            // If the LLDP message has multicast bit set, then we need to
            // broadcast the packet as a regular packet (after checking IDs)
            //如果收到的是标准的LLDP则不出来
            if (isStandard) {
                if (log.isTraceEnabled()) {
                    log.trace("Got a standard LLDP=[{}] that was not sent by" +
                            " this controller. Not fowarding it.", lldp.toString());
                }
                return Command.STOP;
            //如果当前收到的是BDDP,同时当前controller ID小于发出BDDP的ID,则继续处理
            } else if (myId < otherId) {
                if (log.isTraceEnabled()) {
                    log.trace("Getting BDDP packets from a different controller"
                            + "and letting it go through normal processing chain.");
                }
                return Command.CONTINUE;
            }
            return Command.STOP;
        }

        if (remoteSwitch == null) {
            // Ignore LLDPs not generated by Floodlight, or from a switch that
            // has recently
            // disconnected, or from a switch connected to another Floodlight
            // instance
            if (log.isTraceEnabled()) {
                log.trace("Received LLDP from remote switch not connected to the controller");
            }
            return Command.STOP;
        }

        if (!remoteSwitch.portEnabled(remotePort)) {
            if (log.isTraceEnabled()) {
                log.trace("Ignoring link with disabled source port: switch {} port {} {}",
                        new Object[] { remoteSwitch.getId().toString(),
                        remotePort,
                        remoteSwitch.getPort(remotePort)});
            }
            return Command.STOP;
        }
        if (suppressLinkDiscovery.contains(new NodePortTuple(
                remoteSwitch.getId(),
                remotePort))) {
            if (log.isTraceEnabled()) {
                log.trace("Ignoring link with suppressed src port: switch {} port {} {}",
                        new Object[] { remoteSwitch.getId().toString(),
                        remotePort,
                        remoteSwitch.getPort(remotePort)});
            }
            return Command.STOP;
        }
        if (!iofSwitch.portEnabled(inPort)) {
            if (log.isTraceEnabled()) {
                log.trace("Ignoring link with disabled dest port: switch {} port {} {}",
                        new Object[] { sw.toString(),
                        inPort.getPortNumber(),
                        iofSwitch.getPort(inPort).getPortNo().getPortNumber()});
            }
            return Command.STOP;
        }

        // Store the time of update to this link, and push it out to
        // routingEngine
        long time = System.currentTimeMillis();
        U64 latency = (timestamp != 0 && (time - timestamp) > 0) ? U64.of(time - timestamp) : U64.ZERO;
        if (log.isTraceEnabled()) {
            log.trace("COMPUTING FINAL DATAPLANE LATENCY: Current time {}; Dataplane+{} latency {}; Overall latency from {} to {} is {}", 
                    new Object[] { time, iofSwitch.getId(), timestamp, remoteSwitch.getId(), iofSwitch.getId(), String.valueOf(latency.getValue()) });
        }
        Link lt = new Link(remoteSwitch.getId(), remotePort,
                iofSwitch.getId(), inPort, latency);

        if (!isLinkAllowed(lt.getSrc(), lt.getSrcPort(),
                lt.getDst(), lt.getDstPort()))
            return Command.STOP;

        // Continue only if link is allowed.
        Date lastLldpTime = null;
        Date lastBddpTime = null;

        Date firstSeenTime = new Date(System.currentTimeMillis());

        if (isStandard) {
            lastLldpTime = new Date(firstSeenTime.getTime());
        } else {
            lastBddpTime = new Date(firstSeenTime.getTime());
        }

        LinkInfo newLinkInfo = new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime);

        //保存链路信息
        addOrUpdateLink(lt, newLinkInfo);

        // Check if reverse link exists.
        // If it doesn't exist and if the forward link was seen
        // first seen within a small interval, send probe on the
        // reverse link.
        newLinkInfo = links.get(lt);
        if (newLinkInfo != null && isStandard && isReverse == false) {
            Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
                    lt.getSrc(), lt.getSrcPort(), U64.ZERO); /* latency not used; not important what the value is, since it's intentionally not in equals() */
            LinkInfo reverseInfo = links.get(reverseLink);
            if (reverseInfo == null) {
                // the reverse link does not exist.
                if (newLinkInfo.getFirstSeenTime().getTime() > System.currentTimeMillis()
                        - LINK_TIMEOUT) {
                    log.debug("Sending reverse LLDP for link {}", lt);
                    this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(),
                            isStandard, true);
                }
            }
        }

        // If the received packet is a BDDP packet, then create a reverse BDDP
        // link as well.
        if (!isStandard) {
            Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
                    lt.getSrc(), lt.getSrcPort(), latency);

            // srcPortState and dstPort state are reversed.
            LinkInfo reverseInfo = new LinkInfo(firstSeenTime, lastLldpTime,
                    lastBddpTime);

            addOrUpdateLink(reverseLink, reverseInfo);
        }

        // Queue removal of the node ports from the quarantine and maintenance queues.
        NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(),
                lt.getSrcPort());
        NodePortTuple nptDst = new NodePortTuple(lt.getDst(),
                lt.getDstPort());

        flagToRemoveFromQuarantineQueue(nptSrc);
        flagToRemoveFromMaintenanceQueue(nptSrc);
        flagToRemoveFromQuarantineQueue(nptDst);
        flagToRemoveFromMaintenanceQueue(nptDst);

        // Consume this message
        ctrLldpEol.increment();
        return Command.STOP;
    }

LinkDiscoveryManager因为实现了IOFMessageListener,所以它能够处理进入控制器的消息,LinkDiscoveryManager将PACK_IN解包后,获得LLDP消息,进入上面handlelldp的代码中.通过获取LLDP中src_dpId和src_portId以及发送PACK_IN消息的dst_dpId和dst_portId建立起链路的关系,存入本地内存中的数据结构中。在上面代码得最后,将源端口和终点端口放入toRemoveFromQuarantineQueue和
toRemoveFromMaintenanceQueue,这样控制器就不会再向这两个端口推送BDDP消息了。
说了这么多,我们在看看BDDP消息是怎么推送的:

protected void processBDDPLists() {
        int count = 0;
        Set nptList = new HashSet();

        while (count < BDDP_TASK_SIZE && quarantineQueue.peek() != null) {
            NodePortTuple npt;
            npt = quarantineQueue.remove();
            /*
             * Do not send a discovery message if we already have received one
             * from another switch on this same port. In other words, if
             * handleLldp() determines there is a new link between two ports of
             * two switches, then there is no need to re-discover the link again.
             * 
             * By flagging the item in handleLldp() and waiting to remove it 
             * from the queue when processBDDPLists() runs, we can guarantee a 
             * PORT_STATUS update is generated and dispatched below by
             * generateSwitchPortStatusUpdate().
             */
            if (!toRemoveFromQuarantineQueue.remove(npt)) {
                sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false);
            }
            /*
             * Still add the item to the list though, so that the PORT_STATUS update
             * is generated below at the end of this function.
             */
            nptList.add(npt);
            count++;
        }

        count = 0;
        while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) {
            NodePortTuple npt;
            npt = maintenanceQueue.remove();
            /*
             * Same as above, except we don't care about the PORT_STATUS message; 
             * we only want to avoid sending the discovery message again.
             */
            if (!toRemoveFromMaintenanceQueue.remove(npt)) {
                sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false);
            }
            count++;
        }

        for (NodePortTuple npt : nptList) {
            generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId());
        }
    }

可以看出,控制器是通过quarantineQueue和maintenanceQueue队列中取出端口,并查看这些端口是否存在于toRemoveFromMaintenanceQueue和toRemoveFromQuarantineQueue当中,如果存在则不发送BDDP,不存在则发送BDDP。
好了,至此我们已经大致把这个模块里面主要功能理解了下,接下来我们需要探索下,基于目前链路的信息,控制器是如何生成拓扑的。

你可能感兴趣的:(源码学习,源码,通信)