(二)ODL Openflowplugin Switch连上控制器Handshake过程源码分析

作者简介:陈卓文,国内某游戏公司私有云团队开发者,主要从事SDN/NFV开发。

本文为Openflowplugin源码分析第二篇,紧跟第一篇:(一)ODL OpenflowPlugin启动流程源码分析
补充说明:Openflowplugin版本0.6.2

读者约定:基本掌握Opendaylight的思想/有一定实践经验,想要深入理解openflowplugin源码/想对openflowplugin源码修改。

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对象。

会为每个connection(switch)创建一个ConnectionAdapterImpl对象,此对象是封装底层switch的关键对象,上层通过此对象与switch通信。从变量名Facade也能推敲出此对象的作用。

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

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

  • 1.创建ConnectionReadyListenerImpl对象给ConnectionAdapterImpl对象传入引用(setConnectionReadyListener);
    • ConnectionReadyListenerImpl对象封装ConnectionContextImplHandshakeContextImpl
    • ConnectionReadyListenerImpl对象提供onConnectionReady()方法,该方法处理是调用HandshakeManagerImpl.shake()
  • 2.创建OpenflowProtocolListenerInitialImpl对象,给ConnectionAdapterImpl对象传入引用(setMessageListener);
    • OpenflowProtocolListenerInitialImpl对象用于处理底层switch发给控制器的消息,比如提供onHelloMessage方法。
    • 注意:该对象仅用于处理handshake过程中涉及的基本消息,在handshake后会被另一对象OpenflowProtocolListenerFullImpl替换。
  • 3.创建SystemNotificationsListenerImpl对象,给ConnectionAdapterImpl对象传入引用(setSystemListener
    • 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

2.没开tls

而上面两个代码片段的connectionFacade变量正是ConnectionAdapterImpl对象。其fireConnectionReadyNotification()方法如下:

可以看到fireConnectionReadyNotification()方法实际是调用connectionReadyListener.onConnectionReady(),而connectionReadyListener变量正是上面第二步中调用setConnectionReadyListener传入的ConnectionReadyListenerImpl对象。

即分配新的线程执行ConnectionReadyListenerImpl.onConnectionReady(),而onConnectionReady()方法会触发handshake,在下面开展。

总结,可以看到在Tcp channel初始化时(TcpChannelInitializer.initChannel),会:

  • 创建ConnectionAdapterImpl对象,封装传入的SocketChannel channel对象;
  • 调用ConnectionManagerImpl.onSwitchConnected方法,给ConnectionAdapterImpl对象setConnectionReadyListener, setMessageListener, setSystemListener
  • 给pipeline设置各种channelHandler
  • 调用ConnectionAdapterImpl.fireConnectionReadyNotification()发起handshake;

2.ConnectionReady开始Handshake

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

onConnectionReady()方法主要逻辑:

  1. connectionContext状态设置为HANDSHAKING
  2. 创建HandshakeStepWrapper对象,分配线程运行:实际上是运行HandshakeManagerImpl对象的shake方法(在ConnectionManagerImpl中创建的)

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

    根据netty pipeline的数据流处理模型,当收到switch发送的消息,会调用DelegatingInboundHandler处理。会调用DelegatingInboundHandler.channelRead方法。

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

    最终就会调用到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)。

    而具体两种协商过程可以参考官方文档说明,在这里不展开。

    两种协商过程,最终都会调用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方法逻辑:

    • 设置connectionContext状态为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连上控制器,从TcpChannelInitializerContextChainHolderImpl,可以看到整个handshake过程的调用,更加认识到了ConnectionAdapterImpl对象就是与底层switch通信的关键封装对象。

    关键类/对象

    个人理解整理:



  • 本站原创文章仅代表作者观点,不代表SDNLAB立场。所有原创内容版权均属SDNLAB,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用,转载须注明来自 SDNLAB并附上本文链接。 本站中所有编译类文章仅用于学习和交流目的,编译工作遵照 CC 协议,如果有侵犯到您权益的地方,请及时联系我们。
  • 本文链接https://www.sdnlab.com/21257.html
分享到:
相关文章
条评论

登录后才可以评论

陈卓文 发表于18-08-10
2