我想用eBPF或者至少cBPF实现一个功能:

  • 根据来源和目标IP地址来选择同一个reuseport组的socket。

比方说,我的服务器上有4个IP地址分别是10.0.0.1,10.0.0.2,10.0.0.3,10.0.0.4,我的服务侦听0.0.0.0:1234,分别有4个reuseport socket提供,我希望的select算法是:

  • 访问10.0.0.1的分派给sk1。
  • 访问10.0.0.2的分派给sk2。
  • 访问10.0.0.3的分派给sk3。
  • 访问10.0.0.4的分派给sk4。

不要问我这个需求哪来的,它是真实存在的,我的reuseport组三线连接三大运营商,每一个组内socket均有不同的策略,我受够了Netfilter,所以我必须用IP地址来进行socket查找。

然而这么简单的算法却无法实现!

对于cBPF而言,bpf程序携带的skb参数是pull过的,一直pull到TCP/UDP的payload位置,因此bpf程序连TCP/UDP头都无法访问,就更别提IP头了。

对于eBPF而言,结构体sk_reuseport_md是eBPF程序的参数:

struct sk_reuseport_md {/** Start of directly accessible data. It begins from* the tcp/udp header.*/// 注意上面的注释!!__bpf_md_ptr(void *, data); /* End of directly accessible data */__bpf_md_ptr(void *, data_end);/** Total length of packet (starting from the tcp/udp header).* Note that the directly accessible bytes (data_end - data)* could be less than this "len".  Those bytes could be* indirectly read by a helper "bpf_skb_load_bytes()".*/__u32 len;/** Eth protocol in the mac header (network byte order). e.g.* ETH_P_IP(0x0800) and ETH_P_IPV6(0x86DD)*/__u32 eth_protocol;__u32 ip_protocol;      /* IP protocol. e.g. IPPROTO_TCP, IPPROTO_UDP */__u32 bind_inany;       /* Is sock bound to an INANY address? */__u32 hash;             /* A hash of the packet 4 tuples */
};

比cBPF强一点,至少可以访问TCP/UDP头了,然而还是无法访问IP头!

那么见招拆招,要想做到让bpf程序可以访问到IP头,办法很简单:

  • 对于cBPF:在bpf_prog_run之前,不要pull,而要push一个TCP/UDP的头。
  • 对于eBPF:在sk_reuseport_md结构体中加入五元组信息即可。

在实际修改生效之前,迄至5.3版本内核,目前 只能基于TCP/UDP不包括协议头的有效payload来选择socket了!

还要说一句,对于UDP而言,每一个数据包均可携带payload,自然可以根据payload的内容来选择socket,正如Quic协议经常用的那样,我自己曾经也用这个方法实现了基于SessionID的UDP隧道的构建。那么对于TCP呢?

TCP仅仅初始化连接时的SYN包受REUSEPORT的控制,然而这个SYN包是没有payload的!如此一来,你只能使用内置的字段来使用了:

//include/uapi/linux/filter.h
#define SKF_AD_OFF    (-0x1000)
#define SKF_AD_PROTOCOL 0
#define SKF_AD_PKTTYPE  4
#define SKF_AD_IFINDEX  8
#define SKF_AD_NLATTR   12
#define SKF_AD_NLATTR_NEST      16
#define SKF_AD_MARK     20
#define SKF_AD_QUEUE    24
#define SKF_AD_HATYPE   28
#define SKF_AD_RXHASH   32
#define SKF_AD_CPU      36
#define SKF_AD_ALU_XOR_X        40
#define SKF_AD_VLAN_TAG 44
#define SKF_AD_VLAN_TAG_PRESENT 48
#define SKF_AD_PAY_OFFSET       52
#define SKF_AD_RANDOM   56
#define SKF_AD_VLAN_TPID        60
#define SKF_AD_MAX      64

除此之外,和TCP SYN包本身相关的任何字段都无法使用,怎么办?这也许是一个败笔!

但是,仍然可以利用TCP的Fastopen特性。该特性让TCP的SYN包也可以携带payload,虽然目前无论在端到端还是中间链路都还没有完备的支持,但至少可以玩一玩。

来来来,现在让我来演示一个示例,表演一下 在使能TCP Fastopen的前提下,如何根据SYN包的paylaod来选择socket。

eBPF太麻烦了,所以我选择退回到cBPF。

下面是服务端的C代码:

// server.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/filter.h>int main(int argc, char **argv)
{int i = 0;int sd = -1;int optval = 1;struct sockaddr_in saddr;int len;
#define NUM     4// 超级简单的cBPF程序:// payload的第一个字节与socket数量取模,获得socket索引。struct sock_filter code[]={{ BPF_LD  | BPF_B | BPF_ABS, 0, 0, 0}, // load载荷的第一个字节到A{ BPF_ALU | BPF_MOD, 0, 0, NUM},           // 将A对4取模{ BPF_RET | BPF_A, 0, 0, 0 },          // 返回A};struct sock_fprog bpf = {.len = 3,.filter = code,};for (i = 0; i < NUM; i++) {if (fork() == 0) {sd = socket(AF_INET, SOCK_STREAM, 0);if (sd < 0) {exit(1);}saddr.sin_family = AF_INET;saddr.sin_port = htons(12345);saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); if (setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, (const void *)&optval,sizeof(optval))) {exit(1);}if (setsockopt(sd, 6, 23, (const void *)&optval, sizeof(optval))) {exit(1);}if (bind(sd, (struct sockaddr *)&saddr, sizeof(struct sockaddr))) {exit(1);}if (listen(sd, 100)) {exit(1);}if (setsockopt(sd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, (const void *)&bpf, sizeof(bpf))) {exit(1);}while (1) {int cd;struct sockaddr_in caddr;len = sizeof(caddr);if ((cd = accept(sd, (struct sockaddr *)&caddr, &len)) == -1) {continue;}printf("client addr%s port:%d  porit %d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),i);close(cd);}}}sleep(1000);return 0;
}

下面给出客户端的python代码:

#!/usr/bin/python
# cli.py
import socket
import sysMSG_FASTOPEN = 0x20000000data = int(sys.argv[1])
host = '127.0.0.1'addr = (host, 12345)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.sendto(str(data), MSG_FASTOPEN, addr)

OK,来试一下吧。

首先使能fastopen:

sysctl -w net.ipv4.tcp_fastopen=3

启动server,然后用不同的数字作为参数运行client:

root@zhaoya-VirtualBox:/usr/py# ./cli.py 1
root@zhaoya-VirtualBox:/usr/py# ./cli.py 0
root@zhaoya-VirtualBox:/usr/py# ./cli.py 2
root@zhaoya-VirtualBox:/usr/py# ./cli.py 3
root@zhaoya-VirtualBox:/usr/py# ./cli.py 0
root@zhaoya-VirtualBox:/usr/py# ./cli.py 1
root@zhaoya-VirtualBox:/usr/py# ./cli.py 4

观察server的输出:

root@zhaoya-VirtualBox:/usr/py# ./server
clent addr127.0.0.1 port:44778  porit 1
clent addr127.0.0.1 port:44780  porit 0
clent addr127.0.0.1 port:44782  porit 2
clent addr127.0.0.1 port:44784  porit 3
clent addr127.0.0.1 port:44786  porit 0
clent addr127.0.0.1 port:44788  porit 1
clent addr127.0.0.1 port:44790  porit 0

完全正确的选择!

基本就是这么个玩法了。如果没有Fastopen,那么对于TCP REUSEPORT的bpf程序选择socket,除了映射一下队列,CPU之外,基本没得玩。

OK,现在回过头来思考一个问题,到底有没有必要用bpf程序来选择socket,我认为代价有点大:

  • 对于TCP而言,由于SYN包没有有效payload,功能支持有限。
  • 对于UDP而言,每个包都要经过REUSEPORT的bpf程序,那么一堆字节码堵在数据路径,影响性能。除非为UDP设计一个REUSEPORT的cache。
  • 即便是支持了IP头字段,仍然得不偿失,完全有另外的方案可以完成此事。

在我看来,使用BPF程序选择socket弊大于利,而且引入了复杂性,意义不大,如果实在遇到无法直接支持的需求,还是直接修改代码来live patch比较妥当。

经理皮了鞋,空悲切!


浙江温州皮鞋湿,下雨进水不会胖。

cBPF/eBPF如何处理reuseport的吐槽和示例相关推荐

  1. 深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium)

    [BPF入门系列-1]eBPF 技术简介 | 深入浅出 eBPF[BPF入门系列-1]eBPF 技术简介https://www.ebpf.top/post/bpf_intro_blog/ 目录 eBP ...

  2. Linux 内核观测技术 eBPF 中文入门指南

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 很早前就想写一篇关于 eBPF 的文章,但是迟迟没有动手,这两天有点时间,所以就来写一篇.这文章主要还是简单的介绍 ...

  3. eBPF 是用来干什么的?

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | ...

  4. eBPF学习仓库bpf_study-996station GitHub鉴赏官

    推荐理由:eBPF学习参考资料,Linux内核观测技术BPF免费下载(英文) "eBPF 是我见过的 Linux 中最神奇的技术,没有之一,已成为 Linux 内核中顶级子模块,从 tcpd ...

  5. java音频源码,Android Java实时音频SDK示例源码下载 - 开发者中心 - ZEGO即构科技

    示例源码 本地下载 本地下载 GitHub下载 说明 示例代码运行指引 1 准备环境 在开始集成 ZEGO Express SDK 前,请确保开发环境满足以下要求(以下说明皆以 macOS 开发电脑为 ...

  6. 【BPF入门系列-1】eBPF 技术简介

    由范老师和我一起翻译的图书 <Linux内核观测技术BPF> 已经在 JD 上有现货,欢迎感兴趣 BPF 技术的同学选购.链接地址 https://item.jd.com/72110825 ...

  7. 和Sidecars说再见,看eBPF如何解决服务网格

    和Sidecars说再见,看eBPF如何解决服务网格 How eBPF will solve Service Mesh - Goodbye Sidecars https://isovalent.com ...

  8. 万字长文|深入理解XDP全景指南

    译者序 本文翻译自 2018 年 ACM CoNEXT 大会上的一篇文章: The eXpress Data Path: Fast Programmable Packet Processing in ...

  9. [Spring Cloud Task]6 Spring Batch批处理应用设计原则

    2019独角兽企业重金招聘Python工程师标准>>> 概述 本文是Spring Cloud Task系列的第五篇文章,如果你尚未使用过Spring Cloud Task,请 移步s ...

最新文章

  1. 如何获取iOS设备的IP地址
  2. hls之m3u8、ts流格式详解
  3. 我的工作日报 - 2020-9-11 星期五
  4. pytorch reshape_PyTorch中的contiguous
  5. Venkat 演讲翻译:你要清除代码中的异味
  6. One-hot encoding 独热编码
  7. 法拉第未来获得2.25亿美元债权及信托融资
  8. 直接使用editbox.clear()清空时,有时会无法清除完全,此时有清空文本框的另一种方法...
  9. 1188 最大公约数之和 V2
  10. c语言学习-猜数字游戏
  11. ENVI5.3.1使用Landsat 8影像进行预处理及分析实例操作
  12. 易筋SpringBoot 2.1 | 第七篇:JPA访问MySQL
  13. nbu备份文件失败,提示信息NBU status: 2074, EMM status: Disk volume is down
  14. html 文字 向上滚动代码,文字向上滚动代码
  15. 当我再次看到你————中秋致Leslie
  16. 从《象形拳法真诠》看王芗斋与薛颠
  17. 阿里云机器的JVM内存调优经历(菜鸟必看,大神请绕道)
  18. OpenCV-DoG
  19. Nebula Graph|信息图谱在携程酒店的应用
  20. 未来简史--读书语句摘录及感悟

热门文章

  1. 一生要做的五十件事(三)
  2. python简单圣诞树手工折纸_圣诞树简单手工diy折纸图解教程
  3. DApp是什么,DApp是必然趋势
  4. Python3如何使用PIL批量给图片加圆角和黑边
  5. 微服务终极笔记:穿针引线“直取京都”,拒绝散兵游勇
  6. https、公钥、私钥、数字签名、数字证书
  7. FPGA 开发板的 I/O BANK 电路 - xc7a35tftg256-1
  8. 滑铁卢计算机工程专业怎么样,滑铁卢大学计算机工程专业:录取几率5%
  9. android平板电脑排行榜,安卓平板电脑排行榜2015前十名 安卓平板电脑什么牌子好...
  10. NDK 环境配置看这篇就够了!