歡迎光臨
每天分享高質量文章

深入OpenFlowPlugin原始碼分析OpenFlow握手過程(一)

 

本文轉自網易遊戲運維公眾號(neteasegameops)
作者:陳卓文,網易遊戲高階開發工程師,負責網易遊戲私有雲平臺底層 SDN/NFV 網路的開發工作。

 

前言

 

隨著雲端計算的火熱,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物件。
  1. connectionFacade = connectionAdapterFactory.createConnectionFacade(ch, null, useBarrier(), getChannelOutboundQueueSize());
會為每個connection(switch)建立一個 ConnectionAdapterImpl物件,此物件是封裝底層switch的關鍵物件,上層透過此物件與switch通訊。從變數名Facade也能推敲出此物件的作用。
2、呼叫 ConnectionManagerImpl.onSwitchConnected方法,傳參傳入的是 ConnectionAdapterImpl物件。
  1. 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:
  1. final ConnectionFacade finalConnectionFacade = connectionFacade;
  2. handshakeFuture.addListener(future -> finalConnectionFacade.fireConnectionReadyNotification());
沒開tls:
  1. if (!tlsPresent) {
  2. connectionFacade.fireConnectionReadyNotification();
  3. }
而上面兩個程式碼片段的connectionFacade變數正是 ConnectionAdapterImpl物件。其 fireConnectionReadyNotification()方法如下:
  1. @Override
  2. public void fireConnectionReadyNotification() {
  3. versionDetector = (OFVersionDetector) channel.pipeline().get(PipelineHandlers.OF_VERSION_DETECTOR.name());
  4. Preconditions.checkState(versionDetector != null);
  5. new Thread(() -> connectionReadyListener.onConnectionReady()).start();
  6. }
可以看到 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()如下: 
onConnectionReady()方法主要邏輯:
  1. connectionContext狀態設定為HANDSHAKING

  2. 建立 HandshakeStepWrapper物件,分配執行緒執行:實際上是執行 HandshakeManagerImpl物件的 shake方法(在 ConnectionManagerImpl中建立的)

  1. @Override
  2. public void run() {
  3. if (connectionAdapter.isAlive()) {
  4. handshakeManager.shake(helloMessage);
  5. } else {
  6. LOG.debug("connection is down - skipping handshake step");
  7. }
  8. }
控制器主動傳送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
  1. // Delegates translated POJOs into MessageConsumer.
  2. ch.pipeline().addLast(PipelineHandlers.DELEGATING_INBOUND_HANDLER.name(),
  3. new DelegatingInboundHandler(connectionFacade));
根據Netty Pipeline的資料流處理模型,當收到switch傳送的訊息,會呼叫 DelegatingInboundHandler處理。會呼叫 DelegatingInboundHandler.channelRead方法。
而 DelegatingInboundHandler的 channelRead方法呼叫的是 ConnectionAdapterImpl物件的 consume方法。
  1. @Override
  2. public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
  3. consumer.consume((DataObject) msg);
  4. }
最終就會呼叫到 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=newDeviceInfoImpl()

  • 最後,向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
  1. https://www.opendaylight.org/what-we-do/current-release/fluorine

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

贊(0)

分享創造快樂