詳解floodlight控制器的路由轉發機制

Floodlight路由機制解析

路由部分是floodlight最核心的機制,這兩天仔細讀了一下floodlight這部分的代碼,總算有了大體上的瞭解,與各位分享。

本文中的floodlight(FL)與控制器/網絡控制器(NC, nework controller ) 等術語等同,交換機(SW)默認爲openflow-enabled switch,不再贅述。

首先談一下SDN控制器的路由原理:當交換機收到一個不能被當前流表各條流匹配的數據包時,會把這個數據包以openflow的格式(PACKET_IN)發送給控制器。控制器經過路由決策後,同樣以openflow的格式(PACKET_OUT)的方式將該數據包的下一跳信息回給該交換機。

如果學過最短路徑計算的同學一定知道,兩點之間的最短路徑計算首先要知道任意兩點之間是否相連,如果兩者間有link相連還需知道其長度。所以總體而言,FL路由包括兩部分:拓撲更新和路由計算,前者是定時事先完成的,形成一個全局的拓撲結構,後者是收到數據包後運行時完成的,根據拓撲生成路由策略。

首先看拓撲更新。SDN環境中拓撲是集中控制的,故FL需要了解全局的拓撲環境,所以FL在鏈路更新時自動更新拓撲。

  • 1 鏈路更新

當交換機加入SDN環境後,控制器通過LLDP協議定時地獲得該交換機與其他設備連接的link信息,然後添加或更新這些link(LinkDiscoveryManager.addOrUpdateLink()),最後將這些link更新的事件添加到updates隊列中(LinkDiscoveryManager.handleLldp())。

  • 2 拓撲更新

計算路由的關鍵在於路徑更新和路由計算,該過程爲:
2.1 啓動 首先拓撲管理啓動(TopologyManager.startUp())時新起一個線程UpdateTopologyWorker,間隔500ms重複運行 。
2.2 更新拓撲 如果之前在第一步中有一些鏈路、交換機等更新,那麼LDUpdates隊列中會有元素,那麼依次取出這些鏈路更新的元素(TopologyManager.updateTopology()/TopologyManager.applyUpdates()),判斷其類型(如鏈路更新/刪除、端口增加/刪除),進行響應處理。
以鏈路更新爲例,調用TopologyManager.addOrUpdateLink()方法,判斷鏈路類型(多跳、單跳或隧道),再把link添加到相應交換機的端口中,如果是多跳的則再刪除可能的單跳link
2.3 計算拓撲路徑 每次新建一個實例(TopologyManager.createNewInstance()),初始化過程中計算拓撲路徑(TopologyInstance.compute()),具體地:
2.3.1 歸類 將現有所有相連的link置於同一個簇中,從而形成多個簇結構(identifyOpenflowDomains())
2.3.2 建簇 遍歷所有link(addLinksToOpenflowDomains()),如果link連接的是同一個簇,則將其加入該簇,所以該方法統計了所有簇內的link。FL這麼做也是減少路由計算的開銷,不然若干個大二層網絡中共有n個節點,就需要n平方的存儲開銷,計算效率也會下降,如果將不相連的節點分開就減少了上述開銷。
2.3.3 遍歷所有簇 對於每個簇內sw,形成一個BroadcastTree結構(包括兩個屬性:cost記錄到其他簇內sw的路徑長度,links記錄其他sw的link),最終將信息保存到destinationRootedTrees中(caculateShortestPathTreeInClusters ()和calculateBroadcastNodePortsInClusters()),核心思想就是使用dijkstra計算簇內任意兩點的最短路徑,保存到該點的cost和下一跳。
2.4 定義路徑計算模式 這樣,FL就獲得了從一個點出發所有相連的所有路徑和點。那麼只要給定一個源SW節點和一個目的SW節點,那就能知道這兩者間的最短路徑長度和。早在2.3開始TopologyInstance在定期新建實例時,就定義了根據拓撲計算路徑的方法:

 
pathcache = CacheBuilder.newBuilder().concurrencyLevel(4)
                    .maximumSize(1000L)
                    .build(
                            new CacheLoader() {
                                public Route load(RouteId rid) {
                                    return pathCacheLoader.load(rid);
                                }
                            });

這裏的pathcache是一個類似hash表的結構,每當被調用get時執行pathCacheLoader.load(rid),所以這裏沒有真正計算路由路徑,只是一個註冊回調。具體運行時在後面3.6中的getRoute方法中被調用:

 
            result = pathcache.get(id);

