深入OpenFlowPlugin源码分析OpenFlow握手过程(一) - Go语言中文社区

深入OpenFlowPlugin源码分析OpenFlow握手过程(一)


640?wx_fmt=jpeg

本文转自网易游戏运维公众号(neteasegameops) ,点击查看原文
作者:陈卓文,网易游戏高级开发工程师,负责网易游戏私有云平台底层 SDN/NFV 网络的开发工作。


前言

640?wx_fmt=png


随着云计算的火热,SDN/NFV,OpenFlow等名词频繁出现。在网络上,有很多介绍名词概念等的博文,但本人深入云底层SDN/NFV网络开发过程中,很少能够找到足够深入的文章学习。因此在学习过程中总结了几篇文章,深入分析OpenDaylight OpenFlowPlugin底层源码,希望对相关云底层SDN/NFV网络开发人员有所帮助。
本系列文章基于OpenFlowPlugin版本0.6.2。本文为第一篇,分析OpenFlow节点连上控制器过程中OpenFlow协议的握手过程。
OpenDaylight

640?wx_fmt=png


在我们的架构中,我们采用了OpenDaylight作为我们开发的底层框架。其作为一个成熟的开源社区,OpenDaylight良好的框架使它能够支持各种协议的南向插件,比如OpenFlow、NETCONF、OVSDB、BGP等。在我们SDN网络控制面,我们采用了其南向OpenFlow协议插件OpenFlowPlugin连接我们的OpenFlow转发节点。OpenDaylight架构图: 
640?wx_fmt=png
OpenFlowPlugin Handshake源码分析

640?wx_fmt=png


Handshake过程
在OpenFlowPlugin启动过程中, SwitchConnectionProviderImpl.startup会启动tcp server监听端口。而tcp server是基于Netty实现,在 TcpHandler.java会创建Bootstrap/EventLoopGroup等,同样会设置channelInitialize。 
640?wx_fmt=png
当switch底层连上控制器tcp server监听的端口6633/6653,Netty在接受channel后,会调用channelInitialize的initChannel方法,即 TcpChannelInitializer.initChannel。关于Netty,可以参考《Netty in action》,推荐阅读。初始化Channel
当switch通过tcp连接上控制器,会触发 TcpChannelInitializer.initChannel方法初始化channel。 
640?wx_fmt=jpeg
在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


640?wx_fmt=jpeg
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:
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对象,封装传入的 SocketChannelchannel对象;

  • 调用 ConnectionManagerImpl.onSwitchConnected方法,给 ConnectionAdapterImpl对象 setConnectionReadyListener, setMessageListener, setSystemListener;

  • 给pipeline设置各种channelHandler

  • 调用 ConnectionAdapterImpl.fireConnectionReadyNotification()发起handshake。


ConnectionReady开始Handshake
在 TcpChannelInitializer.initChannel最后,调用 ConnectionReadyListenerImpl.onConnectionReady()如下: 
640?wx_fmt=jpeg
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");	
        }	
    }

控制器主动发送Hello消息
HandshakeManagerImpl.shake,注意此时调用shake方法时,传入的 receivedHello为null,所以会调用 sendHelloMessage(highestVersion,getNextXid())。 
640?wx_fmt=jpeg
sendHelloMessage方法如下,实际是调用 ConnectionAdapterImpl对象的 hello方法。最终控制器发送hello消息给switch,进行协商OpenFlow版本。
这里就可以看出,控制器与底层switch通信靠 ConnectionAdapterImpl对象封装。 
640?wx_fmt=jpeg
控制器处理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方法: 
640?wx_fmt=jpeg
以Handshake过程的Hello message为例,会调用 messageListener.onHelloMessage((HelloMessage)message);,即调用 OpenflowProtocolListenerInitialImpl.onHelloMessage方法:
回忆上述步骤:在 ConnectionManagerImpl.onSwitchConnected方法中,会将 OpenflowProtocolListenerInitialImpl对象传入( setMessageListener)。 
640?wx_fmt=jpeg
在 onHelloMessage方法中,会查询connectionContext的状态为HANDSHAKING时,会再次分配线程运行 HandshakeStepWrapper,即再次调用 HandshakeManagerImpl.shake方法。
协商OpenFlow协议版本
在 HandshakeManagerImpl.shake中,可以看到处理第二个或更后的hello包后续逻辑是根据switch的第一个hello返回是否带有OpenFlow版本bit,而进行不同协商过程( handleVersionBitmapNegotiation, handleStepByStepVersionNegotiation)。
而具体两种协商过程可以参考官方文档说明,在这里不展开。 
640?wx_fmt=png
两种协商过程,最终都会调用 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)
640?wx_fmt=jpeg
在 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=newDeviceInfoImpl()


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

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


640?wx_fmt=jpeg
为了保证handshake完成,最会向switch发送 barrier消息。如果成功回调 addBarrierCallback()方法。barrier消息作用:用于保证在switch之前的命令都已经被执行。具体可以看《图解OpenFlow》或其他书籍/资料。Switch生命周期开始
Barrier消息发送成功后会触发ContextChainHolderImpl处理。
HandshakeListenerImpl.addBarrierCallback()方法,核心逻辑 deviceConnectedHandler.deviceConnected(connectionContext);,用于调用 ContextChainHolderImpl.deviceConnected方法:
deviceConnectedHandler变量是在 ConnectionManagerImpl.onSwitchConnected方法,创建 HandshakeListenerImpl对象时传入,即 ContextChainHolderImpl。
640?wx_fmt=jpeg
barrier消息发送成功后,会调用 ContextChainHolderImpl.deviceConnected方法,会为Switch创建管理其生命周期的ContextChain对象等。
当调用到 ContextChainHolderImpl.deviceConnected方法时,代表switch已经与控制器完成handshake。在此方法中,除了处理辅助连接,最核心的是为第一次连上控制器的switch创建ContextChainImpl对象!调用 createContextChain(connectionContext)方法,而后续的步骤已经不是handshake过程,是为switch创建各个context,并进行mastership选举等,本文不展开。 
640?wx_fmt=jpeg
总结

640?wx_fmt=png


至此,我们看到了switch连上控制器,从 TcpChannelInitializer到 ContextChainHolderImpl,可以看到整个Handshake过程的调用,主动发送Hello、协商OpenFlow版本号、获取基本Features、发送Barrier消息,并最后完成Handshake后触发 ContextChainHolderImpl开始switch在OpenFlowPlugin核心逻辑的生命周期。
更加认识到了 ConnectionAdapterImpl对象就是与底层switch通信的关键封装对象。
Reference
  1. https://www.opendaylight.org/what-we-do/current-release/fluorine

  2. https://github.com/opendaylight/openflowplugin


Kubernetes实战培训

640?


Kubernetes实战培训将于2019年6月28日在深圳开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习。本次培训包括:云原生介绍、微服务;Docker基础、Docker工作原理、镜像、网络、存储、数据卷、安全;Kubernetes架构、核心组件、常用对象、网络、存储、认证、服务发现、调度和服务质量保证、日志、监控、告警、Helm、实践案例等,点击下方图片或者点击阅读原文了解详情。
640?wx_fmt=png
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m2l0zgssvc7r69efdtj/article/details/93800088
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 20:07:02
  • 阅读 ( 1167 )
  • 分类:Go Web框架

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