Linux TC Traffic Control 框架原理解析
阿新 • • 發佈:2018-11-16
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
近日的工作多多少少和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框架之間的區別就比較容易了。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是屬於不同層次的,我畫了下面一張圖來簡單描述這種情況:
遞迴的控制便於控制邏輯的任意疊加,這個我們在協議棧的設計中看到過,比如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均使用了頻寬比例分配,但是顯而易見,這是屬於不同層次的。整個架構看起來應該是下面這個樣子:
Linux在實現TC的時候,對“佇列”進行了抽象,基本上它維護了兩個回撥函式指標,一個是enqueue入隊操作,一個是dequeue出隊操作。不管是enqueue還是dequeue,都並不一定真正將資料包排入佇列,而僅僅是“執行一系列的操作”。這個“執行一系列的操作”可以是:
1.對於葉子節點,真正排入一個真實的佇列或者從真正的佇列拉出一個數據包;
2.遞迴呼叫其它抽象佇列的enqueue/dequeue。
注意上面的第2點,提到了“其它抽象佇列”,那麼如何來定位這個抽象佇列呢?這就需要一個抉擇,也就是一個選擇器,根據資料包的特徵來將資料包歸入一個抽象佇列,這個時候,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點已經實現了,第一點很容易實現,你只需要實現一個佇列規程即可,或者說為每一個佇列規程都加一個action,看上去如下圖所示:
好了,到此為止,相信我已經把該說的都說了,都是框架性的,沒有任何細節在裡面,雖然不太喜歡TC命令列,但是我還是希望最後用一幅圖展示一下每一條TC命令和核心資料結構的關係,依然是沒有細節,命令也不全,省略了match,因為我知道那些不重要: