LLDP在ODL中的实现及源码分析(一)

本文中主要是与大家分享一下LLDP在ODL中的实现以及其源码分析,主要内容涉及ODL控制器中LLDP帧的产生及发送。文章都是个人理解,希望能够帮助到大家,更希望可以一起讨论看法不一的地方。

1 LLDP简介

ODL使用LLDP实现链路检测。LLDP,即链路层发现协议,是一种数据链路层协议,网络设备可以通过在本地网络中发送LLDPDU(Link Layer Discovery Protocol Data Unit)来通告其他设备自身的状态,是一种能够使网络中的设备互相发现并通告状态、交互信息的协议。

LLDP的以太网类型为0x88cc,一个标准的LLDP帧格式如图1:

图1

LLDPDU格式如图2:

图2

2 ODL中的LLDP

在ODL中,发送LLDP帧的工作由控制器来完成。如图3所示:

图3

在上图中,LLDP帧在控制器中生成,通过openflow协议packet_out消息发给交换机1,交换机1收到来自控制器的LLDP帧后,会转发给直邻交换机2,其直邻交换机2收到LLDP帧后,通过openflow的packet_in消息发给控制器,控制器据此做链路检测。

3 LLDP帧的产生和发送

LLDP帧产生和发送的源码位于ODL中openflowplugin子项目中,具体位置为:openflowplugin/applications/lldp-speaker,其源码目录如下图4:

图4

该模块主要分为两个功能:

♣ 监听MA-SAL中的inventory data tree中的端口信息,学习并检测端口信息的变化,产生与端口一一对应的LLDP帧,然后将其存入本地哈希nodeConnectorMap中,此哈希表键为端口IID,值为端口对应LLDP帧;

♣ 周期性遍历本地哈希表nodeConnectorMap,将哈希表中的LLDP帧通过packet_out消息发往相应的交换机。

3.1 监听端口信息

源码位于NodeConnectorInventoryEventTranslator.java中,首先构造监听节点的IID,源码如下:

//监听inventory data tree 下openflow的端口状态
    private static final InstanceIdentifier<State> II_TO_STATE 
        = InstanceIdentifier.builder(Nodes.class)
            .child(Node.class)
            .child(NodeConnector.class)
            .augmentation(FlowCapableNodeConnector.class)
            .child(State.class)
            .build();
    //监听inventory data tree 下openflow的端口
    private static final InstanceIdentifier<FlowCapableNodeConnector> II_TO_FLOW_CAPABLE_NODE_CONNECTOR
        = InstanceIdentifier.builder(Nodes.class)
            .child(Node.class)
            .child(NodeConnector.class)
            .augmentation(FlowCapableNodeConnector.class)
            .build();

由IID在MD-SAL中注册监听器,实现数据监听,源码如下:

//监听端口
dataChangeListenerRegistration = dataBroker.registerDataChangeListener(
                LogicalDatastoreType.OPERATIONAL,
                II_TO_FLOW_CAPABLE_NODE_CONNECTOR,
                this, AsyncDataBroker.DataChangeScope.BASE);
//监听端口状态
        listenerOnPortStateRegistration = dataBroker.registerDataChangeListener(
                LogicalDatastoreType.OPERATIONAL,
                II_TO_STATE,
                this, AsyncDataBroker.DataChangeScope.SUBTREE);

当监听到端口信息变化之后,需要依据端口状态,判定是否生成相应的LLDP帧。若需要生成LLDP帧,则生成之后存入本地哈希表nodeConnectorMap中,具体源码位于函数onDataChanged之中。即如下:

public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change)  {...}

其中对于change.getCreatedData(),其源码如下:

for (Map.Entry<InstanceIdentifier<?>, DataObject> entry : change.getCreatedData().entrySet()) {
            InstanceIdentifier<NodeConnector> nodeConnectorInstanceId =
                    entry.getKey().firstIdentifierOf(NodeConnector.class);
            if (compareIITail(entry.getKey(),II_TO_FLOW_CAPABLE_NODE_CONNECTOR)) {
                FlowCapableNodeConnector flowConnector = (FlowCapableNodeConnector) entry.getValue();
                if (!isPortDown(flowConnector)) {
                    notifyNodeConnectorAppeared(nodeConnectorInstanceId, flowConnector);  //如果端口开启
                } else {
                    iiToDownFlowCapableNodeConnectors.put(nodeConnectorInstanceId, flowConnector); //
                }
            }
        }

代码分析:对于change.getCreatedData(),即为datastore中新增端口,首先调用函数isPortDown(flowConnector)判断端口此时状态:若返回true,端口为UP,直接生成端口对应LLDP帧,然后存入哈希表nodeConnectorMap;若返回false,端口为DOWN,则将端口存入哈希表iiToDownFlowCapableNodeConnectors,之后如果端口状态变为UP,依然会生成对应LLDP帧,存入哈希表nodeConnectorMap中。

