(一)深入Openflowplugin源码分析Openflow握手过程

(一)深入Openflowplugin源码分析Openflow握手过程

2019-06-12 陈卓文

可直接点击上方蓝字

(网易游戏运维平台)

关注我们,获一手游戏运维方案

 

陈卓文

 

陈卓文,网易游戏高级开发工程师,负责网易游戏私有云平台底层 SDN/NFV 网络的开发工作。

前言

随着云计算的火热,SDN/NFV,Openflow 等名词频繁出现。然而在网络上,有很多介绍名词概念等博文,本人深入云底层 SDN/NFV 网络开发过程,很少能够找到足够深入的文章学习。在学习过程中总结几篇文章,深入分析 Opendaylight openflowlugin 底层源码,希望对相关云底层 SDN/NFV 网络开发人员有所帮助。

本系列文章基于 Openflowplugin 版本 0.6.2。本文为第一篇,分析 Openflow 节点连上控制器过程中 openflow 协议的握手过程。

Opendaylight

在我们的架构中,我们采用了 Opendaylight 作为我们开发的底层框架。其作为一个成熟的开源社区,Opendaylight 良好的框架使它能够支持各种协议的南向插件,比如 openflow, netconf, ovsdb, bgp 等。在我们 SDN 网络控制面,我们采用了其南向 openflow 协议插件 openflowplugin 连接我们的 openflow 转发节点。Opendaylight 架构图:

我们使用 Opendaylight 南向插件 Openflowplugin 作为我们 SDN 控制层的南向通道框架。底层 Openflow 转发节点与控制层 Openflow server 之间会建立协议通道,本文分析 Openflowplugin 是如何实现 Openflow 协议通道建立的握手,从实现上理解 Openflow 的握手细节。

Openflowplugin Handshake 源码分析

Handshake 过程

在 Openflowplugin 启动过程中,

SwitchConnectionProviderImpl.startup

会启动 tcp server 监听端口。

而 tcp server 是基于 netty 实现,

TcpHandler.java会创建 Bootstrap/EventLoopGroup 等,同样会设置 channelInitialize。

当 switch 底层连上控制器 tcp server 监听的端口 6633/6653,netty 在接受 channel 后,会调用 channelInitialize 的 initChannel 方法,

TcpChannelInitializer.initChannel

关于 netty,可以参考《netty in action》,推荐阅读。

1.初始化 Channel

当 switch 通过 tcp 连接上控制器,

会触发TcpChannelInitializer.initChannel方法初始化 channel。

在 initchannel 方法中主要逻辑:

1. 创建ConnectionAdapterImpl对象,

封装SocketChannel channel 对象。

 

connectionFacade = connectionAdapterFactory.createConnectionFacade(ch, null, useBarrier(), getChannelOutboundQueueSize());

会为每个 connection(switch) 

创建一个ConnectionAdapterImpl对象,

此对象是封装底层 switch 的关键对象,上层通过此对象与 switch 通信。从变量名 Facade 也能推敲出此对象的作用。

2. 调用ConnectionManagerImpl.onSwitchConnected方法,传参传入的是ConnectionAdapterImpl对象。

 

getSwitchConnectionHandler().onSwitchConnected(connectionFacade);

而在ConnectionManagerImpl.onSwitchConnected的处理是给ConnectionAdapterImpl对象设置 3 个 listener,用于处理底层各个事件。

  • 1、创建ConnectionReadyListenerImpl对象给ConnectionAdapterImpl对象传入引用(setConnectionReadyListener);

  • 1.1、ConnectionReadyListenerImpl对象封装ConnectionContextImplHandshakeContextImpl

  • 1.2、ConnectionReadyListenerImpl对象提供onConnectionReady()方法,该方法处理是调用HandshakeManagerImpl.shake()

  • 2、创建OpenflowProtocolListenerInitialImpl对象,给ConnectionAdapterImpl对象传入引用(setMessageListener);

  • 2.1、OpenflowProtocolListenerInitialImpl对象用于处理底层 switch 发给控制器的消息,比如提供onHelloMessage方法。

  • 2.2、注意:该对象仅用于处理 handshake 过程中涉及的基本消息,在 handshake 后会被另一对象OpenflowProtocolListenerFullImpl替换。

  • 3、创建SystemNotificationsListenerImpl对象,给ConnectionAdapterImpl对象传入引用(setSystemListener

  • 3.1、SystemNotificationsListenerImpl对象用于处理 SwitchIdleEvent 和 DisconnectEvent 事件。提供onSwitchIdleEvent()方法, 当 swich idle 发送 echo 心跳消息;提供onDisconnectEvent方法处理 disconnect

3. 给 channel.pipeline 设置 ChannelHandler。

会给 channel 的 pipeline 对象传入 ChannelHandler 对象,用于处理 channel idle/inactive、处理 openflow 消息编码解码等。

pipeline 是 netty 针对数据流处理的设计,具体参考《netty in action》

4. 调用

ConnectionAdapterImpl.fireConnectionReadyNotification()方法发起 handshake。

TcpChannelInitializer.initChannel方法中,

可以看到无论是否开启 tls,

最终都会调用

ConnectionAdapterImpl.fireConnectionReadyNotification()方法:

1. 开 tls

 

final ConnectionFacade finalConnectionFacade = connectionFacade;
handshakeFuture.addListener(future -> finalConnectionFacade.fireConnectionReadyNotification());

2. 没开 tls

 

if (!tlsPresent) {
    connectionFacade.fireConnectionReadyNotification();
}

而上面两个代码片段的 connectionFacade 变量正是 ConnectionAdapterImpl 对象。

其 fireConnectionReadyNotification()方法如下:

 

    @Override
    public void fireConnectionReadyNotification() {
        versionDetector = (OFVersionDetector) channel.pipeline().get(PipelineHandlers.OF_VERSION_DETECTOR.name());
        Preconditions.checkState(versionDetector != null);

        new Thread(() -> connectionReadyListener.onConnectionReady()).start();
    }

可以看到

fireConnectionReadyNotification()方法

实际是调用

connectionReadyListener.onConnectionReady()

connectionReadyListener变量

正是上面第二步中调用

setConnectionReadyListener

传入的ConnectionReadyListenerImpl对象。

即分配新的线程执行

ConnectionReadyListenerImpl.onConnectionReady()

onConnectionReady()方法会触发 handshake,在下面开展。

总结

可以看到在 Tcp channel 初始化时

(TcpChannelInitializer.initChannel),会:

  • 创建ConnectionAdapterImpl对象,

    封装传入的SocketChannel channel对象;

  • 调用

    ConnectionManagerImpl.onSwitchConnected方法,

    ConnectionAdapterImpl对象

    setConnectionReadyListenersetMessageListenersetSystemListener

  • 给 pipeline 设置各种 channelHandler

  • 调用

    ConnectionAdapterImpl.fireConnectionReadyNotification()发起 handshake;

2.ConnectionReady 开始 Handshake

TcpChannelInitializer.initChannel最后,调用ConnectionReadyListenerImpl.onConnectionReady()如下:

onConnectionReady()方法主要逻辑:

  1. connectionContext状态设置为 HANDSHAKING

  2. 创建HandshakeStepWrapper对象,分配线程运行:

    实际上是运行HandshakeManagerImpl对象的shake方法(在ConnectionManagerImpl中创建的)

 

    @Override
    public void run() {
        if (connectionAdapter.isAlive()) {
            handshakeManager.shake(helloMessage);
        } else {
            LOG.debug("connection is down - skipping handshake step");
        }
    }

3. 控制器主动发送 Hello 消息

HandshakeManagerImpl.shake

注意此时调用 shake 方法时,

传入的receivedHello为 null,

所以会调用sendHelloMessage(highestVersion, getNextXid())

sendHelloMessage方法如下,

实际是调用ConnectionAdapterImpl对象的hello方法。

最终控制器发送 hello 消息给 switch,进行协商 openflow 版本。

  • 这里就可以看出,控制器与底层 switch 通信靠ConnectionAdapterImpl对象封装

4. 控制器处理 Switch 回复的 Hello 消息

在上述步骤,控制器主动会发送 hello 包到 switch,然后 switch 也会回复数据包给控制器。下面展开探讨控制器是如何处理 Switch 回复。

首先回到TcpChannelInitializer.initChannel

ConnectionAdapterImpl对象

设置了DelegatingInboundHandler

 

// Delegates translated POJOs into MessageConsumer.
ch.pipeline().addLast(PipelineHandlers.DELEGATING_INBOUND_HANDLER.name(),
new DelegatingInboundHandler(connectionFacade));

根据 netty pipeline 的数据流处理模型,当收到 switch 发送的消息,

会调用DelegatingInboundHandler处理,

会调用DelegatingInboundHandler.channelRead方法。

DelegatingInboundHandlerchannelRead方法调用的是ConnectionAdapterImpl对象的consume方法。

 

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        consumer.consume((DataObject) msg);
    }

最终就会调用到

ConnectionAdapterImpl.consumeDeviceMessage方法:以 Handshake 过程的 Hello message 为例,

会调用

messageListener.onHelloMessage((HelloMessage) message);

即调用

OpenflowProtocolListenerInitialImpl.onHelloMessage方法:

  • 回忆上述步骤:

    ConnectionManagerImpl.onSwitchConnected方法中,

    会将OpenflowProtocolListenerInitialImpl对象传入(setMessageListener)

onHelloMessage方法中,

会查询 connectionContext 的状态为 HANDSHAKING 时,会再次分配线程运行HandshakeStepWrapper

即再次调用HandshakeManagerImpl.shake方法。

5. 协商 Openflow 协议版本

HandshakeManagerImpl.shake中,

可以看到处理第二个或更后的 hello 包后续逻辑是根据 switch 的第一个 hello 返回是否带有 openflow 版本 bit,而进行不同协商过程

handleVersionBitmapNegotiation

handleStepByStepVersionNegotiation)。

而具体两种协商过程可以参考官方文档说明。

Alt text

两种协商过程,最终都会调用

HandshakeManagerImpl.postHandshake方法。

6.控制器请求 Switch features 特性

在控制器与 switch 通过协商确定 openflow 版本号后,

会调用HandshakeManagerImpl.postHandshake方法。

postHandshake方法主要操作:

  • 调用get-features rpc,向 switch 请求获取 features。

    这里也是通过调用 ConnectionAdapterImpl 对象

    (connectionAdapter.getFeatures)

  • features 包括:datapathId,buffers,tables,auxiliaryId,capabilities,reserved,actions,phy-port 等(参考openflow-protocol.yang)

get-features成功后,会调用

handshakeListener.onHandshakeSuccessful(featureOutput, proposedVersion);

继续接下来的处理。

7.Handshake 成功设置 connectionContext,发送 barrier 消息

HandshakeListenerImpl.onHandshakeSuccessful方法逻辑:

  • 设置 connectionContex t 状态为 WORKING

  • 设置 connectionContext.featuresReply 为上一步调用 get-features 的返回

  • 设置 connectionContext.nodeId 为 datapathId

  • 调用

    connectionContext.handshakeSuccessful()

    创建 DeviceInfoImpl 对象

    this.deviceInfo = new DeviceInfoImpl()

  • 最后,向 switch 发送barrier消息。如果成功回调addBarrierCallback()方法

    用于保证在 switch 之前的命令都已经被执行

为了保证 handshake 完成,最后会向 switch 发送barrier消息。

如果成功回调addBarrierCallback()方法

barrier 消息作用:用于保证在 switch 之前的命令都已经被执行。具体可以看《图解 Openflow》或其他书籍/资料。

8.Switch 生命周期开始

Barrier 消息发送成功后会触发 ContextChainHolderImpl 处理。

HandshakeListenerImpl.addBarrierCallback()方法,

核心逻辑

deviceConnectedHandler.deviceConnected(connectionContext);

用于调用

ContextChainHolderImpl.deviceConnected方法

  • deviceConnectedHandler变量是在
    ConnectionManagerImpl.onSwitchConnected方法,创建HandshakeListenerImpl对象时传入,即ContextChainHolderImpl

barrier 消息发送成功后,会调用

ContextChainHolderImpl.deviceConnected方法,

会为 Switch 创建管理其生命周期的 ContextChain 对象等。

当调用到ContextChainHolderImpl.deviceConnected方法时,代表 switch 已经与控制器完成 handshake。

在此方法中,除了处理辅助连接,最核心的是为第一次连上控制器的 switch 创建 ContextChainImpl 对象!

调用createContextChain(connectionContext)方法,

而后续的步骤已经不是 handshake 过程,是为 switch 创建各个 context,并进行 mastership 选举等,本文不展开。

总结

至此,我们看到了 switch 连上控制器,

TcpChannelInitializer

ContextChainHolderImpl

可以看到整个 Handshake 过程的调用,主动发送 Hello、协商 Openflow 版本号、获取基本 Features、发送 Barrier 消息;

并最后完成 Handshake 后触发ContextChainHolderImpl开始 switch 在 Openflowplugin 核心逻辑的生命周期;

更加认识到了ConnectionAdapterImpl对象就是与底层 switch 通信的关键封装对象。

Reference

https://www.opendaylight.org/what-we-do/current-release/fluorine

https://github.com/opendaylight/openflowplugin

 

敬请期待下期

(二)深入 Openflowplugin 源码 Switch 生命周期对象

 

往期精彩

NEW

人工智障入门

网易游戏《荒野行动》《阴阳师》等出海实践-AWS 技术峰会演讲实录

MySQL Flashback 拯救手抖党

天下武功,唯快不破——快速搜索工具 ripgrep

网易消息推送系统微服务化实践

 

感谢阅读,如果好看请点击右下角「在看」,点亮小星星,变亮的星星是小编的动力哦!

原始链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值