今天晚上和小小一起学习,上个晚自习。
已经是reuseport骨灰级玩家了,所以简单写。
最开始,Linux协议栈并没有完全兼容BSD的reuseport语义,也就是说,Linux 3.9之前,所谓的reuseport仅仅有socket热备份功能,没有socket负载均衡功能,但是很快在3.9内核之后,Linux协议栈的reuseport功能便完善了:
- Active-Backup
- Active-Active
最开始的 Active-Active 实现非常Low,自己看代码就知道了,必须遍历所有的socket,计算hash,才能选出工作socket,如果系统的Listener(TCP的,或者UDP的,比如DNS)非常多,那么socket lookup将会成为热点,徒耗CPU。
而后,在Linux 4.6,事情起了变化,改成了取模算法,详见:
https://blog.csdn.net/dog250/article/details/80458669
增加了eBPF的支持,看起来很棒,但是好用吗?不一定。
比如,我想实现一致性哈希,Linux 4.6版本的reuseport调用eBPF很难吧。即便实现一个最简单的一致性哈希,为了简洁,我也首选kpatch这类hotfix方案,而不是去写一个eBPF程序。以下是我的一致性哈希的代码:
https://blog.csdn.net/dog250/article/details/89268404
然而,如何用一种更加优雅的方案去促成这件事呢?
…
等吧。
终于,在eBPF的大潮下,我们迎来了 BPF_PROG_TYPE_SK_REUSEPORT ,这是又一个新增的eBPF的HOOK点,当reuseport逻辑选择socket的时候,该类型的eBPF程序被调用,最终的效果是:
- 给定一个数据包skb,该HOOK点的eBPF程序会为该skb选择一个处理它的socket。
先花一两分钟浏览下面的链接:
https://lwn.net/Articles/762101/
https://github.molgen.mpg.de/donald/linux/commit/8217ca653ec601246832d562207bc24bdf652d2f
它们交代了这个eBPF的HOOK点的来龙去脉。
reuseport这个HOOK点的eBPF程序依托两层嵌套的map:
首先,数据包根据元数据map到一个内层的reuseport map,该内层的map以数据包的内容为key,map到特定的socket。
有点意思,不是吗?
若要写代码,也不难,我这里没时间搞了,小小已经睡着了,我也要睡了,不过还是给出一个未完成的demo:
struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, 1);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} outer_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, NR_RESULTS);
__type(key, __u32);
__type(value, __u32);
} result_map SEC(".maps");
SEC("select_by_skb_data")
int _select_by_skb_data(struct sk_reuseport_md *reuse_md)
{
__u32 index = 0, flags = 0, dummy = 0, key = 0;
void *data, *data_end;
void *reuseport_array;
enum result result;
struct udphdr *uh;
int err;
data = reuse_md->data;
data_end = reuse_md->data_end;
if (reuse_md->eth_protocol != bpf_htons(ETH_P_IP))
return SK_PASS;
if (reuse_md->ip_protocol != IPPROTO_UDP)
return SK_PASS;
uh = data;
if (uh + 1 > data_end)
return SK_PASS;
// 仅仅一个reuseport组而已
reuseport_array = bpf_map_lookup_elem(&outer_map, &dummy);
if (!reuseport_array)
return SK_DROP;
// ... key的获取过程,略!
// key计算自数据包内容
err = bpf_sk_select_reuseport(reuse_md, reuseport_array, &key, flags);
if (err)
return SK_DROP;
return SK_PASS;
}
char _license[] SEC("license") = "GPL";
如果想知道更加详细的信息,请自行debug内核源码树的下面的文件:
linux-source-5.3.0/tools/testing/selftests/bpf/progs/test_select_reuseport_kern.c
linux-source-5.3.0/tools/testing/selftests/bpf/test_select_reuseport.c
自诩reuseport,Netfilter/iptables,eBPF/XDP骨灰级玩家,欢迎志同道合者周末一起玩耍,然而也有我不会,不擅长的,比如DPDK,FPGA,Golang,数据库等,等等等等。
浙江温州皮鞋湿,下雨进水不会胖!