先讲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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。