eBPF这项新技术会成为又一个内核玩家们的新圣地。
而我对eBPF了解的越多,就愈发追捧这门技术,乃至有些痴迷,“eBPF造万物”的字眼快要在我嘴里破口而出,背后的原因,是内核给了eBPF足够的空间,去接管系统的一切。
但一个TCP玩家,学习eBPF的目的自然还是服务本源。TCP是我现在的工作,所以我只好把目光从全局回缩一下,先试着捣鼓eBPF对TCP的玩法。
BPF是我的新武器,所以我总想着在这玩出点什么名堂。
于是乎,我放弃了BCC这类框架,直接上手libbpf。
如果感兴趣,请戳下面链接:
有关BCC、libbpf和BPF CO-RE
刚接触eBPF,会偏执的认为这只是一个白盒测试,最有价值的是在于可以追踪应用程序/内核程序的一切信息,动态追踪是它的助燃器。尽管如此,我已经觉得eBPF很厉害了。
但玩了一段时间后,渐渐从BCC这些应用层框架的阵地偏向了kernel(kernel里实现了libbpf、bpftool等)。于是乎,我在kernel里熟悉着libbpf,bpftool、cgroup的同时,也掀开了BPF的幕帘。
eBPF 不仅能监控系统上的数据,甚至可以拿来改变系统的行为。
并且,它绝对安全!
对eBPF有了解的,大多都知道XDP可以在网络收发包的流程里,操作数据包内容,这也是XDP兴起的主要原因。但当内核协议栈拥有了实时处理数据包内容的能力时,相比XDP,就意味着不需要知道偏移量等背景信息,不需要考虑网络包分片的影响,在每个封包/解包的过程中去处理工作。在指定过程中自定义操作,如:二层对Ethernet头处理,三层对IP头处理,四层对TCP、UDP头处理等等。
所以,当你有需要对数据包内容进行自定义操作时,除了XDP,你也可以选用内核协议栈的BPF,(内核协议栈的ebpf技术实现依赖于libbpf)。
最后,给出一个样例:在数据包TCP头部封装时,往TCP Options里注入IP:Port二元组。
// SPDX-License-Identifier: GPL-2.0
#include
#include
#include
#include
#include
#include
// * 程序版本信息
int _version SEC("version") = 1;
// reserved option number
#define TCPOPT_TOA 77
struct tcp_option {
__u8 kind;
__u8 len;
__u16 port;
__u32 addr;
} __attribute__((packed));
SEC("sockops")
int bpf_tcpoptionstoa(struct bpf_sock_ops *skops)
{
//return value for bpf program
int rv = -1;
//struct tcp_option option_buffer;
int op = (int) skops->op;
//update_event_map(op);
switch (op) {
//* client side
case BPF_SOCK_OPS_TCP_CONNECT_CB:
break;
//* client side
case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: {
char fmt2[] = "client: active established\n";
bpf_trace_printk(fmt2, sizeof(fmt2));
/* Client will send option */
//* BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG enables writing tcp options
//* bpf_sock_ops_cb_flags_set用来调用修改flag的bpf程序——BPF_SOCK_OPS_HDR_OPT_LEN_CB/BPF_SOCK_OPS_WRITE_HDR_OPT_CB
//* send new option from server side
bpf_sock_ops_cb_flags_set(skops, BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);
break;
}
// * server side
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:{
char fmt3[] = "server: passive established\n";
bpf_trace_printk(fmt3, sizeof(fmt3));
/* Server will send option */
//* BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG enables writing tcp options
//* bpf_sock_ops_cb_flags_set用来调用修改flag的bpf程序——BPF_SOCK_OPS_HDR_OPT_LEN_CB/BPF_SOCK_OPS_WRITE_HDR_OPT_CB
//* send new option from server side
//bpf_sock_ops_cb_flags_set(skops, BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);
break;
//return 1;
//bpf_printk("rv := %d",rv);
}
case BPF_SOCK_OPS_HDR_OPT_LEN_CB: {
//reserved space
int option_len = sizeof(struct tcp_option);
/* args[1] is the second argument */
if (skops->args[1] + option_len <= 40) {
rv = option_len;
}
else rv = 0;
//* 保留空间已经验证成功
// bpf_printk("option len is %d",rv);
bpf_reserve_hdr_opt(skops, rv, 0);
// bpf_printk("err: %d",err);
break;
}
case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: {
//bpf_printk("",skops->);
struct tcp_option opt = {
.kind = TCPOPT_TOA,
.len = 8, // of this option struct
.port = __bpf_htons(0xeB9F),
.addr = __bpf_htonl(0x93d4860a),
};
/ * Server sends option */
// * write the option
bpf_store_hdr_opt(skops, &opt, sizeof(opt), 0);
// cancel the settings
bpf_sock_ops_cb_flags_set(skops, skops->bpf_sock_ops_cb_flags& ~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);
break;
}
default:
rv = -1;
}
skops->reply = rv;
return 1;
}
// * 必要的许可信息
char _license[] SEC("license") = "GPL";
你也完全可以对IP头部的IP Options做如上注入信息的操作。