用Linux内核的瑞士军刀-eBPF实现socket转发offload
我们已经对eBPF将网络转发offload到XDP(eXpress Data Path)耳熟能详,作为Linux内核的一把 “瑞士军刀” ,eBPF能做的事情可不止一件,它是一个多面手。
继 实现一个基于XDP_eBPF的学习型网桥 之后,我们来看看如何 基于eBPF实现socket转发的offload。
socket数据offload问题
通过代理服务器在两个TCP接连之间转发数据是一个非常常见的需求,特别是在CDN的场景下,然而这个代理服务器也是整条路径中的瓶颈之所在,代理服务器的七层转发行为极大地消耗着单机性能,所以,通过代理服务器的七层转发的优化,是一件必须要做的事。
所以,问题来了, eBPF能不能将代理程序的数据转发offload到内核呢? 如果可以做到,这就意味着这个offload可以达到和XDP offload相近的功效:
-
减少上下文切换,缩短转发逻辑路径,释放host CPU。
这个问题之所以很重要亟待解决,是因为现在的很多机制都不完美:
-
传统的read/write方式需要两次系统调用和两次数据拷贝。
-
稍微新些的sendfile方式不支持socket到socket的转发,且仍需要在唤醒的进程上下文中进行系统调用 。
-
DPDK以及各种分散/聚集IO,零拷贝技术需要对应用进行比较大的重构,太复杂。
-
…
sockmap的引入
Linux 4.14内核带来了sockmap,详见下面的
lwn: BPF: sockmap and sk redirect support: https://lwn.net/Articles/731133/
还有下面的blog也很不错:
https://blog.cloudflare.com/sockmap-tcp-splicing-of-the-future/
又是eBPF!这意味着用sockmap做redirect注定简单,小巧!
我们先看下sockmap相对于上述的转发机制有什么不同,下面是个原理图:
sockmap的实现非常简单,它通过替换sk data ready回调函数的方式接管整个数据面的转发逻辑处理。
按照常规,sk data ready是内核协议栈和进程上下文的socket之间的数据通道接口,它将数据从内核协议栈交接给了持有socket的进程:
常规处理的sk data ready回调函数的控制权转移是通过一次wakeup操作来完成的,这意味着一次上下文的切换。
而sockmap的处理与此不同,sockmap通过一种称为 Stream Parser 的机制,将数据包的控制权转移到eBPF处理程序,而eBPF程序可以实现数据流的Redirect,这就实现了socket数据之间的offload短路处理:
关于 Stream Parser ,详情参见其内核文档:https://www.kernel.org/doc/Documentation/networking/strparser.txt
实例演示
任何机制能实际run起来才是一个真正的起点,现在又到了实例演示的环节。
我们先从一个简单proxy程序开始,然后我们为它注入基于eBPF的sockmap逻辑,实现proxy的offload转发,从而理解整个过程。
我们的proxy程序非常简单,你可以将它理解成一个socket Bridge,它从一个连接接收数据并简单地将该数据转发到另一个连接,稍微修改一下即可实现socket Hub/Switch以及Service mesh。
socket Bridge代码如下:
我们来看一下它的工作过程。
首先起两个netcat,分别侦听两个不同的端口,然后运行proxy程序。在netcat终端敲入字符,就可以看到它被代理到另一个netcat终端的过程了:
我们看到,一次转发经过了两次系统调用(忽略select)和两次数据拷贝。
我们的demo旨在演示基于eBPF的sockmap对proxy转发的offload过程,所以接下来,我们对上述代码进行一些改造,即加入对sockmap的支持。
这意味着我们需要做两件事:
-
写一个在socket之间转发数据的eBPF程序,并编译成字节码。
-
在proxy代码中加入eBPF程序的加载代码,并编译成可执行程序。
首先,先给出ebpf程序的C代码:
上述代码在内核源码树的 samples/bpf 目录下编译,只需要在Makefile中加入以下的行即可:
OK,下面我们给出用户态的测试程序,实际上就是将我们最初的 proxy.c 增加对ebpf/sockmap的支持即可:
同样的,为了和eBPF程序配套,我们在Makefile中增加下面的行:
最后直接在 samples/bpf 目录下make即可生成下面的文件:
为了验证效果,我们起五个屏,下面是一个演示的过程截图和步骤说明:
可见,proxy转发数据流的逻辑通过一个eBPF小程序从用户态服务进程中offload到了内核协议栈。用户态的proxy进程甚至不会由于数据的到来而被wakeup,这是比sendfile/splice高效的地方。
从上面的demo可以看到,sockmap顾名思义可以对接两个socket,这是eBPF这把 “瑞士军刀” 专门针对socket的一个小器件,这完美解决了sendfile的in_fd必须支持mmap的限制:
demo的代码和演示就到这里,我们再一次看到了eBPF之妙!
附:eBPF-可编程内核利器
我先说下为什么我把eBPF看作一把瑞士军刀:
瑞士军刀,包含小巧的圆珠笔、牙签、剪刀、平口刀、开罐器、螺丝刀、镊子等…
eBPF呢,它可以附着在xdp,kprobe,skb,socket lookup,trace,cgroup,reuseport,sched,filter等功能点,有人可能会说eBPF不如Nginx,不如OpenWRT,不如OVS,不如iptables/nftables…确实,但是这就好比说瑞士军刀不如AK47,不如东风-41洲际导弹,不如Zippo,不如张小泉王麻子,不如苏泊尔一样…
eBPF和瑞士军刀一样,小而全是它们的本色( eBPF严格限制指令数量 ),便携,功能丰富,手艺人离不开的利器。
eBPF让 内核可编程 变的可能!
内核可编程是一个很有意思的事情,它使得内核的一些关键逻辑不再是一成不变的,而是可以通过eBPF对其进行编程,实现更多的策略化逻辑。
目前,eBPF已经密密麻麻扎进了Linux的各个角落,eBPF的作用点还在持续增多,迄至Linux 5.3内核,Linux内核已经支持如下的eBPF程序类型:
一共26种类型,26个作用点。而在不久之前的Linux 4.19内核,这个数值也就22。可见eBPF吞噬内核的速度之快!
后面,我们还会看到eBPF在socket lookup机制所起的妙用。
浙江温州皮鞋湿,下雨进水不会胖。
(完)
Linux阅码场精选在线视频课程汇总
觉得内容不错的话,别忘了右下角点个 在看 哦~