其實 Linux 核心已經提供我們許多 queueing disciplines 了。到目前為止﹐最為廣泛應用的是 pfifo_fast 佇列演算法﹐也是預設使用的。這也解釋了為何這些進階功能如此強悍。他們除了只是 '另外的佇列' 以外﹐別無它物。
每一種佇列演算法都各有千秋。不過並非全部都經嚴密測試過的就是了。
望文生意﹐此佇列演算法就是先入先出(First In First Out)﹐接收到的封包﹐均一視同仁。這種佇列演算法有 3 個所謂的 'bands'。在每一個 band 中﹐均奉行 FIFO 的規則。然而﹐z只有在 band0 中的封包處理完之後﹐才輪到 band1 裡面的。在 band1 和 band2 之間﹐也是同樣的情形。
一如前述﹐SFQ 也不是放之四海皆準的﹐但(平均來說)還是可用的就是了。它主要的好處是它所佔用的 CPU 和記憶體都很少﹐而‘真正’的平等(fair) 佇列法需要核心追蹤所有運作中的連線。
在平等佇列演算法家族中﹐Stochastic Fairness Queueing (SFQ) 算是較為簡明的實作。雖然它不如其它演算法那麼精確﹐不過在保持相當平等的前提下﹐它所需要的運算也較少。
在 SFQ 中的關鍵詞是會談(conversation)(或曰流程(flow))﹐在一連串的數據封包中找到足夠的公約數﹐以區分出不同的 conversation。IP 封包裡面都帶有來源和目的位址﹐以及協定號碼﹐此情形下﹐這些參數就派得上用場了。
SFQ 由動態分配的 FIFO queues 組成﹐一個 queue 負責一個 conversation。而 discipline 則以 round-robin 的形式執行﹐每次從一個 FIFO 中送出一個封包﹐這就是為何稱之為平等(fair)的原因了。SFQ 的主要好處在於它讓不同的程式平等的分享連線﹐而避免頻寬被單一的客戶程式所佔據。然而﹐SFQ 卻不能從 bulk flows 中判定出互動(interactive)的部份 --- 這需要先於前面的 CBQ 進行篩選﹐然後才將 bulk 流量導入 SFQ 中。
Token Bucket Filter(TBF)是一種簡單的網路佇列演算法。當網路封包經過這個佇列時﹐就會受到一個預先設定的速率限制(封包數/時間)﹐利用這個﹐可以來減緩網路瞬增流量(buffer short bursts)所造成的網路效能降低。
至於 TBF 的實作﹐則由一個緩衝區(bucket) 構成﹐不斷的被一些虛擬資訊﹐稱為 tokens ﹐按特定的速率(token rate) 填充。而緩衝區的重要參數是其體積﹐也就是它所能存儲的 token 數目。
每一個抵達的 token 抵消一個離開佇列的傳入數據封包﹐然後從緩衝區中清掉。另外﹐有兩個流程(flow) 與此演算法息息相關的﹕ token 和 data﹐這一共會帶出三種狀況﹕
最後一種情形千萬不能掉以輕心﹐因為這會強行將頻寬撥給數據﹐而通過過濾器。至於 token 的積聚﹐則允許超出限制之瞬增流量仍可不被遺失地通過﹐但其後的持續過載﹐均會導致封包被持續的丟棄。
(譯者註﹕這個 TBF 其實不難理解﹐就是 no token no data 原則。只有當 data 獲得相應的 token 才能通過﹐而 token 的載入速率則是固定的。)
Linux 核心看起來似乎超過此一規格﹐而且還允許我們限制瞬增流量的速度。然而﹐Alexey 警告我們說﹕
注意﹕TBF 的最高峰值(peak rate) 相當高﹕當 MTU 為 1500 的時候﹐P_crit = 150Kbytes/sec。
所以﹐如果您需要更大的峰值﹐使用 HZ=1000 的 alpha 機器囉 :-)
FIXME﹕不清楚是否仍有 TSC (pentium+) ﹖嗯﹐看來有那麼點兒
FIXME﹕若不然﹐要為提高的 HZ 另闢章節
RED 可算是身懷絕技。當一個 TCP/IP 連線建立起來的時候﹐連線兩端都不清楚到底頻寬會有多大。所以 TCP/IP 會先由低速開始然後逐漸加快腳步﹐最後受制於 ACKs 回應的延遲。
當一條線路滿載的時候﹐RED 就會開始丟棄封包﹐告知 TCP/IP 這條線路已達擁塞狀態﹐需要減低速度了。聰明之處在於 RED 會模擬真正的擁塞﹐同時在線路完全滿載之前開始丟棄封包。一旦線路完全飽和﹐它就擔當起交通警察的角色。
如需更詳細的資料﹐請參考 Backbone 那章。
如果您不想借助於 router 或其它 Linux 機器﹐而想要限制特定的主機﹐那麼 Ingress qdisc 會是您的隨身暗器。當頻寬超過您所設定的比率的時候﹐您可以管制入向頻寬及丟棄封包。比方說﹐可以保護您的主機抵禦 SYN flood 的攻擊﹐而且也可以用來降低 TCP/IP 速度﹐也就是以丟棄封包的方法來減速。
FIXME﹕除了丟棄之外﹐我們是否可以將之分配給一個真實的 queue 呢﹖
FIXME﹕以丟棄封包來進行管制似乎並非上上之舉﹐倒不如用 token 緩衝區過濾器。不敢莽斷啦﹐Cisco CAR 也都用這個﹐而且人們似乎也受之若然。
請參考本文最後面的 IOS Committed Access Rate 。
簡而言之﹕您可以用之來限制您電腦下載檔案有多快﹐而騰出更多頻寬給其它用途。
請參考 幫貴主機抵禦 SYN floods 那章﹐那裡有一個例子告訴您它是如何做到的。
本章由 Esteve Camps <esteve@hades.udg.es> 撰寫。
首先﹐再首先﹐您最好先到 IETF DiffServ working Group web site 和 Werner Almesberger web site(在 Linux 支援 Differentiated Services 的程式正是由他寫的) ﹐讀一讀 RFC 文件(RFC2474、RFC2475、RFC2597、以及 RFC2598)。
Dsmark 是一種佇列演算戒律(discipline)﹐主要用於 Differentiated Services (也稱為 DiffServ 或簡稱 DS)。DiffServ 是兩種 actual QoS 架構之一 (另外一個叫做 Intergrated Services)﹐主要依靠 IP 封包標頭中的 DS 欄位所攜帶的數值進行判斷。
最早在 IP 設計上所提供的 QoS 層級解決方案﹐其中之一就是 IP 標頭中的 Type of Service 欄位(TOS byte)。改變這些數值﹐我們可以選擇一個 高/低 等級的吞吐量、延遲、或是可靠度。但是這並不能提供足夠的靈活性﹐以滿足較新服務(如 real-time 應用程式、互動程式、和其它)的需求。有鑒於此﹐新的架構出現了。其一就是 DiffSserv﹐它會保留 TOS bits ﹐同時重新命名 DS 欄位。
DiffServ 是以群組為導向的(group-oriented)。我是說﹐我們無須知道流向(flows)是如何運作的(這是 Intergrated Services 的事情)﹔我們只知道流向聚集(flow aggregations)﹐以及根據封包所屬的聚集如何應用不同的行為特性。
當封包抵達一個邊緣節點(即 DiffServ domain 的入口節點)﹐並進入 DiffSer Domain 的時候﹐我們就需要建立一些原則(policy)﹐引導 和/或 標識這些封包(所謂標識﹐就是設定 DS 欄位的數值)。然後 DiffSer Domain 的內部/核心節點就檢查這些標識/數值 ﹐以判定應用什麼樣的行為特性或 QoS 等級。
正如您所推斷的﹐DiffServ 意味著一個應用到所有 DS 規則的 domain。事實上﹐您可以這樣想像"我們會對所有進入 domain 的封包進行分類。一旦它們進入 domain﹐它們就逮屬於分類所指定的規則﹐並且每一個穿越節點都會應用這個 QoS 等級"。
實際而言﹐您可以在本地 domains 裡面應用您自定的原則﹐但是﹐當您連接到其它 DS domains 的時候﹐就需要顧及到某些 服務等級協議(Service Level Agreements) 。
至此﹐您或許滿腹疑團吧。DiffServ 遠比我所解釋的要複雜得多。事實上﹐您不難想像﹐我可沒那能耐將 3 個以上的 RFC 壓縮在短短 50 行裡面哦 :-)
根據 DiffServ 學科所指定﹐我們要區別出邊界(boundary)節點和內部(interior)節點。在流量路徑上有兩個關鍵點﹐兩者在封包到達的時候均會進行分類。在封包真正送出網路之前﹐其結果會在 DS 處理過程中的不同地方使用到。這是因為 DiffServ 程式提供了一個稱為 sk_buff 的機制﹐包括一個新的欄位﹐稱為 skb->tc_index﹐用來儲存初始分類的結果﹐用於 DS 處理中的不同目的。
該 skb->tc_index 數值會被 DSMARK qdisc 用來做初始設定﹐從每一個接收封包的 IP 標頭之 DS 欄位就可以獲得。另外﹐cls_tcindex 分類器會讀取全部或部份的 skb->tcindex 數值﹐用來選擇等級(classes)。
然而﹐首先﹐請參閱一下 DSMARK qdisc 命令以及它的參數﹕
... dsmark indices INDICES [ default_index DEFAULT_INDEX ] [ set_tc_index ]
這些參數究竟代表什麼呢﹖
然後讓我們看看 DSMARK 的運作。
此 qdisc 的步驟如下﹕﹕
New_Ds_field = ( Old_DS_field & mask ) | value
skb->ihp->tos
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >
| | ^
| -- If you declare set_tc_index, we set DS | | <-----May change
| value into skb->tc_index variable | |O DS field
| A| |R
+-|-+ +------+ +---+-+ Internal +-+ +---N|-----|----+
| | | | tc |--->| | |--> . . . -->| | | D| | |
| | |----->|index |--->| | | Qdisc | |---->| v | |
| | | |filter|--->| | | +---------------+ | ---->(mask,value) |
-->| O | +------+ +-|-+--------------^----+ / | (. , .) |
| | | ^ | | | | (. , .) |
| | +----------|---------|----------------|-------|--+ (. , .) |
| | sch_dsmark | | | | |
+-|------------|---------|----------------|-------|------------------+
| | | <- tc_index -> | |
| |(read) | may change | | <--------------Index to the
| | | | | (mask,value)
v | v v | pairs table
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->
skb->tc_index
那如何做標識呢﹖只需修改您想重新標識的分類(class)之 mask 和 value 就可以了。請參考下一行程式﹕
tc class change dev eth0 classid 1:1 dsmark mask 0x3 value 0xb8
這會改變那對存於雜湊表格內的 mask 和 value ﹐並重新將封包標識為屬於 class 1:1 。您必須 "修改" 這個數值﹐因為預設的數值 (mask, value) 會獲得初始值(看後面的表格)。
現在﹐我們將解釋一下 TC_INDEX 過濾器是如何工作的﹐以及如何滿足它的需求。另外﹐TCINDEX 過濾器還可以用在其它設定上﹐並不只限於 DS 服務之中。
宣告一個 TC_INDEX 過濾器的基本命令如下﹕
... tcindex [ hash SIZE ] [ mask MASK ] [ shift SHIFT ]
[ pass_on | fall_through ]
[ classid CLASSID ] [ police POLICE_SPEC ]
這裡﹐我們沿用過往例子解釋 TC_INDEX 的運算模式。請特別留意標為粗體的文字﹕首先﹐假設我們收到一個標識為 EF 的封包。如果您讀過 RFC2598﹐那您應該看到 EF 流量的 DSCP 建議值為 101110。這表示 DS 欄位將會是 10111000 (要知道 TOS byte 中的次重要位元並不會用於 DS 中)﹐或是以十六進位表示為 0xb8 。
TC INDEX
FILTER
+---+ +-------+ +---+-+ +------+ +-+ +-------+
| | | | | | | |FILTER| +-+ +-+ | | | |
| |----->| MASK | -> | | | -> |HANDLE|->| | | | -> | | -> | |
| | . | =0xfc | | | | |0x2E | | +----+ | | | | |
| | . | | | | | +------+ +--------+ | | | |
| | . | | | | | | | | |
-->| | . | SHIFT | | | | | | | |-->
| | . | =2 | | | +----------------------------+ | | |
| | | | | | CBQ 2:0 | | |
| | +-------+ +---+--------------------------------+ | |
| | | |
| +-------------------------------------------------------------+ |
| DSMARK 1:0 |
+-------------------------------------------------------------------------+
封包抵達之後﹐將 DS 欄位設定為 0xb8。正如我們前面所解釋的﹐例中的 dsmark qdisc 被鑒定為 id 1:0 ﹐讀取 DS 欄位﹐並存放於 skb->tc_index 變數之內。例中的下一步﹐相當於該 qdisc 關聯的過濾器(例中第 2 行)。這將進行下一個運算﹕
Value1 = skb->tc_index & MASK
Key = Value1 >> SHIFT
例中﹐MASK=0xFC i SHIFT=2。
Value1 = 10111000 & 11111100 = 10111000
Key = 10111000 >> 2 = 00101110 -> 0x2E in hexadecimal
所返回的數值就是 qdisc 內部過濾器之 handle(本例中為 identifier 2:0)。假如找到這個 ID 的過濾器﹐原則和測量條件就獲得確認 (本例中的過濾器就包括這個)﹐同時會返回 classid(本例中為﹕classid 2:1)﹐並且存放於 skb->tc_index 變數裡面。
不過﹐假如找到任何帶此 ID 的過濾器﹐其結果將取決於 fall_through 旗標的宣告。然則﹐返回的 classid 則以數值鍵值(value key)為準。否則﹐將返回一個錯誤﹐同時處理程序將繼續剩餘的過濾器。小心哦﹐如果您使用 fall_through 旗標的話﹐假如 skb->tc_index 變數之數值和 class id 之間存在一個簡單的關聯﹐即可完成。
最後要註解的參數是 hash 和 pass_on。前者關乎雜湊表格的體積﹔而 pass_on 則指示﹕如果沒有發現與該過濾器結果相等的 classid﹐則嘗試下一個過濾器。預設動作為 fall_through (看下一個表格)
最後﹐讓我們看看有哪些可能的數值﹐是可以用來設定全部 TCINDEX 數值的﹕
TC Name Value Default
-----------------------------------------------------------------
Hash 1...0x10000 Implementation dependent
Mask 0...0xffff 0xffff
Shift 0...15 0
Fall through / Pass_on Flag Fall_through
Classid Major:minor None
Police ..... None
此類過濾器實在非常厲害。它必須探勘所有的可能性。另外﹐這個過濾器不僅可以用在 DiffServ 設定中﹐還可以應用在其它種類的過濾器上面。
我建議您抽空看看 iproute2 套件中的所有 DiffServ 範例。我向各位保證﹐我將會竭盡全力儘快完成文字的部份。另外﹐我所作的解釋全都是許許多多測試的結果。