TC 可以在 ingress 或 egress 的時候對封包的接收/發送進行排程。雖然根據 Jiri Benc 的介紹中有提到 TC ingress,但若查詢 tc(8)
文件會發現:TC 中能用於控制 egress 的功能比 ingress 多得很多。而 Arch Linux 文件中的 Advanced traffic control 也提到:
Qdiscs on ingress traffic provide only policing with no shaping.
而在許多資料中,都會以 egress 作為介紹 TC 的例子。所以這邊會以 egress 為主。不過 這裡 有一個 TC ingress 的簡介 (雖然是西班牙文) (裡面 Disciplinas de cola de entrada 的部分)。不過下面的圖片大部分會引用這份文件。
除此之外,Traffic Control HOWTO 跟 QoS in Linux with TC and Filters 也是不錯的說明。而 tc-htb
的官方網站 中有一篇 HTB Linux queuing discipline manual - user guide,內容如其標題是 HTB 這個 qdisc 的使用說明。這個使用說明中也有順便提到不少 tc
的細節。
最後,TC 當中的 filter 部分,雖然一開始的功能只是「將封包依照某些特徵分流給不同的 class」處理,但實際上這個分類機制已經演進成一個稱為 classfier-action 的子系統,可以像 netfilters 那樣,對滿足特定條件的封包執行某些操作。這些操作在 TC 的術語中稱為 action,而「將封包分配給不同的 class」則只是這 action 中的一個例子。而關於 filter 機制,在 Jamal Hadi Salim 寫的 Linux Traffic Control Classifier-Action Subsystem Architecture 中有非常詳細的介紹。而這篇文章是他本人在 Netdev 0.1 中的 Linux Traffic Control Classifier-Action Subsystem Architecture 演講的總結 (下面有這個演講的影片)。
Use Traffic Control to Simulate Network Chaos in Bare metal & Kubernetes 是一篇用 Docker 來實驗 tc
功能的例子。除此之外,xdp-project/bpf-examples 中的 tc-basic-classifier 也是一個例子:
一個 Qdisc 可以是一個 classless qdisc,像下面這樣:
或者可以是一個 classful Qdisc,意思是這個 Qdisc 裡面會包含其他 class,而這些 class 的內部又包含一個 Qdisc。所以可能會像這樣:
可以想像封包進入一個 classful Qdsic 之後,應該要有某些機制決定這個封包該分給哪個 class。而這件事則是 filter 來決定的。一個這樣的 class 內部的 qdisc 又可以是…沒錯!一個 classless Qdisc 或一個 classful Qdisc ,因此這個問題就回到一開始。然後就這樣遞迴地定義下去。根據 tc(8)
中的敘述:
Some qdiscs can contain classes, which contain further qdiscs - traffic may then be enqueued in any of the inner qdiscs, which are within the classes.
一個 class 中除了包含 Qdisc 之外,裡面也可以繼續包含更多更細的 class,這些 class 又可能繼續包含其他 class … 最後「最內層」的 class 再包含 Qdisc。所以就形成了類似以下的俄羅斯娃娃遞迴結構 (圖片來源:QoS in Linux with TC and Filters):
或是另外一個稍微複雜的例子 (圖片來源):
而當封包進入「封包進入 Qdisc -> 分成多個 class -> 每個 class 底下又再細分多個 class … 分到最底下的 class 之後放進它裡面的 qdisc 中」。即使在建立這個 class 時,沒有指定要用哪種 Qdisc,那些位於最內層 class 也會自動有個預設的 Qdisc。以 HTB 為例,在 HTB Linux queuing discipline manual - user guide 中有提到:
Now we can optionally attach queuing disciplines to the leaf classes. If none is specified the default is pfifo.
而如果這個最內層的 class 當中新增了另外一個 class,那麼這個預設的 Qdisc 就會被移除。所以只有那些「裡面沒有更多 class」的 class 底下可以有 Qdisc。這點在 Traffic Control HOWTO 有提到:
Any newly created class contains a FIFO. This qdisc can be replaced explicitly with any other qdisc. The FIFO qdisc will be removed implicitly if a child class is attached to this class.
這種一層包一層的 class 關係畫起圖來不太方便,所以有時候也會畫成樹的樣子。樹的節點間的親子關係表示 class 或 qdisc 的包含關係。比如說 Jiri Benc 的 Doing Network Magic with the tc Tool 演講中就是使用這種畫法:
每個 qdisc 跟 class 都會有一個用以識別的編號,稱為 handle,或直接稱呼它叫 ID。而這個編號的形式會是 x:y
,其中 x
稱為 major number,而 y
稱為 minor number。這一樣可以在 tc(8)
的文件中找到:
All qdiscs, classes and filters have IDs, which can either be specified or be automatically assigned. IDs consist of a major number and a minor number, separated by a colon - major:minor. Both major and minor are hexadecimal numbers and are limited to 16 bits. There are two special values: root is signified by major and minor of all ones, and unspecified is all zeros.
或是 HTB 的文件中:
handles are written x:y where x is an integer identifying a qdisc and y is an integer identifying a class belonging to that qdisc.
因為不同的 interface 可以各自有各自的 Qdisc,所以這個編號只在同一個 interface 中唯一:
handles are local to an interface, e.g., eth0 and eth1 could each have classes with handle 1:1.
除此之外,這個編號並不是任意給定的,而是會依照 Qdisc 與 class 之間的包含關係來決定數值。
對於一個 Qdisc 來說,他的 handle 中的 minor 部分一定會是 0。也就是形式會是 x:0
。因為一定會是 0,所以有有時候也會看到直接簡寫為如x:
的形式:
The handle for a qdisc must have zero for its y value and the handle for a class must have a non-zero value for its y value. The "1:" above is treated as "1:0".
而在 HTB 的文件中,也可以找到如下的敘述:
handles are written x:y where x is an integer identifying a qdisc and y is an integer identifying a class belonging to that qdisc. The handle for a qdisc must have zero for its y value and the handle for a class must have a non-zero value for its y value. The "1:" above is treated as "1:0"
一個 Qdisc 底下的 class 的 handle,規定必須跟其所屬的 Qdisc 具有同一個 major number。也就是說:若一個 Qdisc 的 handle 是 a:0
,則其底下的 class 的 handle 都會具有 a:b
的形式,其中 b
是一個正數。這個在 tc(8)
的文件中也可以找到:
QDISCS A qdisc, which potentially can have children, gets assigned a major number, called a 'handle', leaving the minor number namespace available for classes…
另外,class 的 handle 名稱形式固定是 x:y
,其中 x
是其所屬的 Qdisc 的 handle,而 y
則是一個不重複的編號:
handles are written x:y where x is an integer identifying a qdisc and y is an integer identifying a class belonging to that qdisc. The handle for a qdisc must have zero for its y value and the handle for a class must have a non-zero value for its y value. The "1:" above is treated as "1:0".
tc qdisc show
–- 顯示目前的 Qdisc若想知道一個 namespace 中的所有 interface 上面所連接的 qdisc 的相關資訊,可以使用 tc qdisc
中的 show
命令。比如說,如果想知道 $L_NS
中的 interface 所連接的 qdisc,可以用以下的命令。
會輸出類似以下的結果:
在這個例子中,left-veth
這個 interface 連接著一個 noqueue
qdisc。而
接下來會在 $L_NS
中的
以上面的環境來說,可以使用:
在 "$L_NS"
中的 "L_DEV"
的 root 上加上一個 handle
為 1:0
的 HTB。這時候如果再執行一次:
會發現 left-veth
這個 interface 的 root qdisc 從原先的 noqueue
變成了 htb
:
另外,如果想知道某個 namespace 中的某個 interface 上連接的 qdisc 的相關資訊,則可以用以下的方法:
這時候就會出現:
Qdisc 的 handle 是上面 handle
選項中的 1:
。這個 1:
其實是 1:0
的意思:
雖然裡面的圖是這樣:
但實際上是下面這樣:
只是那個 Class 1:1 很畫起圖來有點亂,所以就省略掉。
tc class show
這時候會什麼都沒有。
為了方便,定義:
一個 class 可以以一個 classful qdisc 直接相連作為父親。比如說:
像這樣直接與 qdisc 相連的 class 稱作一個 root class:
The first line creates a "root" class, 1:1 under the qdisc 1:. The definition of a root class is one with the htb qdisc as its parent.
如果這時候使用
就會出現:
除此之外,也可以以一個 class 作為父親,形成一系列 class 的階層:
以這邊為例,在 handle 為 1:1
的 class 底下又新增了 3 個 class,他們的 handle 分別是 1:10
、1:20
與 1:30
。
可以加上 -g
選項。這樣一來就會畫出樹狀階層:
而這兩者的差別是:root class 之間沒辦法做 borrowwing:
A root class, like other classes under an htb qdisc allows its children to borrow from each other, but one root class cannot borrow from another.
最後,如同前面的描述,在一個 Qdisc 當中的 class 形成的諸多 tree 當中,所有的 leaf class 最終需要有一個 qdisc 來把分類之後的封包排進去傳送。而如果沒有指定,這個預設的 qdisc 會是 pfifo:
Now we can optionally attach queuing disciplines to the leaf classes. If none is specified the default is pfifo.
這時候如果使用:
就會出現:
有了 Qdisc 的構造之後,可以藉由 filter 選擇該網封包送往哪個 qdisc。以下面的圖為例,有 3 個 filter 可以決定進入這個 qdisc 的封包要送往哪個 class:
tc filter show
如果在 $R_NS
啟動三個 iperf3
的伺服器:
iperf3
連線並且在 $L_NS
使用 3 個 client 跟剛剛建立的 iperf
伺服器建立連線:
就會出現類似以下的結果: