通過opendaylight源碼解讀控制器與openflow交換機建立過程

opendaylight兩大技術特色:1採用了osgi框架2引入了SAL,而今天我們主要介紹服務抽象層(SAL)適配的南向協議之一OF協議模塊

OF協議模塊啓動與消息處理

osgi框架實例化controller類,初始化其變量包括事件隊列、消息監聽及交換機監聽器集合,然後創建事件處理線程,在創建I/O處理線程。controllerIOThread監聽底層交換機連接請求,建立連接則監聽消息,當收到消息後判斷消息類型再調用相應方法處理:

while (running) {
    try {
        // wait for an incoming connection
        // check interface state every 5sec
        selector.select(5000);
        Iterator<SelectionKey> selectedKeys = selector
                     .selectedKeys().iterator();
        netInterfaceUp = isNetInterfaceUp(netInterfaceUp);
        while (selectedKeys.hasNext()) {
           SelectionKey skey = selectedKeys.next();
           selectedKeys.remove();
           //selector選擇器接收連接請求
           if (skey.isValid() && skey.isAcceptable()) {
                  ((Controller)listener).handleNewConnection(
                               selector,serverSelectionKey);
           }
        }
    } catch (Exception e) {
          continue;
    }
}

handleNewConnection從事件隊列中獲取處理事件,如果是新增交換機事件,則換存該交換機並通知監聽器交換機信息改變;如果是刪除或異常事件,則斷開I/O連接;如果是OFMessage消息,則通知SwitchHandler來處理該消息。

I/O處理線程中的消息處理

TCP 連接建立後,交換機和控制器就會互相發送 hello 報文(SwitchHandler處理函數handleMessages處理的第一個消息類型)。Hello 報文是使用 OpenFlow 協議的一個對稱的數據包。Hello 報文中唯一的內容 是 OpenFlow 報文頭中的“類型值=0”。

for (OFMessage msg : msgs) {
    logger.trace("Message received: {}", msg);
    this.lastMsgReceivedTimeStamp = System.currentTimeMillis();
    OFType type = msg.getType();
    switch (type) {
    case HELLO:
        sendFeaturesRequest();
        break;
    case ECHO_REQUEST:
        OFEchoReply echoReply = (OFEchoReply) factory.getMessage(OFType.ECHO_REPLY);

        byte []payload = ((OFEchoRequest)msg).getPayload();
        if (payload != null && payload.length != 0 ) {
            // the response must have the same payload as the request
            echoReply.setPayload(payload);
            echoReply.setLength( (short)(echoReply.getLength() + payload.length) );
        }

        // respond immediately
        asyncSendNow(echoReply, msg.getXid());

        // send features request if not sent yet
        sendFeaturesRequest();
        break;
    case ECHO_REPLY:
        this.probeSent = false;
        break;
    case FEATURES_REPLY:
        processFeaturesReply((OFFeaturesReply) msg);
        break;
    case GET_CONFIG_REPLY:
        // make sure that the switch can send the whole packet to the
        // controller
        if (((OFGetConfigReply) msg).getMissSendLength() == (short) 0xffff) {
            this.state = SwitchState.OPERATIONAL;
        }
        break;
    case BARRIER_REPLY:
        processBarrierReply((OFBarrierReply) msg);
        break;
    case ERROR:
        processErrorReply((OFError) msg);
        break;
    case PORT_STATUS:
        processPortStatusMsg((OFPortStatus) msg);
        break;
    case STATS_REPLY:
        processStatsReply((OFStatisticsReply) msg);
        break;
    case PACKET_IN:
        break;
    default:
        break;
    } // end of switch
    if (isOperational()) {
        ((Controller) core).takeSwitchEventMsg(thisISwitch, msg);
    }
} // end of for

1.首先讀取到Hello消息後,發送請求報文(Feature Request)。這是控制器發向交換機的一條Openflow消息,目的是爲了獲取交換機性能,功能以及一些系統參數。該報文中OpenFlow數據頭“類型值=5”。
2.Echo請求(Echo request)和Echo 響應(Echo reply)屬於OpenFlow中的對稱型報文,他們通常作爲在OpenFlow交換機和OpenFlow控制器之間保持連接的消息(Keep-alive)來使用。通常echo請求使用OpenFlow數頭“類型值=2”,echo響應使用OpenFlow數據頭“類型值=3”。不同各廠商提供的不同實現中,echo請求和響應報文中攜帶的信息也會有所不同。如果是Echo request消息,則發送Echo reply響應和Feature Request請求消息;如果是Echo reply消息,則標識交換機正常連接
3. 控制器和交換機之間的連接經過 TCP 建立、Hello 報文、功能請求與響應環節後建立。這些連接的存在是 Packet-In 事件發生的前提。交換機怎樣觸發 Packet-In 事件。當 OpenFlow 交換機收到數據包後,如果流表中與數據包沒有任何匹配條目,這時候 Packet-In 事件就被觸發了,交換機會將這個數據包封閉到Openflow 協議報文中發送至控制器。 opendaylight中Packet-In 事件是交給了IMessageListener監聽器的實現類來處理的,比如DataPacketMuxDemux類,Packet-In 域提供數據包的信息,這些信息都是在那些特定的 Packet-In 被封裝的。得到 Packet-In 信息後,控制器根據需要對原始數據包做出處理。
case SWITCH_MESSAGE:
OFMessage msg = ev.getMsg();
if (msg != null) {
IMessageListener listener = messageListeners
.get(msg.getType());
if (listener != null) {
listener.receive(sw, msg);
}
}
break;

4.控制器要發送數據包至交換機時,就會觸發 Packet-Out 事件將數據包發送至交換機。這一事件的觸發可以看做是控制器主動通知交換機發送一些數據報文的操作。通常,當控制器想對交換機的某一端口進行操作時,就會使用 Packet-Out 報文。
5. 交換機端口狀態發生改變(端口 up/down、增添或移除)或者端口配置標誌發生改變時,會觸發端口狀態(Port Status)消息事件的發生。這一消息由 OpenFlow 交換機觸發,端口狀態(Port Status)消息由 OpenFlow 交換機發往控制器,用於通告交換機端口狀態的改變。
6. 當控制器想要改變交換機的配置時,就會發送一個設置配置信息,這信息是控制器—>交換機信息。設置配置信息(Set Configuration)包括:
- 交換機配置標誌
- Miss發送長度,表示重新配置後發送至控制器的流的最大8位字節,默認值128。
7.獲取配置請求的報文(Get Config)中沒有內容(只包含 OpenFlow 常規數據頭);OpenFlow 交換機通過“TypeCode = 7”識別這個報文。 交換機發出配置答覆消息作爲反饋,該消息包含了交換機的所有配置信息,配置答覆消息包括:
- 交換機配置標誌
- Miss發送長度
- 表示發送至控制器的新的流的最大8位字節,默認值128。
8.當控制器需要增添、修改或刪除交換機中流表的時候,觸發 修改流(Flow-Modification)事件。
9.控制器(管理員)試圖修改端口配置標誌—“admin down,” “no STP,” “no receive,” “no receive STP,” “no flood,” “no FWD,” “no packet-in” 的時候,會觸發修改端口(Port-Modify)事件
10.當控制器試圖從交換機處獲得不同類型的統計數據信息時,統計(Stats)請求和響應事件被觸發。
11.當控制器試圖瞭解其分配給 OpenFlow 交換機的任務是否完成或將在何時完成的時候,Barrier 請求和響應事件將被觸發。Barrier 請求消息用OpenFlow 數據頭消息“類型值=19”表示。 收到請求消息的交換機,在完成控制器分配的任務後,會發送響應消息至控制器。 障礙響應消息用 OpenFlow 數據頭消息“類型值=20”表示,並附有 Barrier 請求消息的交換標識。
12.當控制器試圖問詢 OpenFlow 交換機端口的隊列配置時候,觸發隊列獲取配置(Queue Get Configuration)請求和響應事件。隊列請求包括所請求隊列信息的端口號。隊列配置響應消息包括端口號和該端口的隊列配置信息。
13.當控制器發送的數據包不能被讀出或支持,或者交換機不能執行的時候,就產生了錯誤事件。所以任何發送至交換機的控制數據包都可能觸發錯誤事件。

鏈路發現(LLDP)

OF協議模塊還提供鏈路發現服務,它爲拓撲模塊提供鏈路數據支持(topomanager實現IListenTopoUpdates的edgeUpdate,它是有TopologyServiceShim中的線程TopologyNotify調用,此線程是個阻塞線程,notifyEdge方法使得notifyQ的成員的增加會觸發此線程,而notifyEdge是由DiscoveryService的updateEdge調用的,依次addEdge,processDiscoveryPacket,receiveDataPacket DataPacketMuxDemux通過receive調用receiveDataPacket receive是由Controller監聽SWITCH_MESSAGE消息時調用的)。

public PacketResult receiveDataPacket(RawPacket inPkt) {
    if (inPkt == null) {
        logger.debug("Ignoring null packet");
        return PacketResult.IGNORED;
    }

    byte[] data = inPkt.getPacketData();
    if (data.length <= 0) {
        logger.trace("Ignoring zero length packet");
        return PacketResult.IGNORED;
    }

    if (!inPkt.getEncap().equals(LinkEncap.ETHERNET)) {
        logger.trace("Ignoring non ethernet packet");
        return PacketResult.IGNORED;
    }

    NodeConnector nodeConnector = inPkt.getIncomingNodeConnector();
    if (((Short) nodeConnector.getID()).equals(NodeConnector.SPECIALNODECONNECTORID)) {
        logger.trace("Ignoring ethernet packet received on special port: "
                + inPkt.getIncomingNodeConnector().toString());
        return PacketResult.IGNORED;
    }

    if (!connectionOutService.isLocal(nodeConnector.getNode())) {
        logger.debug("Discoery packets will not be processed from {} in a non-master controller", nodeConnector.toString());
        return PacketResult.IGNORED;
    }

    Ethernet ethPkt = new Ethernet();
    try {
        ethPkt.deserialize(data, 0, data.length * NetUtils.NumBitsInAByte);
    } catch (Exception e) {
        logger.warn("Failed to decode LLDP packet from {}: {}", inPkt.getIncomingNodeConnector(), e);
        return PacketResult.IGNORED;
    }

    if (ethPkt.getPayload() instanceof LLDP) {
        NodeConnector dst = inPkt.getIncomingNodeConnector();
        if (isEnabled(dst)) {
            if (!processDiscoveryPacket(dst, ethPkt)) {
                // Snoop the discovery pkt if not generated from us
                snoopDiscoveryPacket(dst, ethPkt);
            }
            return PacketResult.CONSUME;
        }
    }
    return PacketResult.IGNORED;
}

控制器在執行鏈路發現過程時,會首先通過一個packet-out消息向所有與之連接的交換機發送LLDP數據包,該消息命令交換機將LLDP數據包發送給所有端口,一旦交換機接收到packet-out消息,他就會把LLDP數據包通過其所有的端口發送給與之連接的設備,如果其鄰居交換機是一臺OpenFLow交換機,那麼該交換機將自行相應的流表查找操作。因爲交換機中並沒有專門的流表項用於處理LLDP消息,所有它將通過一個packet-in消息將數據包發送給控制器。而控制器在收到packet-in消息後,會對數據包進行分析並在其保存的鏈路發現表中創建2臺交換機之間的鏈接記錄。網絡中其他交換機也都採用相同的方式向控制器發送packet-in消息,因此控制器就能夠創建完整的網絡拓撲視圖,基於這樣的視圖,控制器可以根據業務應用的流量需求,爲每臺交換機推送下發不同的流表項。鏈路發現可分爲兩個部分:
1.LLDP數據分組監聽解析,通過LLDP分組中LTV信息獲得節點鏈路狀態,保存到本地同時通知拓撲模塊變化及時更新。
2.自動鏈路探測部分,通過控制器的交換機連接信息獲取探測節點,發送LLDP探測分組,再由監聽部分獲取探測分組更新鏈路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章