42

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

 4 years ago
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架构图:

Mnu6nuY.png!web

OpenFlowPlugin Handshake源码分析

Handshake过程

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

zYJFNfB.png!web

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

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

初始化Channel

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

qeAJ7bN.jpg!web

在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

R3yumqZ.jpg!web 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() 如下: FFNraaj.jpg!webonConnectionReady() 方法主要逻辑:

  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())Ufi26z7.jpg!websendHelloMessage 方法如下,实际是调用 ConnectionAdapterImpl 对象的 hello 方法。最终控制器发送hello消息给switch,进行协商OpenFlow版本。

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

MviumyQ.jpg!web

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

ANFjauj.jpg!web

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

回忆上述步骤:在 ConnectionManagerImpl.onSwitchConnected 方法中,会将 OpenflowProtocolListenerInitialImpl 对象传入( setMessageListener )。 mM7FZfM.jpg!webonHelloMessage 方法中,会查询connectionContext的状态为HANDSHAKING时,会再次分配线程运行 HandshakeStepWrapper ,即再次调用 HandshakeManagerImpl.shake 方法。

协商OpenFlow协议版本

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

而具体两种协商过程可以参考 官方文档说明 ,在这里不展开。 M7Zrumi.png!web 两种协商过程,最终都会调用 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

7ZBNJzm.jpg!webget-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之前的命令都已经被执行

fUvq6jf.jpg!web 为了保证handshake完成,最会向switch发送 barrier 消息。如果成功回调 addBarrierCallback() 方法。

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

Switch生命周期开始

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

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

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

JnUZv2v.jpg!web barrier消息发送成功后,会调用 ContextChainHolderImpl.deviceConnected 方法,会为Switch创建管理其生命周期的ContextChain对象等。

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

3iQZjuQ.jpg!web

总结

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

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

Reference


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK