skaiuijing
前言
笔者最近在做网络协议栈方面的工作,在编写eBPF采集Linux网络协议栈信息的模块时查阅了一些书籍,忽然发现书上说可以使用eBPF程序对内核进行一定程度的魔改,比如流量控制,但是具体怎么实现却没有细说。
流量限制
参考了一些资料,总体思路是:
- 在 eBPF 程序里维护一个 per-PID 的令牌桶(BPF map)。
- 每到一个数据包,通过
bpf_get_current_pid_tgid()
拿到调用进程的 PID,更新桶内令牌。
- 当桶内可用令牌少于本包长度时,直接丢包(
TC_ACT_SHOT
),否则扣令牌后放行。
- 用
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); __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) #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; 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 一样,但也提供每个连接的带宽,甚至终端图