Linux TC(Traffic Control)框架原理解析


近日的工作多多少少和Linux的流控有點關系。自打幾年前知道有TC這么一個玩意兒而且多多少少理解了它的原理之后,我就沒有再動過它,由於我不喜歡TC命令行,實在是太繁瑣了。iptables命令行也比較繁瑣,可是比TC命令行直觀,而TC命令行則太過於技術化。

或許是我對TC框架沒有對Netfilter框架理解深刻吧。或許是的。iptables/Netfilter相應的就是tc/TC。
       Linux內核內置了一個Traffic Control框架。能夠實現流量限速。流量整形,策略應用(丟棄,NAT等)。從這個框架你能想到別的什么嗎?或許如今不能,可是我會先簡單說一下。和TC框架比較相似的是Netfilter框架,可是二者卻又有非常大的不同。
       在精通了Netfilter框架之后。再來體會TC框架會簡單得多,特別是,當你認為Netfilter具有這樣那樣的局限時,帶着這些問題去體會TC框架的設計,你可能會發現,TC在某些方面彌補了Netfilter的不足。在具體深入到細節前,我先來介紹一下二者的同樣點以及因其初衷不同而導致設計的大相徑庭。
       先說Netfilter。無疑這個框架被設計用來在網絡協議棧的內核路徑上過濾數據包,就像在一條路上的關卡一樣,Netfilter在協議棧處理網絡數據包的路徑上的5個位置設置了這樣的關卡,一個數據包在被處理的路徑上經過這些關卡被檢查。結果就是若干個動作:接受。丟棄,排隊,導入其他路徑等,框架僅僅需針對一個數據包得出一個結果就可以,關卡內部提供什么服務在Netfilter框架中並沒有不論什么規定。
       如今我們看TC。它旨在對數據包或者數據流提供一種服務,比方限速,整形等,而這並非一個相似Netfilter的結果能夠表達的,提供這些服務須要運行一系列的動作。因此怎樣來“規划和組織這些動作的運行”是TC框架設計的關鍵!

也就是說,TC框架關注的是怎樣運行而不是僅僅想要得到一個要運行的動作。換句話說,Netfilter框架關鍵做什么,而TC框架關注怎么做。(關於Netfilter我已經寫了大量的代碼和文章,不再贅述了...)
       有關限速。流量整形方面的理論已經非常多了,比較常見的比方使用令牌桶,可是本文關注的是Linux對TC框架的實現而不是令牌桶算法相關的內容,然而在一篇短文中又不可能具體描寫敘述從流量控制理論到各種操作系統版本號實現的歷史,可是我們知道。使用隊列是大多數實現中實際的選擇,那么如今問題來了,Linux的TC框架是怎樣組織隊列的。在具體深入討論隊列組織之前。我最后一次比較一下Netfilter和TC。
       假設你知道UNIX的字符設備和塊設備之間的差別。那么理解Netfilter框架和TC框架之間的差別就比較easy了。Netfilter的一個HOOK點相似一個管道字符設備,而skb就是這個設備中的單向字符流,一般都是依照從一端流入。然后依照進入的順序從還有一端流出,附帶一個結果,比方ACCEPT。DROP等。而TC框架比較相似一個塊設備。對內容進行隨機存儲和隨機訪問,即skb進入的順序並不一定是skb出來的順序。而這正是流量整形須要做的。也就是說。TC框架必須實現一個隨機訪問的數據包存儲緩沖區。在這個緩沖區中進行流量控制。當然,我們已經知道,這是由隊列實現的。
       當然,不論什么事情都不是絕對的,Netfilter的一個HOOK點也能夠有存儲緩沖區或者運行一系列的動作,典型的就是conntrack中的分片重組以及NAT功能,對於PREROUTING這個HOOK點的分片重組。無疑對於分片而言,僅僅是進入HOOK,臨時保存在里面。直到全部分片都來了切重組成功后才一次性流出這個HOOK點,而對於NAT而言,Netfilter的處理結果無疑是“運行了一系列的動作”而不僅僅是ACCEPT。此外,我也寫過一些模塊,用Netfilter來實現流量控制,反過來,TC框架也能夠實現Netfilter的功能,總之,當你理解了這些框架的設計原則以及其本質后。在使用和擴展上。你就能夠庖丁解牛。游刃有余了。
       個人認為,對於單獨的一個Netfilter HOOK點,TC框架是其超集。實現上更加靈活,當然也就更加復雜。

Netfilter所擁有的TC不具備的魅力在於其HOOK點位置的定義。
       好了。如今開始正式介紹TC框架的設計。
       非常多網上搜到的資料在介紹TC的時候。無一例外地介紹了TC是由“隊列規程,類別。過濾器”三者組成的。大多數含糊不清,我敢說這些都是出自一篇文檔或者一本書。

非常少有人從另外一個角度去理解TC框架的設計,而這本身就是一個比較有挑戰性的事,我個人比較喜歡這樣的事情。在介紹TC的隊列組織之前。我先來介紹一下什么叫作遞歸控制。所謂的遞歸控制就是分層次地控制,而對於每個層次,控制方式都是一致的。熟悉CFS調度的都知道,對於組調度和task調度都採用了全然同樣的調度方式。然而顯然組和task是屬於不同層次的,我畫了以下一張圖來簡單描寫敘述這樣的情況:




不光是控制邏輯的組織,就連Linux在實現UNIX進程模型時,也採用了這樣的樹形的遞歸控制邏輯,每個層次都是一個兩層的樹,下圖展示了這個模型:




能夠看出,遞歸控制是分形的。假設能用立體的圖展示會更好些,對於上圖而言。除了葉子節點之外的每個節點都是一顆獨立的小樹,無論是大樹還是小樹。對於控制邏輯或者組織邏輯而言,其性質是全然一樣的。
       遞歸的控制便於控制邏輯的隨意疊加,這個我們在協議棧的設計中看到過,比方X over Y,簡稱XoY,比方PPPoE,IP over UDP(tun模式的OpenVPN),TCP over IP(原生的TCP/IP棧)...對於TC而言,考慮以下一個需求:
1.將整個帶寬依照2:3的比例分給TCP和UDP;
2.在TCP流量中,依照源IP地址段將其划分為不同的優先級。
3.在同樣的優先級隊列中,依照2:8的比例將帶寬分給HTTP應用和其他;
4....

從以上需求能夠看出,這是一個遞歸控制的需求。當中1和3均使用了帶寬比例分配。可是顯而易見,這是屬於不同層次的。整個架構看起來應該是以下這個樣子:




可是事情遠非想象的那個單純,盡管上面的圖已經讓你看出了TC框架的端倪。然而對於實現它卻沒有一點幫助。

幾個典型的問題擺在那里,你怎么甄別數據包到不同的隊列,圖中的非葉子節點要呈現成什么數據結構,既然不是真正的隊列卻又要有隊列的行為,那么怎樣表達它們?...
       Linux在實現TC的時候,對“隊列”進行了抽象。基本上它維護了兩個回調函數指針,一個是enqueue入隊操作,一個是dequeue出隊操作。無論是enqueue還是dequeue,都並不一定真正將數據包排入隊列,而僅僅是“運行一系列的操作”。

這個“運行一系列的操作”能夠是:
1.對於葉子節點。真正排入一個真實的隊列或者從真正的隊列拉出一個數據包;
2.遞歸調用其他抽象隊列的enqueue/dequeue。


注意上面的第2點。提到了“其他抽象隊列”,那么怎樣來定位這個抽象隊列呢?這就須要一個抉擇。也就是一個選擇器,依據數據包的特征來將數據包歸入一個抽象隊列,這個時候,TC的設計框圖能夠用下圖來表達:




能夠看到,我並沒實用那個經典的“隊列規程,類別,過濾器”三元組來定義TC框架。而是用一種遞歸控制的意義來解釋。假設用經典三元組來套在這幅圖上,就會是以下這個樣子。注意,我刪去了不必要的文字。這樣圖不至於太過混亂,須要文字的請參考上圖:




可見,萬變不離其衷或者說英雄所見略同。
       好了,如今說點題外話,還是和Netfilter有關的。當然不是它和TC的比較。而是我個人的一點想法。

曾幾何時,我十分推崇Cisco的ACL,應為它們是應用於網卡接口的。而Netfilter則是攔截在處理路徑上而不是處理設備上。對於Netfilter而言,處理設備僅僅是一個毫無特殊之處的match,無論有無關系。全部的數據包均要經過Netfilter HOOK點的抉擇。起碼你要推斷它是否匹配-i ethX...我想在net_device上掛一個filter_list,也寫過一些代碼。發現效果比較好,准備採用。我是一個常常反復造輪子的人。當我后來看了TC的實現后,發現TC框架正是我想要找的。於是我放言。能用Netfilter實現的。用TC也一樣能實現。而且。TC基於隊列規程(數據結構字段正是這么寫,Qdisc-queue discipline,這並非受經典三元組表達法的影響)的,抽象的入隊/出隊並沒有規定怎樣實現,且隊列規程和網卡綁定(更精確地說是網卡的隊列-假設網卡支持多隊列的話)而不是攔截在處理路徑上。於是我有兩種選擇:
1.實現一個新的Qdisc,其內置一個簡單的FIFO隊列,enqueue操作進行從Netfilter移植過來的matches/target,全部ACCEPT的數據包排入FIFO;
2.在分類器上做文章,是否將數據包歸於一個類別不光要看數據包的特征,還要額外運行一個action回調函數,僅僅有該函數返回0才代表成功。而既然作為回調。你便能夠在當中進行不論什么action(drop,nat等)。關起門來lualu。


以上1和2中,第2點已經實現了,第一點非常easy實現,你僅僅須要實現一個隊列規程就可以,或者說為每個隊列規程都加一個action,看上去例如以下圖所看到的:




對於第2點。比較簡單,其本質就是在那個菱形中做文章,放大后的菱形例如以下圖所看到的:




這樣就用TC框架實現了防火牆的功能以及NAT的功能,這是我一直以來的願望。事實上我早就知道這件事,僅僅是我不太喜歡TC的命令,由於它配置起來太技術化了,維護起來極其困難,甚至比iptables規則維護起來都困難,而維護是超級重要的,它甚至比你想到怎樣書寫這個規則更重要,由於怎樣書寫是一瞬間的事,假設你有足夠的積累,那么一瞬間你就能搞定,假設你碰到了難題,敢說靈感的顯現也是一瞬間的,比方酒后,可是維護卻是長久的事,且維護的人不一定是你自己,你必須要為別人考慮。由於技術社會是利他的社會。
       好了。到此為止,相信我已經把該說的都說了。都是框架性的,沒有不論什么細節在里面,盡管不太喜歡TC命令行。可是我還是希望最后用一幅圖展示一下每一條TC命令和內核數據結構的關系,依舊是沒有細節。命令也不全,省略了match,由於我知道那些不重要:




看我的文章。你可能非常難得到那種復制了之后直接粘貼上就能用的東西。代碼省略了,命令省略了,就算是我自己,在看到自己多年前寫的東西時,十分想高速運行點什么,可是沒有這樣的東西。

可是我認為,思想大於實現。假設你理解了實現背后或者現實背后的本質,那么你就會得心應手,游刃有余。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM