内容简介:随着云计算的火热,SDN/NFV,OpenFlow等名词频繁出现。在网络上,有很多介绍名词概念等的博文,但本人深入云底层SDN/NFV网络开发过程中,很少能够找到足够深入的文章学习。因此在学习过程中总结了几篇文章,深入分析OpenDaylight OpenFlowPlugin底层源码,希望对相关云底层SDN/NFV网络开发人员有所帮助。本系列文章基于OpenFlowPlugin版本关于Netty,可以参考《Netty in action》,推荐阅读。
前言
随着云计算的火热,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
以上所述就是小编给大家介绍的《深入OpenFlowPlugin源码分析OpenFlow握手过程(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 深入OpenFlowPlugin源码分析OpenFlow握手过程(二)
- 源码角度分析 OkHttp 实现 WebSocket:握手/保活/数据处理...
- TCP 的 三次握手 四次握手
- TCP连接为什么是三次握手,而不是两次握手,也不是四次握手?
- 握手过程中,非对称密钥的应用
- 用 Wireshark 图解 TCP 三次握手
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
随机密码生成器
多种字符组合密码
XML、JSON 在线转换
在线XML、JSON转换工具