【概述】linux
Click是一種基於軟件控制的模塊化路由器。其架構能夠大體視爲一系列數據包處理模塊(稱爲elements)組成的。一個Click路由器能夠當作一張由elements做爲頂點,數據包傳遞路徑做爲邊的圖。這種模塊化設計使得內部功能結構清晰且易於拓展。程序員
1.介紹算法
如今的趨勢下,路由器的功能已經再也不是單純用於包轉發,每每還同時具備地址轉換、包過濾等功能,並每每扮演着防火牆一類的角色。可是,如今的路由器設計的都很封閉,管理員僅僅能控制功能的開關,卻很難實現各個功能之間更爲複雜的交互,而第三方開發者也很難對路由器功能進行拓展。網絡
所以就有了Click模塊化路由器,它能夠提供細粒度的網絡控制。Click由一系列的稱爲element的功能模塊組成。這些elements都具備很窄的功能interface,提供最爲atomic的功能實現,可是能夠經過多個elements互相之間的拓展來完成更爲複雜的功能。這些elements拼成一個有向圖(directed graph),就造成了一個模塊化的路由器。要拓展一個路由器的功能,程序員能夠本身寫新的功能模塊,也能夠將已有的功能模塊從新組裝,完成不一樣的功能。 (文中提到這個idea和Unix很類似?我不太懂Unix,有空能夠了解一下。)架構
2.架構app
正如前面所說,Click由一系列的elements構成,而全部的Click路由器的原子化的功能都包含在elements中。具體來講,在一個running router 中,一個element就是一個C++對象,一條connection就是一個指向該對象的指針,而一個傳遞動做就是一次內部函數的調用。框架
一個element有以下重要的屬性:dom
1) element類 (element class)ide
正如前面所述,一個element就是一個C++的對象,所以也就有一個class與之對應,element屬於該element class。模塊化
2) 端口(port)
一個element能夠由多個輸入輸出的端口。全部的connection都是始自於一個element的輸出端口,終於另外一個element的輸入端口。
3)配置語句 (configuration string)
配置語句是一個可選項,用於在路由器初始化時配置其中的狀態。
4)方法接口(method interface)
一個element能夠提供一個或多個接口,如最基本的router能夠提供packet transfer的接口,最基本的包轉發功能,而支持隊列的element能夠提供回報queue length的接口。
2.1 push和pull鏈接
Click路由器中的鏈接分爲push和pull兩種類型。push鏈接是常規的包轉發鏈接,由源element發送一個packet出來到目的element。而pull鏈接則是相反,由目的element向源element發出一個request索取一個包,再由源端口回覆給它。一個鏈接是push仍是pull和鏈接兩端的端口有關。push端口到push端口的就是push鏈接,pull端口到pull端口的就是pull鏈接。而push到pull端口的鏈接非法。
路由器中還有一種端口稱爲agnostic,也就是未指定狀態的端口,當它和push端口創建鏈接時就是push端口,當它和pull端口創建鏈接時就是pull端口,所以能夠看做是一箇中性的狀態。可是須要注意的是,當一個router要進行propagation以前,全部沿途的端口都必須賦予相應且合法的狀態,在此以前路由器沒法進行轉發功能(propagation constraint)。
2.2 數據包存儲
element的端口上並不默認包含隊列(queue),在Click中,隊列自己就是一個element,也就是一個對象(或說是一個類)。這也就意味着網絡管理員能夠顯式的定義數據是如何存儲的。一個Queue須要一個push的input端口和一個pull的output端口。
2.3 CPU調度
Click控制着一個任務隊列(task queue),這個隊列內的內容單元就是一個個element。經過調度這個隊列,路由器能夠安排在某個時間內哪一個element的功能能夠調用CPU的資源。換言之,element既是包處理的最小單元,也是CPU佔用控制的任務隊列中的最小單元。可是大多數element並不須要放在任務隊列中,而是經過push和pull方法輪到它時自動隱性調用的。
Click目前是以單線程運行的,也就是說從一個包轉發開始,CPU跟蹤這個包走過一個個element,同一時間內只會有一個element在運行處理這個包,一直到這個包被drop或被store。
2.4 Flow-Based Router Context
一個element想找到另外一個element有兩種方法,一種是經過名字來調用,另外一種是經過Flow-based router context來調用(不知道怎麼翻譯了,簡稱FBRC吧)。FBRC能夠用來聲明一個從某一個element出來的packet通過若干次transfer之後在哪裏停下來,也能夠用來聲明到達某一個element的packet是從哪裏發出來的,說簡單點就是「從哪來」和「到哪去」。更具體點說,一個element可能會向系統發問「若是我這個包從端口2發出去,它可能會到哪裏去?」這類問題的答案都是FBRC通過計算之後加以回覆的。FBRC使用的是一種配置拓撲圖的簡單數據流算法,通常來講elements會在初始化的時候詢問一次FBRC,並在路由器運行期間使用這個結果做爲快速reference,而element須要具備接受多個result並在結果過多或過少時報錯的能力。
2.5 Click語言
Click語言中主要包含兩個重要部分,聲明(declaration)和鏈接(connection)。聲明是用來建立element的,鏈接則用來指明element之間是怎麼互相關聯的。Click語言中還包含一種抽象機制稱爲「複合元素」(compound element)。它能夠包含多個基本element,如一個queue元素加一個shaper元素能夠合併爲一個Shapedqueue元素,而其餘配置則能夠將其視爲一個總體元素加以調用。
2.6 安裝配置
目前Click能夠運行在兩種驅動平臺上,一種是Linux內核驅動(Linux in-kernal driver),一種是用戶層驅動(user-level driver)。用戶層驅動通常是用來作調試使用的,而內核驅動則每每用來用做生產。咱們暫時就關注於內核驅動。
要安裝一個Click的配置,用戶首先須要將一個寫好的Click文件傳到核心驅動中,由核心驅動經過分析該文件來生成相應的elements並將路由器加入網絡中。一次新的配置的安裝會移除舊的配置及其狀態,如queue中的包等。可是,Click包含兩種手段可使得原配置不被丟失:
1) Handlers: Handlers如同一個element面向用戶的界面。一個element中能夠包含多個handler,若是這個element叫c,那麼一個名爲count的handler就是一個放在proc/click/c 路徑下的名爲count的文件。
2)Hot swapping(熱插拔):有的時候對於configuration的更改多是增添或刪除element,也就意味着沒法使用element內部handler進行配置的保留。此時可使用熱插拔的方法,即寫一個新的configuration file,只有當這個配置運行正常時才使用這個配置,若是這個配置出問題則會從新降級到本來的配置中。經過利用這種熱插拔機制,Click還能夠實如今路由器運行時動態增刪elements。
2.7 Element的具體實現
簡潔的說,一個element是一個element class的對象,一個element class是一個名爲Element類的C++子類,Element中包含了20多種虛函數,可是因爲大多數虛函數中已經有了默認的實現,所以在繼承時其實只須要重載其中的五六種方法,而若是要實現最基本的路由功能則只須要三個方法就夠了。
2.8 Element的設計體系及限制
通常來講,用戶都傾向於細粒度的element和更爲簡單的標準。可是有的狀況下粗粒度的控制會更好一些,特別是在Click中不少對於流的處理,每每不會對於一個流內部的不一樣包進行細粒度的控制,或者是一些複雜的域間協議也不會對內部的具體內容單元進行單獨的控制。
傳統的路由器中會包含一些並不實際參與具體路由動做的功能(而是起到一些路由前的決策和準備的做用),例如路由表,網絡參數統計等,這些功能在Click中都包含在數據包的轉發路徑中的某些element中,例如路由表會包含在選擇路徑的element中,而網絡統計也會包含在相應的收集模塊中。這些模塊也會向外界提供相應的方法來調用這些內部的功能。
Click也具備一些限制和待完善的地方:
1)Click沒法讓CPU針對每個特定的流來調度;
2)Click提供的FBRC機制缺少針對性(not specific);
3)Click語言不提供配置變量,例如沒法將一個Ethernet address賦值給一個變量a並調用它,而必須屢次重複顯式寫出來這個地址值。
3. 一個IP 路由器
該部分展現了一種真實的Click路由器的配置。IP轉發任務因爲只包含本地信息,所以能夠很天然的應用於Click的架構當中。舉一個DecIPTTL的例子。DecIPTTL被用於檢測一個進來的IP包的TTL(time-to-live)字段,若是一個包的TTL依然有效,則DecIPTTL會將該字段減1,修改checksum並從第一個端口轉發出去;若是一個包的TTL已通過期,則DecIPTTL將會把這個包從端口2發送出去(每每發送至一個ICMPerror的element中),之因此這種操做都是基於本地信息,是由於諸如TTL字段的判別和decision只是包含在packet當中,而不受該包的轉發路徑上其餘element的影響。
另外一類狀況就是一個element對於包的操做使受到其餘element的影響的,也就再也不是基於本地信息了。在Click中針對這類操做可使用一種稱爲註釋(annotations)的方法,註釋每每由某個element添加並跟隨在包的header當中,可是並不算packet data的一部分。在本部分所舉的IP router的例子中也會使用這種註釋的方法,具體有以下幾種:
1)目標IP地址: 雖然一個IP包的header中包含了目標地址,可是因爲在路由過程當中的中間路由器可能並不須要知道IP的目標地址(例如僅僅須要知道下一跳網關地址),所以element之間可能就會頻繁修改這個目標地址。可是這種轉發操做不該該修改數據包自己字段,所以可使用註釋的方法,element之間僅僅查看和修改對應的目標地址註釋從而肯定轉發路徑。和目標IP地址註釋一塊兒使用的有幾個方法:GetIPAddress用於從header中複製目標地址到annotation中;LookupIPRoute用於替換當前annotation到下一個IP網關地址;ARPQuerier則用於將當前annotation映射到下一個以太網地址。
2)着色(paint ):着色element能夠講一個包標記爲一個整型的「顏色」,CheckPaint能夠將一個包從它的第一轉發口發送出去,並在次轉發口發送出該包着色之後的備份,IP路由器經過這種機制能夠知道一個數據包是不是從它的接受端口發送出去,從而能夠探測這種轉發並觸發ICMP重定向(ICMP Redirection)。
*附:什麼是ICMP重定向【來源:百度百科】
ICMP重定向報文是ICMP控制報文中的一種。在特定的狀況下,當路由器檢測到一臺機器使用非優化路由的時候,它會向該主機發送一個ICMP重定向報文,請求主機改變路由。路由器也會把初始數據包向它的目的地轉發。
發生ICMP重定向一般有兩種狀況:
1)當路由器從某個接口收到數據還須要從相同接口轉發該數據時;
2)當路由器從某個接口到發往遠程網絡的數據時發現源ip地址與下一跳屬於同一網段時。
3)鏈路層廣播標記(Link-level broadcast flag):FromDevice用於設置這個標誌位,表示一個包是以鏈路廣播的形式發送過來的,當發現這類數據包將要發送至其餘接口時,IP路由器使用DropBroadcast的方式來丟棄這類包。
4)ICMP參數錯誤指針(ICMP Parameter Problem pointer):這個註釋是由IPGWOptions設置的,用於聲明該包的IP header字段中包含錯誤信息,並被ICMPError查看並用來生成ICMP錯誤信息。
5)修正IP源標記(Fix IP Source Flag):因爲ICMP Error消息包的源地址必定是發生錯誤的接口,可是ICMPError並不能預測這個接口,所以它發送出的數據包在該字段中使用默認值並伴隨一個Fix IP Source 的註釋,用於提醒沿途的FixIPSrc添加正確的源地址並從新計算校驗。
還有一種狀況下,狀態參數的傳遞並不侷限於elements之間,而多是element須要獲取到更爲困難的全局變量,例如整個路由器在網絡中的IP地址,或所處網絡的廣播地址,Click中的模塊不少狀況下必須掌握這些信息。每一個模塊都須要掌管一個list來存儲全部這些全局信息,更爲理想的狀況是能自動獲取這個list(例如經過FBRC)。
對於原有IP標準的一致性同時體如今Click的element自己以及elements之間的順序上。單個的element表現爲:例如CheckIPHeader會檢查IP頭是否符合規範且合法;IPFragment會把大於MTU的包進行分塊,並把沒法分塊的過大的包發送到報錯的端口;ICMP error 消息不會對廣播包、ICMP錯誤消息保等進行回覆。Elements之間的順序表現爲:例如DecIPTTL被安排在LookupIPRoute以後,由於只有判斷這個包不是發送給本身的時候纔會把TTL減1。
4. Click拓展模塊
Click的轉發路徑上總共要通過16個模塊,在這章中要展現的就是模塊化帶來的易於拓展的好處。
4.1 調度(scheduling)
所謂調度的意思就是讓一個路由器在多個輸入的狀況下複用(multiplex)一個輸出端口。Click中的scheduler表現爲一個pull element,當這個調度器被要求發送出一個數據包時,會按照必定的算法每次從多個輸入端口中選取一個端口的數據包並將其返回。目前主要實現的有三種調度器:RoundRobinSched,PrioSched和StrideSched。
RoundRobinSched顧名思義使用的就是RoundRobin規則(即輪詢算法)來進行輸入端口的選取;PrioSched是嚴格依照優先級進行調度,它始終優先傳送優先級最高的輸入端口,在一次傳送次優先級的端口內容;StrideSched使用了stride scheduling 算法,簡單來講,stride scheduling就是一種按比例分配的算法,經過設定參數使得某個輸入被輪到的頻率是另外一個輸入被輪到頻率的n倍。
因爲常規的隊列(queue)和一個調度器(scheduler)只是在輸入端口的選擇上內部處理不一樣,但對外表現都是同樣的。具體來說,它們的輸出端口都是pull類型的,所以下游element並不知道數據是從隊列傳來仍是從scheduler傳來的。因此一個scheduler能夠用做一個virtual queue,從而對外表現的像一個隊列可是能夠實現比FIFO更復雜的數據轉發機制。
4.2 丟棄機制(dropping policies)
在一個Queue的element中每每會應用一個最簡單的丟棄機制,即超出隊列最大容量的全部包都會被丟棄,而另外一些policies可能會在Queue的這種機制基礎上加以改進,如使用替換的方式。例如一個RED(Random Early Dection)的element可能會使用RED方法來drop過多的包,而這種dropping policy是獨立於queue的數據存儲功能的,只是在Queue基礎上增長功能而已。
RED在檢測到網絡擁擠時有較大機率開始drop數據包。當一個隊列中有不少包是發往同一鏈路時,該鏈路就被認爲是擁擠的,此時RED模塊就會查看路由器中各個隊列的長度來決定是否要丟棄數據包,而在實際應用,RED模塊每每就看下游最近的Storage element(Storage是全部具備存儲功能的element向外提供的共有的接口,如queue或相似的模塊都會具備Storage的接口),這種查看是經過FBRC完成的。
RED能夠支持下游的多個隊列(而不僅是查看一個)。若是RED下游有多個隊列,返回的packet count會是全部隊列模塊的和(一個virtual count),這種機制可使得RED有不一樣的變體:
a.例如把一個RED放在scheduler的兩個隊列以前,則會計算這兩個隊列總共的數據包。
b.另外一個例子是使用weighted RED,即packet會根據它們的優先級按不一樣的機率進行丟棄。
c.第三種狀況是RED放在了queue的後面(而不是前面),這時RED表現爲一個pull element並會檢測上游的Storage element(而不是下游的),從而應用相似於drop-from-front RED的策略。
最簡單的RED dropping功能能夠經過在queue以前加RED模塊來實現。
4.3 複雜拓展與簡單分組
Click能夠同時勝任簡單或複雜的任務,一個網絡管理員可讓一個Click路由器完成特定的路由功能,也能夠將Click路由器部分的element用於其餘應用中。
4.4 區別化服務
在Click中,邊界路由器和核心路由器能夠共同提供區別化服務,管理聚合的數據流。邊界路由器負責將不一樣服務等級的數據包進行分類並打上標籤,保證相應服務等級的數據流不會以太高的速率進入網絡;核心路由器則經過這些標籤來對流進行調度和隊列管理。
上圖就是一個區別化服務的具體應用框架。輸入的數據包經過IP頭部的DSCP(Diffierentiated Services Code Point)字段來劃分爲4個不一樣的traffic streams。前三個(A,B,C)流是速率受控(rate limited)即提供保障的流,第四個(D)是提供最普通的盡力而爲服務的數據流。具體來講:A經過使用dropping機制來達到速率控制,既當每秒有超過7500個包到達時就會把多於的包丟棄;B是使用shaper進行整形,每秒容許10000個包經過shaper,多的包則存在前面的queue中;C是使用重分類(reclassification)機制,即超過設定數量的包會被從新標記爲D類,降級進行盡力而爲的傳輸。
4.5 以太網交換機
Click也能夠用來構造非IP層的交換機,以下圖中給出的就是一個符合IEEE 802.1d標準的以太網交換機。
該交換機自身以學習網橋(learning bridge)的方式運行,並能夠和其餘的802.1d網橋一塊兒在網絡中運行生成樹協議。在該交換機中,EthernetSwitch提供簡單的學習網橋功能,EthernetSpanTree和Suppressor兩個element則只是爲了當LAN中存在多個交換機時防止環路的發生。其中EthernetSpanTree使用IEEE802.1d協議來爲網絡構建生成樹,Suppressor用於控制將一個數據包從某一個特定的端口轉發出去,同時Suppressor也對外提供方法接口來封鎖或解鎖一個特定的端口,全部到達Supressed的端口都會被丟棄(就是STP中的Block狀態)。須要注意的是,Supressors沒法經過FBRC來找到,所以使用者必須使用配置字符串(configuration string)使用名字來定位到它(在第二章提過)。
5. 內核環境
Click的核心線程是運行在linux2.2內核基礎上的,核心線程啓動路由器驅動並從而運行task queue中的多個任務。理論上只有中斷才能夠佔據一個運行的線程,可是出於對系統交互性(responsive)的考慮,Click會主動每隔一段時間將線程控制權交還於Linux來執行一些系統級操做。通常來講,在一個時間內只會有一個driver thread在運行,可是在thread初創建時可能會存在多個driver thread其中包含一個新線程和多個將要過時的老router thread。
Click運行在linux內核中,並使用linux的proc文件夾與用戶進行交互。若是想要編寫一個Click路由器,開發者能夠將寫好的代碼放在proc/click/config文件夾中。當安裝配置時,Click會在proc/click爲每一個element建立子文件夾,其中包含了每一個element的handler(2.6中提到)。
一個Click路由器中包含有四種對象類別:
1) Elements: 系統爲每個當前運行的element生成一個element對象。
2) Router: 一個router object會根據給定的router配置收集相關信息,包括配置elements,檢查connections,將router上線,並管理任務隊列。
3) Packets:Click的packet數據存儲於內存塊中,packet數據使用寫時拷貝技術(copy-on-write),即當須要copy一個packet時,系統只會copy這個包的header而不會copy它的data。註釋會以固定順序存儲於packet的header中,且沒法動態增長新的註釋。
4)Timers: 在Linux內核中,Click的timer經過Linux的timer queue來實現,在一個Intel PC上能夠由0.1s的分辨率。
5.1Poll和設備處理
最原始的Click是和Linux共享中斷機制和設備處理。雖然Click的宗旨仍是儘可能少的修改Linux,可是中斷的開銷和對於設備處理會很大程度影響performance,並且Click運行在較中斷更低的優先級上,所以會致使「活鎖「現象(livelock),即當input變多的時候,中斷處理就會漸漸starve其餘的操做,從而減小吞吐量。
如今的Click使用Polling代替了interrupt。用於設備處理的element(也就是FromDevice和ToDevice)將它們本身放在task queue中。Fromdevice會查看(poll)它的receive DMA queue是否有新到達的packets,若是有就依照configuration來push這些packet;ToDevice會檢查它的device transmit DMA queue來看是否有空的slot,若是有的話就從它的input中pull出packets並裝填進queue中。因爲這種內部運做機制,兩端的設備自己永遠不會調用中斷,從而使用Polling機制解決了中斷問題。不過,雖然因爲中斷調用的過度昂貴,在低強度的工做環境下,傳統的中斷仍是被容許的。
因爲這種Polling機制,Click會在原來的linux結構上有所調整,包括增長中斷的開關控制,查看接收數據包,以及清理transmitted DMA ring.