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

栏目: 服务器 · 发布时间: 5年前

内容简介:随着云计算的火热,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源码分析OpenFlow握手过程(一)

OpenFlowPlugin Handshake源码分析

Handshake过程

在OpenFlowPlugin启动过程中, SwitchConnectionProviderImpl.startup 会启动tcp server监听端口。而tcp server是基于Netty实现,在 TcpHandler.java 会创建Bootstrap/EventLoopGroup等,同样会设置channelInitialize。

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

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

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

初始化Channel

当switch通过tcp连接上控制器,会触发 TcpChannelInitializer.initChannel 方法初始化channel。

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

在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 对象封装 ConnectionContextImplHandshakeContextImpl
    • 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

深入OpenFlowPlugin源码分析OpenFlow握手过程(一) 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() 如下: 深入OpenFlowPlugin源码分析OpenFlow握手过程(一) 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())深入OpenFlowPlugin源码分析OpenFlow握手过程(一) sendHelloMessage 方法如下,实际是调用 ConnectionAdapterImpl 对象的 hello 方法。最终控制器发送hello消息给switch,进行协商OpenFlow版本。

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

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

控制器处理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 方法:

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

以Handshake过程的Hello message为例,会调用 messageListener.onHelloMessage((HelloMessage) message); ,即调用 OpenflowProtocolListenerInitialImpl.onHelloMessage 方法:

回忆上述步骤:在 ConnectionManagerImpl.onSwitchConnected 方法中,会将 OpenflowProtocolListenerInitialImpl 对象传入( setMessageListener )。 深入OpenFlowPlugin源码分析OpenFlow握手过程(一)onHelloMessage 方法中,会查询connectionContext的状态为HANDSHAKING时,会再次分配线程运行 HandshakeStepWrapper ,即再次调用 HandshakeManagerImpl.shake 方法。

协商OpenFlow协议版本

HandshakeManagerImpl.shake 中,可以看到处理第二个或更后的hello包后续逻辑是根据switch的第一个hello返回是否带有OpenFlow版本bit,而进行不同协商过程( handleVersionBitmapNegotiation , handleStepByStepVersionNegotiation )。

而具体两种协商过程可以参考 官方文档说明 ,在这里不展开。 深入OpenFlowPlugin源码分析OpenFlow握手过程(一) 两种协商过程,最终都会调用 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

深入OpenFlowPlugin源码分析OpenFlow握手过程(一)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之前的命令都已经被执行

深入OpenFlowPlugin源码分析OpenFlow握手过程(一) 为了保证handshake完成,最会向switch发送 barrier 消息。如果成功回调 addBarrierCallback() 方法。

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

Switch生命周期开始

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

HandshakeListenerImpl.addBarrierCallback() 方法,核心逻辑 deviceConnectedHandler.deviceConnected(connectionContext); ,用于调用 ContextChainHolderImpl.deviceConnected 方法:

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

深入OpenFlowPlugin源码分析OpenFlow握手过程(一) barrier消息发送成功后,会调用 ContextChainHolderImpl.deviceConnected 方法,会为Switch创建管理其生命周期的ContextChain对象等。

当调用到 ContextChainHolderImpl.deviceConnected 方法时,代表switch已经与控制器完成handshake。在此方法中,除了处理辅助连接,最核心的是为第一次连上控制器的switch创建ContextChainImpl对象!调用 createContextChain(connectionContext) 方法,而后续的步骤已经不是handshake过程,是为switch创建各个context,并进行mastership选举等,本文不展开。

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

总结

至此,我们看到了switch连上控制器,从 TcpChannelInitializerContextChainHolderImpl ,可以看到整个Handshake过程的调用,主动发送Hello、协商OpenFlow版本号、获取基本Features、发送Barrier消息,并最后完成Handshake后触发 ContextChainHolderImpl 开始switch在OpenFlowPlugin核心逻辑的生命周期。

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

Reference


以上所述就是小编给大家介绍的《深入OpenFlowPlugin源码分析OpenFlow握手过程(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Kubernetes权威指南

Kubernetes权威指南

龚正、吴治辉、王伟、崔秀龙、闫健勇、崔晓宁、刘晓红 / 电子工业出版社 / 2016-10 / 99

Kubernetes是由谷歌开源的Docker容器集群管理系统,为容器化的应用提供了资源调度、部署运行、服务发现、扩容及缩容等一整套功能。《Kubernetes权威指南:从Docker到Kubernetes实践全接触(第2版)》从一个开发者的角度去理解、分析和解决问题,囊括了Kubernetes入门、核心原理、实践指南、开发指导、高级案例、运维指南及源码分析等方面的内容,图文并茂、内容丰富、由浅入......一起来看看 《Kubernetes权威指南》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换