此处说明一下,端口为down,一般指端口或者端口处link存在问题,无法正常转发数据包。

对于change.getUpdatedData(),其源码如下:

// Iterate over updated node connectors (port down state may change)
        for (Map.Entry<InstanceIdentifier<?>, DataObject> entry : change.getUpdatedData.entrySet()) {
            InstanceIdentifier<NodeConnector> nodeConnectorInstanceId =
                    entry.getKey().firstIdentifierOf(NodeConnector.class);
            if (compareIITail(entry.getKey(),II_TO_FLOW_CAPABLE_NODE_CONNECTOR)) {
                FlowCapableNodeConnector flowConnector = (FlowCapableNodeConnector) entry.getValue();
                if (isPortDown(flowConnector)) {
                    notifyNodeConnectorDisappeared(nodeConnectorInstanceId);
                } else {
                    notifyNodeConnectorAppeared(nodeConnectorInstanceId, flowConnector);
                }
            } else if (compareIITail(entry.getKey(),II_TO_STATE)) {
                FlowCapableNodeConnector flowNodeConnector = iiToDownFlowCapableNodeConnectors.get(nodeConnectorInstanceId);
                if (flowNodeConnector != null) {
                    State state = (State)entry.getValue();
                    if (!state.isLinkDown()) {
                        FlowCapableNodeConnectorBuilder flowCapableNodeConnectorBuilder = new FlowCapableNodeConnectorBuilder(flowNodeConnector);
                        flowCapableNodeConnectorBuilder.setState(state);
                        notifyNodeConnectorAppeared(nodeConnectorInstanceId, flowCapableNodeConnectorBuilder.build());
                        iiToDownFlowCapableNodeConnectors.remove(nodeConnectorInstanceId);
                    }
                }
            }
        }

代码分析:对于更新数据为端口,调用函数isPortDown(flowConnector),判断端口状态:若端口为UP,则直接调用函数notifyNodeConnectorAppeared(nodeConnectorInstanceId, flowConnector);存入哈希表nodeConnectorMap;若端口为DOWN,则调用函数notifyNodeConnectorDisappeared(nodeConnectorInstanceId),从哈希表nodeConnectorMap中移除。

若更新数据为端口状态,则检查其对应端口是否在哈希表iiToDownFlowCapableNodeConnectors中,若存在且其端口链路状态为true,则生成此端口对应LLDP帧,存入哈希表nodeConnectorMap中,并且将其从哈希表iiToDownFlowCapableNodeConnectors中移出。

对应change.getRemovedPaths(),源码如下:

// Iterate over removed node connectors
        for (InstanceIdentifier<?> removed : change.getRemovedPaths()) {
            if (compareIITail(removed,II_TO_FLOW_CAPABLE_NODE_CONNECTOR)) {
                InstanceIdentifier<NodeConnector> nodeConnectorInstanceId = removed.firstIdentifierOf(NodeConnector.class);
                notifyNodeConnectorDisappeared(nodeConnectorInstanceId);
            }
        }
    }

代码分析:对于删除的端口,则直接将其从哈希表nodeConnectorMap中删除即可。

3.2 LLDP帧生成

根据前文分析,当检测到新的端口时,会调用函数notifyNodeConnectorAppeared先生成对应LLDP帧,然后存入哈希表nodeConnectorMap中。其中生成LLDP帧源码如下:

// Generate packet with destination switch and port
        TransmitPacketInput packet = new TransmitPacketInputBuilder()
                .setEgress(new NodeConnectorRef(nodeConnectorInstanceId))
                .setNode(new NodeRef(nodeInstanceId))
                .setPayload(LLDPUtil.buildLldpFrame(
                        nodeId, nodeConnectorId, srcMacAddress, outputPortNo, addressDestionation)).build();

代码分析:其中负责生成LLDP以太网帧的函数为

LLDPUtil.buildLldpFrame(nodeId, nodeConnectorId, srcMacAddress, outputPortNo, addressDestionation)

此函数在LLDPUtil.java文件中实现,主要逻辑是依次生成LLDPDU中的ChassisID TLV,PortID TLV,TTL TLV等字段,然后添加LLDP帧的以太网包头。由于代码逻辑比较清晰,此处不再详细叙述。

3.3 LLDP帧周期性发送

ODL中会周期性的发送LLDP帧,去检测网络设备间link的有效性。LLDP帧周期性发送在LLDPSpeaker.java中实现,采用以下代码实现周期性的执行任务:

private final ScheduledExecutorService scheduledExecutorService;
private ScheduledFuture<?> scheduledSpeakerTask;
scheduledSpeakerTask = this.scheduledExecutorService.scheduleAtFixedRate(this, LLDP_FLOOD_PERIOD,LLDP_FLOOD_PERIOD, TimeUnit.SECONDS);

