skaiuijing

前言

笔者最近在做网络协议栈方面的工作,在编写eBPF采集Linux网络协议栈信息的模块时查阅了一些书籍,忽然发现书上说可以使用eBPF程序对内核进行一定程度的魔改,比如流量控制,但是具体怎么实现却没有细说。

流量限制

参考了一些资料,总体思路是:

  1. 在 eBPF 程序里维护一个 per-PID 的令牌桶(BPF map)。
  2. 每到一个数据包,通过 bpf_get_current_pid_tgid() 拿到调用进程的 PID,更新桶内令牌。
  3. 当桶内可用令牌少于本包长度时,直接丢包(TC_ACT_SHOT),否则扣令牌后放行。
  4. tc qdisc clsact 加载该 eBPF 程序到网卡 ingress/egress,即可实现进程级别的限速。

BPF Map 定义(令牌桶状态)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct bucket {
__u64 ts_ns; // 上次更新时间
__u64 tokens; // 当前剩余令牌(字节数)
};

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u64); // PID + tgid
__type(value, struct bucket);
__uint(max_entries, 1024);
} buckets SEC(".maps");
  • key = bpf_get_current_pid_tgid()
  • value 存储该 PID 的上次时间戳和令牌余量。

eBPF 程序:TC Classifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/pkt_cls.h>

#define RATE_BPS (5ull * 1024 * 1024) // 5 MiB/s
#define NSEC_PER_SEC 1000000000ull
#define MAX_BUCKET RATE_BPS // 桶容量上限

static __inline __u64 now_ns(void) {
return bpf_ktime_get_ns();
}

SEC("classifier")
int tc_shape(struct __sk_buff *skb) {
__u64 id = bpf_get_current_pid_tgid();
__u64 now = now_ns();
__u64 delta_ns;
struct bucket *b = bpf_map_lookup_elem(&buckets, &id);

if (!b) {
struct bucket init = { .ts_ns = now, .tokens = MAX_BUCKET };
bpf_map_update_elem(&buckets, &id, &init, BPF_ANY);
b = bpf_map_lookup_elem(&buckets, &id);
if (!b)
return TC_ACT_OK;
}

delta_ns = now - b->ts_ns;
// 按时间积累令牌,饱和度为 MAX_BUCKET
b->tokens += (delta_ns * RATE_BPS) / NSEC_PER_SEC;
if (b->tokens > MAX_BUCKET)
b->tokens = MAX_BUCKET;
b->ts_ns = now;

// 若令牌不足,丢包;否则扣除本包长度后放行
if (b->tokens < skb->len)
return TC_ACT_SHOT;
b->tokens -= skb->len;
return TC_ACT_OK;
}

char __license[] SEC("license") = "GPL";
  • skb->len 即本次要发送/接收的字节数。
  • 丢包动作使用 TC_ACT_SHOT;正常放行返回 TC_ACT_OK

参考

eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制 - eunomia

EBPF-samples/tc 在主 ·pinoOgni/ebpf-样本

网络传输协议 - ebpf–入门05(流量控制) - putao0525 - SegmentFault 思否

cilium/cilium:基于 eBPF 的网络、安全性和可观测性

Ivlyth/process-bandwidth:一个基于 ebpf 的程序,专注于进程的网络带宽,就像 Nethogs 一样,但也提供每个连接的带宽,甚至终端图