ODL開發者指引:OpenFlow協議庫開發者指南

編者按:本文系SDNLAB社區譯者計劃發布文章,SDNLAB將與國外技術社區、優質媒體和個人進行長期的內容合作,帶來更多的優質技術文章。本文是OpenDaylight開發者指南的OpenFlow協議庫開發指南部分。

本文譯者:toles,雖然是個SDN初學者,但是很看好SDN前景,在不斷努力的學習中,希望和大家多多交流。

介紹

OpenFlow協議庫是OpenDaylight的一個組件,調解OpenDaylight controller和支持OpenFlow協議的硬件設備之間通信。主要目標是提供用戶(或上層OpenDaylight)通信通道,可用于管理網絡硬件設備。

功能概覽

Openflowjava內部的三個特性:

  • odl-openflowjava-protocol提供全部的openflowjava bundles, 需要與openflow設備通信. 它可以確保消息的轉換和處理網絡的連接. 它還提供了openflow協議具體模型.
  • odl-openflowjava-all 目前只包含了odl-openflowjava-protocol 特性.
  • odl-openflowjava-stats 提供消息計數和報告機制. 可用于性能分析。

odl-openflowjava-protocol架構

此特性包含基本的bundles有openflow-protocol-api, openflow-protocol-impl, openflow-protocol-spi and 工具.

  • openflow-protocol-api – 包含openflow模型,常量和用于(反)序列化注冊的秘鑰.
  • openflow-protocol-impl – 包含消息工廠, 轉換二進制消息進入DataObjects, 反之亦然. Bundle還包括網絡連接處理服務器, netty pipeline處理程序, …
  • openflow-protocol-spi -入口點為openflowjava配置,啟動和關閉.基本的起始實現
  • util -二進制JAVA轉換和便于實驗者秘鑰創建的實用工具類

odl-openflowjava-stats 特性

運行在odl-openflowjava-protocol上.它包含了各種的信息類型/事件和報告計數在特定時間周期內. 統計信息收集被配置在openflowjava- config/src/main/resources/45-openflowjava-stats.xml

關鍵API和接口

基礎API/SPI類是ConnectionAdapter (Rpc/通知) 和SwitchConnectionProcider (配置, 啟動, 關閉)

安裝

檢出代碼并導入工程到你的IDE.
git clone ssh://@git.opendaylight.org:29418/openflowjava.git

配置

當前實現允許配置:

  • 監聽端口(強制)
  • 傳送協議(強制)
  • 交換空閑超時 (強制)
  • TLS配置(可選)
  • 線程數(可選)

你可以在如下所示找到典型的Openflow?協議庫實例配置:

可能傳輸協議選項:

  • TCP
  • TLS
  • UDP

交換機空閑超時指定時間檢測交換機的空閑狀態.當一段時間內沒有收到來自交換機的消息,上層被通知交換機閑置.可以使用典型的TLS配置:

  • 取消 標簽注釋
  • 復制exemplary-switch-privkey.pem, exemplary-switch-cert.pem 和exemplary- cacert.pem文件到你的虛擬機
  • 設置VM加密選項用作復制秘鑰 (請訪問TLS支持的wiki頁關于TLS詳細信息)
  • 開始通信

線程模型配置指定需要多少個線程以執行?Netty 的?I/O?操作.

  • boss-threads指定注冊傳入連接的線程數
  • worker-threads 指定執行讀/寫(+ 序列化/ 反序列化) 操作線程數.

架構

公共API(openflow-protocol-api)

接口和構建者集合代表Openflow協議結構的不可變數據傳輸對象.

為了減少冗長的定義和重復性代碼,通過代碼生成器從YANG模型推出傳輸對象和服務API.

以下是YANG模型的定義:

  • openflow-types – 定義通用Openflow特定類型
  • openflow-instruction – 定義基本的Openflow 指令
  • openflow-action - 定義基本的Openflow活動
  • openflow-augments – 定義對象擴展
  • openflow-extensible-match – 定義Openflow OXM 匹配
  • openflow-protocol – 定義Openflow 協議消息
  • system-notifications – 定義系統通知對象
  • openflow-configuration – 定義結構用于ConfigSubsystem

這個模塊也可以從下面的YANG模型中重復使用類型:

  • ietf-inet-types – IP地址, IP前綴, IP協議相關類型
  • ietf-yang-types - Mac地址, 等等.

預定義類型的使用使API約定更安全, 有更好的可讀性和記錄(例如 用 MacAddress代替字節數組…)

TCP 通道pipeline(openflow-protocol-impl)

創建基于配置和支撐的通道處理pipeline.

TCP通道流水線. imageopenflowjava/500px-TCPChannelPipeline.png[width=500]

交換連接提供者.用于其它工程連接點的實現.庫通過本類公開其功能.庫能夠在這里被配置, 啟動和關閉.也有方法為客戶定制的?(反) 序列化注冊.

Tcp連接初始化程序.為了初始化TCP連接到一個設備(交換機),OF插件調用在SwitchConnectionProvider的方法initiateConnection()。該方法依次初始化(Bootstrap)通向設備的服務側通道。

TCP處理(TCP Handler).表示單服務通過TCP/TLS協議處理進入的連接。TCP處理程序創建一個單實例的TCP通道初始化程序對通道進行初始化。之后監聽配置過的InetAddress和端口。當一個新設備連接,TCP處理程序注冊他們的通道并把控制傳給TCP通道初始化程序。

TCP通道初始化程序.此類用于通道初始化/拒絕和傳遞參數.之后一個新通道被注冊,它調用交換連接處理(OF Plugin)接收方法決定是否庫應該保持新的注冊通道或者是否通道應該被關閉. 如果通道已經被接受, TCP通道初始化程序創建整個管道所需的處理與ConnectionAdapter?實例.之后通道pipeline準備好了,交換連接處理程序被onConnectionReady通知. OpenFlow?插件可以開始發送下游信息.

空閑處理程序.如果超過指定時間沒有收到任何消息,這個處理程序觸發空閑狀態通知.交換機從ConnectionConfiguration設置收到空閑超時參數.當交換空閑超時內收到消息空閑狀態處理程序處于非激活狀態.如果超過超時值沒有收到指定信息,處理程序創建SwitchIdleEvent消息并發送給下游.

TLS處理程序.通過TLS協議加密和解密消息.約束TLS
處理器進入pipeline 是配置的事情( tag). TLS通信要么不支持要么是必需支持. TLS處理程序作為Netty SslHandler表示.

OF幀解碼器. 解析輸入流進入正確長度的消息幀為further處理.幀基于Openflow頭長度. 如果收到的消息比OpenFlow最短消息(8字節)短, OF幀解碼器等待更多的數據.接收至少為8字節后,解碼器檢查OpenFlow頭長度.如果仍然有一些字節丟失,解碼器等待它們.其他的幀解碼器發送正確長度的消息到下一個處理程序中的通道管道.

OF版本探測器.檢測使用OpenFlow協議的版本并丟棄不受支持的版本消息.如果檢測版本支持, OF版本探測器創建包含檢測的版本和字節消息的VersionMessageWrapper目標,并且向上游發送對象.

OF解碼器.選擇正確的對象反序列化工廠 (基于消息類型) 并且反序列化消息生成DTO (數據傳輸對象). OF解碼器接收VersionMessageWrapper對象并將其傳遞到DeserializationFactory返回轉換的DTO. DeserializationFactory創建帶版本和接收消息類型的MessageCodeKey對象和對象類被接收消息序列化. 在DecoderTable搜索相應解碼器時此對象被用作秘鑰. DecoderTable實際上是一個map存儲解碼器.找到解碼器翻譯成接收消息進入DTO.如果沒有找到解碼器, 返回null.之后返回轉換的DTO回到OF解碼器,解碼器檢查是否為null.當DTO為null時,解碼器記錄日志并且拋出異常.否則傳遞DTO further到上游.最后, OF解碼器釋放ByteBuf包含的接收和解碼字節消息.

OF編碼器.選擇正確的序列化工廠 (基于DTO類型) 并且序列化DTOs為字節消息. OF編碼器相對于解碼器使用同樣的原則. OF Encoder receives DTO OF編碼器接收DTO,如果結果不為null把它轉化,它發送轉化的DTO作為 ByteBuf到下游.通過MessageTypeKey尋找合適的編碼器,基于版本和接收的DTO類.

授權入棧處理程序.授權收到的DTO到連接適配器.在channelInactive和channelUnregistered事件中反應.其中一個事件觸發, DelegatingInboundHandler 創建DisconnectEvent消息并且發到上游,通知上層交換斷鏈.

通道出站隊列.消息刷新處理程序.
存儲輸出消息(DTOs)并刷新.刷新的執行是基于時間過期和消息隊列數.

連接適配器.提供了pipeline頂部的外觀,隱藏了netty.io特性.提供了一種方法來注冊傳入的消息并將消息發送到特定的通道/會話. ConnectionAdapterImpl主要實現了3個接口(統一在一個超接口ConnectionFacade中):

  • ConnectionAdapter
  • MessageConsumer
  • OpenflowProtocolService

ConnectionAdapter接口有用于設置監聽器的方法(消息, 系統和連接準備監聽器),該方法檢查是否所有監聽器被設置,檢查是否通道存活并斷開連接方法. 斷鏈方法清除responseCache并禁用新消息.

MessageConsumer接口只有一個方法: consume(). Consume()方法被DelegatingInboundHandler方法調用.此方法基于其類型處理接收的DTO.有三種接收對象類型:

  • 系統通知 – 調用OpenFlow插件中的系統通知(systemListener設置).至于DisconnectEvent消息,連接適配器清除響應緩存并禁止consume()方法處理,
  • OpenFlow異步消息 (來自交換機) -調用Openflow插件中響應的通知,
  • OpenFlow 對稱消息 (響應請求) - create RpcResponseKey with XID and DTO’s class set. RpcResponseKey用于在responseCache在responseCache找到future對象. Future 對象收到的消息和錯誤 (如果任何發生)被設置成功標志.假設在responseCache沒有發現future對象,連接適配器記錄告警和丟棄的消息到日志.連接適配器也記錄接收到一個未知的DTO警告到日志.

OpenflowProtocolService接口包含了全部rpc-methods為發送消息從上層到下游并響應。請求消息返回將來填充的期望回復消息,否則這個期望的將來是Void類型。

注意: MultipartRequest消息是唯一例外.實際上它是請求-應答消息類型, 如果是作為rpc實現它不能處理更多的MultipartReply 消息(only one Future).這是為什么MultipartReply作為通知的實現. OpenFlow插件負責糾正消息處理.

UDP通道pipeline (openflow-protocol-impl)

創建以配置和支撐為基礎的UDP通道處理pipeline.交換機連接提供者, 通道出站隊列和連接適配器與TCP連接/通道pipeline的情況下實現的作用相同 (請看上面).

Figure 16.1. UDP Channel pipeline

UDP處理程序.代表一個單獨的服務器正在處理UDP?(DTLS)協議之上的傳入連接. UDP處理程序創建一個UDP通道初始化的單例實例,這個實例將出示通道.之后監聽綁定配置的地址和端口.當一個新設備連接, UDP處理程序注冊通道并傳遞控制權給UDP通道初始化程序.

UDP通道初始化程序.這個類被用于通道初始化和傳遞參數.之后一個新通道被注冊(UDP也永遠只有一個通道) UDP通道初始化程序創建整個流水線與所需要的處理程序.

DTLS處理程序.還沒有實現.將處理安全DTLS連接.

OF數據報文處理程序. 結合OF幀解碼器和OF版本檢測器功能.從接收數據報文提取消息并檢查消息版本是否支持.如果收到的消息來自未知發送機, OF報文處理程序為此發送機創建連接適配器并將其存儲在UdpConnectionMap發送機的地址.此map也被用于發送消息和正確連接適配器查找,委托消息從一個通道到多個會話.

OF數據報文解碼器.選擇正確的反序列化工廠 (基于消息類型)并且反序列化消息進入生成DTOs.OF解碼器接收VersionMessageUdpWrapper對象將其傳遞到DeserializationFactory,返回轉化的DTO. DeserializationFactory創建帶版本和接收消息類型的MessageCodeKey對象并將接收到的消息反序列化為對象的類.此對象被用作在DecoderTable搜索相應解碼器的關鍵字. DecoderTable是基于映射存儲解碼器. 發現解碼器轉換接收的消息進入DTO (DataTransferObject).如果沒有發現解碼器, 返回null.之后返回轉換的DTO到OF報文解碼器,此解碼器檢查是否為null.當DTO為null,解碼器把此狀態記錄日志中.否則在UdpConnectionMap內通過DTO找到相應的連接適配器.最后, OF解碼器釋放包含的ByteBuf和解碼字節消息.

OF數據報文編碼器.選擇正確的序列化工廠(基于DTO的類型)并且串行DTOs成為字節消息. OF報文編碼器使用了相同原則做相反的處理. OF編碼器接收DTO并轉換,如果結果不為null,轉換為數據包發送給下游,通過MessageTypeKey尋找相應的編碼器, 基于版本和接收DTO的類.

SPI (openflow-protocol-spi)

定義接口用于其他工程庫的連接點. 庫通過這個接口來公開其功能.

集成測試(openflow-protocol-it)

測試與簡單的客戶端通信.

簡單客戶端(simple-client)

輕量級交換機仿真器 –期望場景可編程.

實用工具 (util)

包含實用工具類,主要用于和ByteBuf一起工作.

庫的生命周期

步驟 (之后庫的bundle啟動):

  • [1] 庫由ConfigSubsystem配置 (地址, 端口, 加密, …)
  • [2] Plugin injects its SwitchConnectionHandler into the Library
  • [3] 插件開啟庫
  • [4]庫創建配置協議處理程序(e.g. TCP Handler)
  • [5] 協議處理程序創建通道初始化
  • [6] 通道初始化程序通知插件是否接受傳入每個新交換連接
  • [7] 插件響應:
  • true - 繼續構建管道
  • false - 拒絕連接/斷開連接通道
  • [8] Library notifies Plugin with onSwitchConnected(ConnectionAdapter) notification, passing reference to ConnectionAdapter, that will handle the connection[9]插件注冊系統和消息監聽器[10] FireConnectionReadyNotification() 被觸發,宣布用于通信的流水線處理程序已經被創建并且插件可以開始通信[11]需要的時候插件關閉這個庫

圖 16.2. 庫的生命周期

統計信息收集器

介紹

統計信息采集器采集消息統計. Current collected statistics (DS - downstream,
US - upstream):

  • DS_ENTERED_OFJAVA – 所有信息在openflowjava 登記(從openflowplugin拾取)
  • DS_ENCODE_SUCCESS - 編碼消息成功
  • DS_ENCODE_FAIL -在編碼(序列化)過程中失敗的消息
  • DS_FLOW_MODS_ENTERED -全部flow-mod消息進入openflowjava
  • DS_FLOW_MODS_SENT -全部的flow-mod消息成功發送
  • US_RECEIVED_IN_OFJAVA -從交換機接收的消息
  • US_DECODE_SUCCESS -消息解碼成功
  • US_DECODE_FAIL -消息解碼(反序列化)過程中失敗
  • US_MESSAGE_PASS -消息轉交給openflowplugin

Karaf

為了開啟統計, 需要特性:install odl-openflowjava-stats. To see the logs one should use log:set DEBUG org.opendaylight.openflowjava.statistics and than
probably log:display (you can log:list to see if the logging has been set).調整集合設置修改modify 45-openflowjava-stats.xml足以.

JConsole

Jconsole為統計信息收集器提供兩個命令:

  • 打印當前統計
  • 重置統計計數

之后JConsole 附屬到正確的處理程序上, 只需進入MBeans tab # org.opendaylight.controller # RuntimeBean # statistics-collection- service-impl # statistics-collection-service-impl # Operations 能使用這些命令.

TLS Support

注意

請看OpenFlow Plugin Developper Guide

可擴展性

介紹

擴展性入口點是SwitchConnectionProvider. SwitchConnectionProvider 為(解)序列化注冊包含了方法c.注冊解序列化需要使用.register*Deserializer(key, impl). 注冊序列化必須使用.register*Serializer(key, impl).注冊可以發生在配置過程中或者運行時.

注意:假設當接收到實驗者信息,沒有(反)序列化器被注冊,此庫將拋出IllegalArgumentException.

基本原理

為了使用擴展需要增加現有模型和注冊新(反)序列化器.

增加模型: 1. 創建新增加

Register (de)serializers: 1.創建你的(反)序列化器 2. 實現OFDeserializer<> / OFSerializer<> -以防你(反)序列化接口需要使用多TableFeatures消息, 讓它實現HeaderDeserializer<> / HeaderSerializer 3.實現規定的方法 4.在相應的秘鑰下注冊你的反序列化器 (案例 ExperimenterActionDeserializerKey) 5.相應的秘鑰下注冊你的序列化器 (in our case ExperimenterActionSerializerKey) 6. 完成, 測試你的實現

注意:如果你不知道用什么秘鑰實現你的(反)序列化,請看Registration keys一頁.

例子

假設我們有供應商/實驗者動作,由這種結構表示:

首先, 我們必須增強現有的模型. 我們創建一個新模型, 導入"openflow-types.yang" (不要忘記更新你的pom.xml和api依賴).現在我們創建了foo操作標識:

這將作為我們結構中的類型. 現在我們必須增強現有的action結構,以致我們將有所需的第一和第二字段.為了創建新擴展, 我們的模塊不得不導入"openflow-action.yang". 增加如下:

我們完成了模型的改變. 運行mvn clean編譯生成源代碼.生成后,我們需要實現我們的(反)序列化.

反序列化:

序列化:

序列化和反序列化注冊:

我們已經準備好測試我們的實現.

NOTE:供應商/實驗者結構只定義供應商/實驗者ID標識(除操作類型).對全部供應商消息,供應商/實驗者ID是唯一的-這就是為什么供應商能只注冊在一個ExperimenterAction(De)SerializerKey類下.這就是為什么供應商不得不在他們擁有的子類/子類型交換/選擇.

詳細演練:反序列化可擴展性

外部接口& 類描述. OFGeneralDeserializer:

  • OFDeserializer
  1. deserialize(ByteBuf) – 反序列化ByteBuf
  • HeaderDeserializer
  1. deserializeHeaders(ByteBuf) – 序列化只有 E 頭(在 TableFeatures消息使用)

DeserializerRegistryInjector

  • injectDeserializerRegistry(DeserializerRegistry) -注入注冊進反序列化器.客戶反序列化器需要訪問其他反序列化器時有用.

NOTE: DeserializerRegistryInjector 不是OFGeneralDeserializer派生的.它是一個獨立的接口.

MessageCodeKey及他們的后代被用作在DeserializerRegistry反序列化查找. MessageCodeKey 應該在一般情況下使用,然而它的派生類用在更特殊的情況下.例如ActionDeserializerKey被用作行動解序列化器查找和(解)注冊.供應商提供僅包含最必要字段特殊關鍵字. 這些關鍵字通常開始帶Experimenter前綴(MatchEntryDeserializerKey 是一個異常).

MessageCodeKey有這些域:

  • short version - Openflow wire版本號
  • int value – 讀取字節消息的值
  • Class<?> clazz – 創建對象類

場景介紹

  • [1]在自定義bundle場景開始要擴展庫的功能.自定義bundle公開實現創建反序列化器OFDeserializer/ HeaderDeserializer 接口 (封裝在OFGeneralDeserializer統一超接口).
  • [2]創建的反序列化器與相應的ExperimenterKeys是配對的,用于反序列化器查找.如果你不知道什么關鍵字應被用于你的(反)序列化器實現,請瀏覽Registration keys.
  • [3]一對反序列化器通過SwitchConnectionProvider傳遞到OF庫.registerCustomDeserializer(key, impl).庫注冊返序列化器.
  • 注冊時, 庫檢查是否反序列化器是一個 DeserializerRegistryInjector接口的實例. 如果是, DeserializerRegistry (它存儲了所有的反序列化參考)注入進反序列化程序.

當返序列化程序需要訪問其他反序列化程序特別有用. 舉例 IntructionsDeserializer 需要訪問 ActionsDeserializer 為了能夠處理OFPIT_WRITE_ACTIONS/OFPIT_APPLY_ACTIONS指令.

Detailed walkthrough: 序列化可擴展性

外部接口& 類描述.OFGeneralSerializer:

  • OFSerializer
  • serialize(E,ByteBuf) – 序列化 E 進入給出ByteBuf
  • HeaderSerializer
  • serializeHeaders(E,ByteBuf) – 序列化 E 頭(用于多TableFeatures消息)

SerializerRegistryInjector * injectSerializerRegistry(SerializerRegistry) – 注入serializer registry into serializer.當序列化程序需要訪問其他序列化程序時有用.

NOTE: SerializerRegistryInjector不是OFGeneralSerializer后代.

MessageTypeKey和它們的后代 這些關鍵字被用于序列化器在SerializerRegistry內查找. MessageTypeKey當它的后代被用在特殊的情況使用.例如ActionSerializerKey被用于Action反序列化器查找注銷. 供應商提供僅包含最必要字段特殊關鍵字.這些關鍵字通常開始帶Experimenter前綴 (MatchEntrySerializerKey是一個異常).

MessageTypeKey 存在這些域:

  • short version - Openflow wire 版本號
  • Class msgType - DTO

class方案演練

  • [1]串行擴展原理類似于反序列化原理.方案開始于一個自定義的包中.自定義bundle創建序列化器實現外露的OFSerializer / HeaderSerializer接口 (覆蓋OFGeneralSerializer超級接口下).
  • [2] 創建序列化器搭配他們的ExperimenterKeys,這用于序列化器的查找.如果你不知道什么秘鑰用作你的序列化器的實現,請瀏覽注冊秘鑰一頁.
  • [3]成對的序列化器通過SwitchConnectionProvider傳遞給OF庫.registerCustomSerializer(key, impl). 庫注冊序列化器.
  • While registering,庫檢查是否序列化器是SerializerRegistryInjector接口的一個實例. If yes如果是, SerializerRegistry (存儲所有序列化器引用)被注入進序列化器.

當序列化器需要訪問其他反序列化器時特別有用.例如IntructionsSerializer為了能夠處理OFPIT_WRITE_ACTIONS/OFPIT_APPLY_ACTIONS指令需要訪問ActionsSerializer.

Figure 16.4. Serialization scenario walkthrough

內部描述

SwitchConnectionProvider SwitchConnectionProvider構造和初始化序列化器和反序列化器注冊默認的(反)序列化器. 拒絕DeserializerRegistry 進入DeserializationFactory, SerializerRegistry 進入 SerializationFactory.當調用自定義的(反)序列化, SwitchConnectionProvider在適當的注冊表調用注冊方法.

DeserializerRegistry / SerializerRegistry為了初始化默認的(反)序列化器DeserializerRegistry / SerializerRegistry,注冊表都包含init()方法.注冊表檢查是否關鍵字或(反)序列化器實現不為null.如果至少有一個是null, 拋出NullPointerException.否則如果他是(De)SerializerRegistryInjector實例,(反)序列化器被檢查.如果它是這個接口的實例,注冊表被注入進(反)序列化實現.

GetSerializer(key) 或 GetDeserializer(key)執行注冊查找. 因為有兩個獨立接口可能放入注冊表,此注冊表用作它們的統一super接口. 獲得(De)Serializer(key) 方法 強制轉換super接口為所需的類型.從注冊表接收有一個null檢查為(反)序列化器.如果反序列化器沒有找到, NullPointerException 與秘鑰描述被拋出.

注冊秘鑰

反序列化. openflow擴展和秘鑰

這有三個供應商特性的擴展在Openflow v1.0和八個在Openflow v1.3.這些擴展被注冊在注冊密鑰下:

Table 16.1. Deserialization反序列化

序列化. openflow擴展和秘鑰

在Openflow v1.0有三個供應商特定擴展Openflow v1.3有七個.這些擴展被注冊在秘鑰下,如下表所示:

Table 16.2. 序列化

SDNLAB社區譯者 正在火熱招募中

 
成為譯者的好處:

  • 優質的英文原材料,最直接的提升英語能力
  • 提高社區影響力,國內極具影響力的SDN交流平臺
  • 最優的內容傳播途徑,認可才是硬道理
  • 社區福利免費拿,一手的學習資料
  • 分享推動SDN發展,提供國內新鮮的技術資料

什么樣的人才能成為譯者?

  • 熱愛分享、熱愛社區;喜愛SDN等網絡創新技術;

怎樣成為譯者?
1、添加微信:353176266 或點擊識別二維碼

2、進行自我介紹
3、閱讀社區提供的翻譯資料
4、翻譯測試

 
編譯類僅出于傳遞更多信息之目的,系SDNLAB對海外相關站點最新信息的翻譯稿,僅供參考,不代表證實其描述或贊同其觀點,投資者據此操作,風險自擔;翻譯質量問題請指正。


  • 本站原創文章僅代表作者觀點,不代表SDNLAB立場。所有原創內容版權均屬SDNLAB,歡迎大家轉發分享。但未經授權,嚴禁任何媒體(平面媒體、網絡媒體、自媒體等)以及微信公眾號復制、轉載、摘編或以其他方式進行使用,轉載須注明來自 SDNLAB并附上本文鏈接。 本站中所有編譯類文章僅用于學習和交流目的,編譯工作遵照 CC 協議,如果有侵犯到您權益的地方,請及時聯系我們。
  • 本文鏈接http://www.taian720.com/16581.html
分享到:
相關文章
條評論

登錄后才可以評論

SDNLAB君 發表于16-04-19
0