先講tc,再講ebpf
tc 的底層原理
- 隊列管理器(qdisc)
隊列管理器是 tc 的核心組件,用於管理數據包的隊列。不同的 qdisc 實現了不同的流量控制策略,如 FIFO、TBF(Token Bucket Filter)、HTB(Hierarchical Token Bucket)等。
FIFO(First In, First Out):最簡單的隊列管理器,按照數據包到達的順序進行處理。
TBF(Token Bucket Filter):用於流量整形和速率限制。
HTB(Hierarchical Token Bucket):用於帶寬分配和優先級控制。 - 分類器(Classifier)
分類器用於根據特定的規則對數據包進行分類。常見的分類器包括 u32、fw、route 等。分類器可以根據 IP 地址、端口號、協議類型等對數據包進行匹配,並將匹配的數據包發送到相應的隊列。
u32:基於 u32 位掩碼的分類器,支持複雜的匹配規則。
fw:基於 iptables 標記的分類器。
route:基於路由表的分類器。 - 動作(Action)
動作用於定義在數據包匹配特定規則後的處理方式。常見的動作包括 mirred(鏡像或重定向數據包)、police(流量控制)、skbedit(編輯數據包字段)等。
mirred:用於鏡像或重定向數據包。
police:用於流量控制和速率限制。
skbedit:用於編輯數據包字段,如修改優先級。
帶寬管理
帶寬限制
可以使用 tc 來限制特定流量的帶寬。例如,你可以限制某個 IP 地址或某個端口的帶寬,以防止其佔用過多的網絡資源。
# 限制 eth0 接口上的出站流量帶寬為 1Mbps
tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 400ms
- burst 指定了令牌桶的大小
- latency 400ms 表示允許的最大延遲為 400 毫秒。如果數據包在隊列中等待的時間超過這個延遲,它們可能會被丟棄。
-
root 表示隊列管理器將被添加到根隊列(root qdisc),即整個接口的出站流量將受到這個隊列管理器的控制。
可以為不同的流量類型分配不同的帶寬。例如,為視頻流量分配更高的帶寬,為文件下載分配較低的帶寬。# 添加 HTB 根隊列管理器 tc qdisc add dev eth0 root handle 1: htb default 10 # 添加類 1:1,帶寬為 10 Mbps tc class add dev eth0 parent 1: classid 1:1 htb rate 10mbit # 添加子類 1:10,帶寬為 5 Mbps tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit # 添加子類 1:20,帶寬為 5 Mbps tc class add dev eth0 parent 1:1 classid 1:20 htb rate 5mbit可以使用 tc 為不同類型的流量設置優先級,以確保高優先級流量(如語音和視頻)在網絡擁塞時得到優先處理。
# 添加過濾器,將 HTTP 流量分配到類 1:10 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:10 # 添加過濾器,將 HTTPS 流量分配到類 1:20 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 443 0xffff flowid 1:20
流量整形
# 使用 Token Bucket Filter (TBF) 進行流量整形
tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 400ms
延遲管理
可以使用 tc 來引入人工延遲,以模擬網絡延遲和抖動。這對於測試網絡應用程序的性能非常有用。
# 使用 netem 引入 100ms 的延遲
tc qdisc add dev eth0 root netem delay 100ms
流量標記
可以使用 tc 為特定流量打上標記,以便在後續的流量控制策略中進行識別和處理。
# 使用 tc 為流量打上標記
tc qdisc add dev eth0 root handle 1: prio
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff action skbedit priority 1:1
流量鏡像
可以使用 tc 將特定流量鏡像到另一個接口或主機,用於流量監控和分析。
# 使用 tc 將流量重定向到另一個接口
tc qdisc add dev eth0 ingress
tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev eth1
流量重定向
可以使用 tc 將特定流量重定向到另一個接口或主機,用於流量負載均衡和故障轉移。
# 使用 tc 將流量重定向到另一個接口
tc qdisc add dev eth0 ingress
tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev eth1
還有很多場景,展示忽略,不是本文的重點
TC 層和 XDP 層的區別
- TC層:TC 層位於內核網絡棧的上層,主要用於隊列管理、流量整形和分類。它可以在數據包進入網絡棧後的多個階段進行處理。由於 TC 層位於網絡棧的上層,數據包需要經過內核網絡棧的處理,因此性能相對較低。TC 層適用於需要複雜流量管理和分類的場景。
- XDP層:XDP 層位於網絡驅動程序層,數據包在進入內核網絡棧之前就被處理。這是網絡數據包處理的最早階段。XDP 層在驅動層直接處理數據包,避免了內核網絡棧的開銷,因此具有極高的性能和低延遲
struct __sk_buff 是 Linux 內核中用於表示網絡數據包的一個關鍵結構體,尤其在網絡堆棧的處理和網絡編程的上下文中。這個結構體包含了大量與數據包相關的信息,使得內核中的網絡代碼可以高效地處理數據包的傳輸、接收、過濾和修改。
__u32 len;:數據包的總長度(不包括任何可能的鏈路層頭部)。
__u32 pkt_type;:數據包類型,如多播、廣播或單播。
__u32 mark;:用於用户空間設置的特殊標記,可以用來控制數據包的路由和處理。
__u32 queue_mapping;:這個數據包應該被映射到的隊列號。
__u32 protocol;:數據鏈路層協議類型,如以太網(Ethernet)。
__u32 vlan_present;:是否存在 VLAN(虛擬局域網)標記。
__u32 vlan_tci;:VLAN 標籤控制信息,包含 VLAN ID 和優先級。
__u32 vlan_proto;:VLAN 協議的以太網類型(通常是 0x8100)。
__u32 priority;:數據包的優先級。
__u32 ingress_ifindex;:接收數據包的網絡接口索引。
__u32 ifindex;:發送數據包的網絡接口索引。
__u32 tc_index;:流量控制(TC)的索引,用於指定數據包的流量控制類別。
__u32 cb[5];:控制塊,用於內核中的各種控制和元數據。
__u32 hash;:數據包的哈希值,可能用於快速查找或決策。
__u32 tc_classid;:流量控制的類ID。
__u32 data; 和 __u32 data_end;:分別指向數據包數據和數據末尾的指針。
__u32 napi_id;:NAPI(非阻塞和輪詢I/O)接口的ID。
在 BPF(Berkeley Packet Filter)程序特別關心的部分:
__u32 family;:地址族,如 IPv4 或 IPv6。
__u32 remote_ip4; 和 __u32 local_ip4;:遠程和本地 IPv4 地址(網絡字節序)。
__u32 remote_ip6[4]; 和 __u32 local_ip6[4];:遠程和本地 IPv6 地址(網絡字節序)。
__u32 remote_port; 和 __u32 local_port;:遠程和本地端口號(網絡字節序和主機字節序)。
還有一些與性能測量和 BPF 特定功能相關的字段:
__u32 data_meta;:額外的數據包元數據。
__bpf_md_ptr(struct bpf_flow_keys *, flow_keys);:指向 BPF 流的鍵信息的指針。
__u64 tstamp; 和 __u64 hwtstamp;:分別代表軟件時間戳和硬件時間戳。
__u32 wire_len;:線路上數據包的總長度(包括任何鏈路層頭部)。
__u32 gso_segs; 和 __u32 gso_size;:與 GSO(Generic Segmentation Offload)相關的字段,用於描述如何將大數據包分割成多個小數據包。
__bpf_md_ptr(struct bpf_sock *, sk);:指向與該數據包關聯的 socket 的指針。
交通控制(Traffic Control, TC)模塊中使用的。它們代表了不同的交通控制動作(Actions)的返回值或類型。交通控制是Linux內核中一個強大的框架,用於管理和控制網絡流量的各個方面,包括隊列管理、分類、過濾和調度。
- TC_ACT_SHOT:丟棄。這個值表示數據包應該被丟棄。在某些情況下,如果數據包不符合某些條件或策略,它可能會被丟棄以防止網絡擁塞或出於安全考慮。
- TC_ACT_OK:操作成功。當交通控制動作成功處理數據包,並且沒有進一步的特殊操作需要執行時,通常會返回這個值。
- TC_ACT_UNSPEC:未指定的動作。這通常用作默認值或錯誤條件,表示動作的類型未明確指定。在這裏,它被定義為-1,這是一個常見的做法,用於表示錯誤或無效的值。
- TC_ACT_QUEUED:已排隊。這個值表示數據包已經被成功排隊,等待後續的處理或傳輸。TC_ACT_REPEAT:重複。這個值可能表示數據包應該被重新提交給交通控制框架進行進一步的處理或評估。
- TC_ACT_RECLASSIFY:重新分類。這個值表示數據包應該被重新分類到另一個類別中。在交通控制中,數據包可以根據其特性(如源地址、目標地址、端口號等)被分類到不同的隊列中,以便進行不同的處理。
案例01:基於源 IP 地址的流量分類和帶寬限制
# 添加 HTB 根隊列管理器
tc qdisc add dev eth0 root handle 1: htb default 30
# 添加類 1:1,帶寬為 10 Mbps
tc class add dev eth0 parent 1: classid 1:1 htb rate 10mbit
# 添加子類 1:10,帶寬為 5 Mbps
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit
# 添加子類 1:20,帶寬為 2 Mbps
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 2mbit
編寫一個 eBPF 程序,基於源 IP 地址對流量進行分類,並將其編譯為 BPF 對象文件。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/pkt_cls.h>
#include <linux/ip.h>
SEC("classifier")
int tc_classifier(struct __sk_buff *skb) {
struct iphdr *ip = bpf_hdr_pointer(skb, sizeof(struct ethhdr), sizeof(struct iphdr));
if (!ip)
return TC_ACT_OK;
// 基於源 IP 地址進行流量分類
if (ip->saddr == bpf_htonl(0xc0a80001)) { // 192.168.0.1
return bpf_redirect(skb->ifindex, 1);
} else if (ip->saddr == bpf_htonl(0xc0a80002)) { // 192.168.0.2
return bpf_redirect(skb->ifindex, 2);
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
# 加載 eBPF 程序
tc filter add dev eth0 protocol ip parent 1:0 bpf obj tc_classifier.o sec classifier
# 添加過濾器,將源 IP 為 192.168.0.1 的流量分配到類 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.0.1/32 flowid 1:10
# 添加過濾器,將源 IP 為 192.168.0.2 的流量分配到類 1:20
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.0.2/32 flowid 1:20