在TopologyInstance.buildRoute方法實現該路由rid的計算:先確定目的sw,因爲2.3.2中以獲得源目的sw的最短路徑,然後根據nexthoplinks迭代查找該路徑上的所有sw,最終形成一個path。
雖然2.4中沒有最終計算節點間的路徑,但是2.3中使用dijkstra計算了任意兩點間的距離,基本上已經完成了90%的路由計算功能,在大二層網絡中這也是不小的開銷。

  • 3 路由計算

當完成拓撲計算後,FL在運行時可計算輸入的數據包應走的路由。這裏還需說明一下,FL的某些模塊監聽PACKET_IN數據包,Controller收到這個數據包後,會逐個通知這些模塊,調用其receive方法:

 
                    for (IOFMessageListener listener : listeners) {
                        pktinProcTime.recordStartTimeComp(listener);
                        cmd = listener.receive(sw, m, bc);
                        pktinProcTime.recordEndTimeComp(listener);

                        if (Command.STOP.equals(cmd)) {
                            break;
                        }
                    }

更詳細的模塊加載和監聽機制可參考我之前寫的這篇文章http://blog.marvelplanet.tk/?p=424
那麼回到路由計算這部分來,Controller會依次調用以下模塊:
3.1 LinkDiscoveryManager鏈路處理模塊,如果數據包是LLDP處理該消息,如步驟1中在此處會有處理;如果是正常的數據包則略過,所以這裏忽略
3.2 TopologyManager拓撲管理模塊,查看源端口在TopologyInstance是否允許轉發
3.3 DeviceManager設備模塊,通過源目SW的id和port相連的device找到源設備和目的設備,如找不到則放棄,否則暫存入一個上下文結構context
3.4 Firewall防火牆模塊,判斷該數據包的源目的主機是否可以通信,如不在規則表中則繼續
3.5 LoadBalancer負載均衡模塊,如是ARP數據包則查看是否在vipIPToId中,如不在則繼續。這裏關係不大,忽略。
3.6 ForwardBase路由模塊,首先在上下文context查找之前模塊是否已有路由策略,如無則檢查是否數據包是廣播的,如是則Flood。否則調用doForwardFlow,我們主要看這個方法,具體的,從上下文中獲取3.3中解析的源目的主機信息,如果不在同一個iland上(我的理解是這兩臺主機在FL的知識庫中沒有路徑,也許中間有別的傳統方法相連,也許根本不相連),則Flood;如果在同一個端口,那就是同一臺主機,則放棄;否則調用routingEngine.getRoute獲得路由。這個纔是最重要的,也是2.4中沒有完成的最終部分,這裏使用了TopologyInstance的getRoute方法,使用2.4的方法計算出路由路徑,然後調用將路由所對應的路徑推送給交換機,如果是多跳路徑,則將路由策略推送給該路徑上的所有交換機(ForwardBase.pushRoute())。

最後給出一個場景吧,假定h1和h2是主機,s1、s2和s3是交換機,拓撲是h1-s1-s2-s3-h2。當h1 ping h2,首先h1發送ARP包,由於該數據包是廣播的,那麼最終ForwardBase會執行doFlood,所有交換機都會廣播這個數據包,最後h2收到s3廣播的的ARP請求後,向h1發送ARP響應。h1收到ARP響應後發送ping請求,該包在交換機以PACKET_IN的形式發送給floodlight,此時floodlight知道h1所在的交換機s3,所以調用doForwardFlow,計算出
s1到s3的路徑s1->s2,s2->s3,然後將OFFlowMod命令發送給s1、s2和s3。最終數據包通過s1、s2和s3直接被髮送到h2,搞定。

p.s.1 Floodlight在計算拓撲calculateShortestPathTreeInClusters時,首先會將所有的路徑置爲失效,然後在計算所有兩點間距離,如果每次(間隔爲500ms)存在一條鏈路變動,那麼就需要重做所有計算,在大二層網絡中會不會出現性能瓶頸?
p.s.2 Floodlight的計算出路由後,下發的流只包括源目的主機的mac,流的其他字段都是通配符,這麼做的邏輯應該是“我只做路由和轉發功能,別的不用控制”。這樣會給北向的APP造成很大的麻煩,例如在做DDoS的防禦時,需要檢查每條流的目的端口,如果流的dst_transport_addr是*的話,顯然無法檢測攻擊,甚至連FL自帶的Firewall都力不從心。
p.s. 一般而言,交換機的端口與與其他端口相連,有兩個對稱的link。但TopologyManager中有一類特殊的端口broadcastDomainPorts,這類是廣播端口,不是一般的端口,沒有對稱的link。

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