其中LLDP_FLOOD_PERIOD值为5,即每过5秒会将哈希表nodeConnectorMap中所有的LLDP帧发送给相应交换机。

函数setOperationalStatus将会设置LLDP机制的工作状态,RUN表示运行状态,即会周期性的发送LLDP帧,进行链路检测;STANDBY表示待命状态,即没有开启LLDP链路检测,不会周期性的发送LLDP帧。源码如下:

public void setOperationalStatus(final OperStatus operationalStatus) {
        LOG.info("Setting operational status to {}", operationalStatus);
        this.operationalStatus = operationalStatus;
        if (operationalStatus.equals(OperStatus.STANDBY)) {
            scheduledSpeakerTask.cancel(false);  //参数false表示,如果任务在执行,则等待任务结束后再取消;若为参数true表示立即取消
        } else if (operationalStatus.equals(OperStatus.RUN)) {
//如果没有任务,或者任务已经取消,则开启定时任务
            if (scheduledSpeakerTask == null || scheduledSpeakerTask.isCancelled()) {
                scheduledSpeakerTask = this.scheduledExecutorService.scheduleAtFixedRate(this, LLDP_FLOOD_PERIOD,
                        LLDP_FLOOD_PERIOD, TimeUnit.SECONDS);           
 }
        }
}

遍历哈希表nodeConnectorMap,发送LLDP帧的操作在run()函数完成中,其中是调用rpc方法transmit-packet发送LLDP帧,此rpc在packet-processing.yang中被定义。

@Override
    public void run() {
        if (OperStatus.RUN.equals(operationalStatus)) {
            LOG.debug("Sending LLDP frames to {} ports...", nodeConnectorMap.keySet().size()); //发送端口的数量
            for (InstanceIdentifier<NodeConnector> nodeConnectorInstanceId : nodeConnectorMap.keySet()) {
                NodeConnectorId nodeConnectorId = InstanceIdentifier.keyOf(nodeConnectorInstanceId).getId();
                LOG.trace("Sending LLDP through port {}", nodeConnectorId.getValue());  //发送端口的端口号
                packetProcessingService.transmitPacket(nodeConnectorMap.get(nodeConnectorInstanceId)); //调用rpc方法transmit-packet发送LLDP包
            }
        }
    }

由于篇幅有限,源码只选择重要部分粘贴,完整源码请从以下链接下载:

https://github.com/opendaylight/openflowplugin/tree/stable/beryllium。

四、总结

上文已经具体分析了LLDP帧的生成和周期发送的逻辑以及关键代码。当LLDP帧发送,经过转发再次被发给控制器时,控制器需要从中提取出链路信息,并以此检测已经发现的链路是否老化,以及是否发现新的链路。这部分的源码将在下一篇文章《LLDP在ODL中的实现及源码分析(二)》中分析。

原文发布于微信公众号 - SDNLAB(SDNLAB)

原文发表时间:2016-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员宝库

PHP 面试知识梳理

算法与数据结构 BTree和B+tree BTree B树是为了磁盘或者其他存储设备而设计的一种多叉平衡查找树,相对于二叉树,B树的每个内节点有多个分支,即多叉...

43012
来自专栏Adamshuang 技术文章

Guava Cache -- Java 应用缓存神器

Guava 作为Google开源Java 库中的精品成员,在性能、功能上都十分出色,本文将从实际使用的角度,来对Guava进行讲解。

3.2K7
来自专栏美团技术团队

不可不说的Java“锁”事

Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景进行举例,为...

1412
来自专栏java一日一条

Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边...

802
来自专栏大内老A

WCF服务端运行时架构体系详解[中篇]

在这篇文章中,我们对信道分发器本身作一个深入的了解,首先来看看它具有哪些可供扩展的组件,以及我们可以针对信道分发器对WCF实现哪些可能的扩展。 目录: ...

20310
来自专栏蓝天

Linux系统面面观 PROC文件系统详细介绍

什么是proc文件系统? proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。用户和应用...

1232
来自专栏大数据和云计算技术

hdfs auditlog(审计日志)

hdfs审计日志(Auditlog)记录了用户针对hdfs的所有操作,详细信息包括操作成功与否、用户名称、客户机地址、操作命令、操作的目录等。对于用户的每一个...

2963
来自专栏我爱编程

运维开发笔试

all(iterable) and any(iterable) all(x)如果all(x)参数x对象的所有元素不为0、''、False或者x为空对象(即所有...

2423
来自专栏开源优测

RFC1945 超文本传输协议--HTTP/1.0 之一

1172
来自专栏Golang语言社区

Go语言·我的性能我做主

对于一些服务来说,性能是极其重要的一环,事关系统的吞吐、访问的延迟,进而影响用户的体验。 写性能测试在Go语言中是很便捷的,go自带的标准工具链就有完善的支持,...

37210

扫码关注云+社区

领取腾讯云代金券