

深入OpenFlowPlugin源码分析OpenFlow握手过程(一)
source link: https://www.tuicool.com/articles/NBr2AnY
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言
随着云计算的火热,SDN/NFV,OpenFlow等名词频繁出现。在网络上,有很多介绍名词概念等的博文,但本人深入云底层SDN/NFV网络开发过程中,很少能够找到足够深入的文章学习。因此在学习过程中总结了几篇文章,深入分析OpenDaylight OpenFlowPlugin底层源码,希望对相关云底层SDN/NFV网络开发人员有所帮助。
本系列文章基于OpenFlowPlugin版本 0.6.2 。本文为第一篇,分析OpenFlow节点连上控制器过程中OpenFlow协议的握手过程。
OpenDaylight
在我们的架构中,我们采用了OpenDaylight作为我们开发的底层框架。其作为一个成熟的开源社区,OpenDaylight良好的框架使它能够支持各种协议的南向插件,比如OpenFlow、NETCONF、OVSDB、BGP等。在我们SDN网络控制面,我们采用了其南向OpenFlow协议插件OpenFlowPlugin连接我们的OpenFlow转发节点。OpenDaylight架构图:

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》,推荐阅读。
初始化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,用于处理底层各个事件。
-
创建
ConnectionReadyListenerImpl
对象给ConnectionAdapterImpl
对象传入引用(setConnectionReadyListener
);-
ConnectionReadyListenerImpl
对象封装ConnectionContextImpl
和HandshakeContextImpl
; -
ConnectionReadyListenerImpl
对象提供onConnectionReady()
方法,该方法处理是调用HandshakeManagerImpl.shake()
;
-
-
创建
OpenflowProtocolListenerInitialImpl
对象,给ConnectionAdapterImpl
对象传入引用(setMessageListener
);-
OpenflowProtocolListenerInitialImpl
对象用于处理底层switch发给控制器的消息,比如提供onHelloMessage
方法。 - 注意: 该对象仅用于处理handshake过程中涉及的基本消息,在handshake后会被另一对象
OpenflowProtocolListenerFullImpl
替换。
-
-
创建
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()
方法:
开tls:
java final ConnectionFacade finalConnectionFacade = connectionFacade; handshakeFuture.addListener(future -> finalConnectionFacade.fireConnectionReadyNotification());
没开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
对象setConnectionReadyListener
,setMessageListener
,setSystemListener
; - 给pipeline设置各种channelHandler
- 调用
ConnectionAdapterImpl.fireConnectionReadyNotification()
发起handshake。
ConnectionReady开始Handshake
在 TcpChannelInitializer.initChannel
最后,调用 ConnectionReadyListenerImpl.onConnectionReady()
如下: onConnectionReady()
方法主要逻辑:
-
connectionContext
状态设置为HANDSHAKING - 创建
HandshakeStepWrapper
对象,分配线程运行:实际上是运行HandshakeManagerImpl
对象的shake
方法(在ConnectionManagerImpl
中创建的)
@Override public void run() { if (connectionAdapter.isAlive()) { handshakeManager.shake(helloMessage); } else { LOG.debug("connection is down - skipping handshake step"); } }
控制器主动发送Hello消息
HandshakeManagerImpl.shake
,注意此时调用shake方法时,传入的 receivedHello
为null,所以会调用 sendHelloMessage(highestVersion, getNextXid())
。 sendHelloMessage
方法如下,实际是调用 ConnectionAdapterImpl
对象的 hello
方法。最终控制器发送hello消息给switch,进行协商OpenFlow版本。
这里就可以看出,控制器与底层switch通信靠 ConnectionAdapterImpl
对象封装。

控制器处理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
方法。
而 DelegatingInboundHandler
的 channelRead
方法调用的是 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
方法。
协商OpenFlow协议版本
在 HandshakeManagerImpl.shake
中,可以看到处理第二个或更后的hello包后续逻辑是根据switch的第一个hello返回是否带有OpenFlow版本bit,而进行不同协商过程( handleVersionBitmapNegotiation
, handleStepByStepVersionNegotiation
)。
而具体两种协商过程可以参考 官方文档说明 ,在这里不展开。 两种协商过程,最终都会调用
HandshakeManagerImpl.postHandshake
方法。
控制器请求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);
继续接下来的处理。
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》或其他书籍/资料。
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
Recommend
-
108
作者简介:陈卓文,国内某游戏公司私有云团队开发者,主要从事SDN/NFV开发,个人邮箱:[email protected] 本文为OpenflowPlugin源码分析第一篇,会有一系列文章深入openflowpl...
-
38
作者简介:隙中驹,开放网络拥护者及实践者,熟悉传统网络技术及行业解决方案,关注SDN/NFV等ICT融合技术。 email:[email protected] OpenFlow协议从2009年发布的v1.0到最近...
-
61
作者简介:陈卓文,国内某游戏公司私有云团队开发者,主要从事SDN/NFV开发。 1.MastershipChangeServiceManager是什么 2.MastershipChangeServiceM...
-
42
作者简介:陈卓文,国内某游戏公司私有云团队开发者,主要从事SDN/NFV开发。 1.成为SLAVE流程 1.1 创建slaveTask 1.2 超时成为SLAV...
-
69
【编者的话】本文为OpenFlowPlugin(0.6.2)源码分析第二篇,也是OpenFlowPlugin为上层北向应用提供服务的关键逻辑的开篇! 回顾第一篇笔记,在Switch连上控制器Handshake完成后,会调用 ContextChainHolderImpl.deviceCo...
-
27
【编者的话】OpenFlow协议支持多个控制器,其工作模式是怎样的?在OFP中,控制器节点会选举Master,该Master节点为北向应用提供该交换机的服务。本文深入OpenFlowPlugin中的Master选举过程,以及北向应用怎么感知选举并处理。 Op...
-
12
TCP协议三次握手过程分析
-
13
SSL协议握手过程报文解析 2021-06-08 21:35:00 https://blog.csdn.net/tterminator/article/details/50675540 SSL建立握手连接...
-
10
详细说下 TCP 三次握手的过程?-吴师兄学编程 当前位置:吴师兄学编程 > 计算机网络 > 详细说下 TCP 三次握手的过程? ...
-
7
本文主要分析 TCP 协议的实现,但由于 TCP 协议比较复杂,所以分几篇文章进行分析,这篇主要介绍 TCP 协议建立连接时的三次握手过程。 TCP 协议应该是 TCP/IP 协议栈中最为复杂的一个协议(没有之一),TCP 协议的复杂性来源于其面向连接和保证可靠传输。
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK