Linux内核社区最近发布了bpfilter,一个使用Linux BPF提供的高性能网络过滤内核模块,用来替代netfilter作为iptables的长期支持的内核底层的实现,实现Linux用户的无痛向BPF过渡的换心手术。
BPF可能我们比较生疏,但是我说起tcpdump、Wireshark等流行的网络抓包和分析工具你一定听说并可能使用过,他们底层的包过滤实现就是用的BPF。所以他不是一个新的技术,也已经陪伴我们很久了。
目前,BPF已经成长为一个高度灵活的和丰富功能的框架,它可以以不牺牲系统性能和安全性为前提下,大幅度扩展Linux的功能。BPF强大的灵活性、稳定性和丰富的功能,使得业界翘楚比如谷歌,facebook和Netflix等Linux内核前瞻性大企业用户纷纷对其伸出橄榄枝,用BPF来实现网络安全、负载均衡,还有性能监控、故障排查等大量的用途。 Netflix的Brendan Gregg首先称其为Linux的BPF Superpowers。
本文将介绍这些大企业实践中由于如何超负荷使用iptables内核子系统,从而导致的冗余、性能低下等问题,以及新内核如何利用新特性优雅地从底层解决这些问题。
在过去15年类,Linux内核社区构建了很多内核子系统,包括TPC/IP栈,iptables(netfiter)等等,在此过程中我们也看到了BPF一步步发展、成长、壮大。现在内核的新转变让我们意识到了BPF不仅仅是另一个功能,而是代表了一个根本的技术转型,它将及时改变Linux从网络到安全的各个方面。从iptables到bpfilter的转变,只是BPF为振兴Linux网络栈领域,使其更现代化的重要一步。为了解释为什么会有这激动人心的一步转变,首先我们介绍下内核中iptables的历史演变。
iptables及顺序过滤的起源
多年来iptables一直是Linux上实现防火墙和网络数据包过滤器的最主要的工具。从最初的ipchains,很多linux老司机最初可能都接触ipchains,他是iptables前身,是在linux 内核 2.2.10引入。然后是在2001年linux内核版本2.4.0开始引入了iptables。从此,多少年一来,iptables一直给用户带来了便利和麻烦两重天。一方面享受的灵活性和快速修复。另一方面,又为了在调试5000条沉沉的过滤规则而犯难,想为此骂娘。
在业界有一句笑话:"在任何团队,你都的需要一个坦克(武力输出),一个治疗师(奶妈),一个伤害输出(DPS),一个拥有控制群众能力的人(会长),还要一个知道iptables的人"。
当iptables在20年前取代其前身ipchains时,开始其生命周期时,防火墙功能的范围还很简单明确:
保护本地应用程序免受不需要的网络通信(INPUT链)
保护本地应用程序发送不需要的网络通信(OUTPUT链)
过滤由Linux系统(FORWARD链)转发/路由的网络流量。
那时后网络速度很慢,日子过的很慢。还记得用宿舍用Modem,201卡拨号情形么?那是iptables最初被设计和开发的时代。
用iptables实现访问控制列表(ACLs)的标准做法是使用连续的规则列表,即每个接收或发送的网络数据包都要逐一与规则列表进行匹配,直到匹配或者全不匹配。
然而,逐行的处理有明显的缺陷:过滤数据包的成本,随着添加的规则数量线性增加。
权衡处理:ipset
一段时间过去了,网络速度开始提高了,iptables的设置规则,也从十几条增加到数千条规则。从性能和延迟角度来看,遍历顺序的iptables列表已经变得不可忍受。
社区很快发现了瓶颈所在:长长的规则列表要么拒绝,要么允许单独的IP地址和端口组合。为此引进了ipset对IP地址进行管理。 ipset允许将与IP地址和端口组合存储到散列表中,在iptables只需引用ipset中的散列的键名就可以,极大的减少iptables规则的数量,而且ipset散列中的IP地址信息常驻内存,匹配非常快。但这也只是"头疼医头,脚疼治脚"的治表不治本的暂时权衡之策。
更不幸的是,ipset还不能适用于所有情况。近些年,随着容器技术的崛起,一个明显的问题是kube-proxy,他是Kubernetes的一个组件,容器要使用iptables和-j DNAT规则为服务提供负载均衡。它要为为每个后端服务要添加多条iptables规则。对于添加到Kubernetes的每个服务,要遍历的iptables规则列表呈指数增长。
最近的KubeCon议题中详细研究kube-proxy的性能表现细节。研究结果显示,随着服务数量的增长,网络延迟和性能下降无法估量。还披露了iptables的另一个主要缺点,无法实现增量更新。每次添加新规则时,必须更新整个规则列表。结果是对2万个Kubernetes服务16万条的iptables规则,装配这些规则需要耗时5个小时。
使用基于IP/端口的机制一般还有许多其他明显的缺点,特别是在容器应用环境下。容器需要经常部署和删除。这可能导致个别IP地址的使用快速变化。一个IP地址可能被一个容器使用几秒钟,然后在几秒钟之后会换到一个容器使用。这使得依靠使用IP地址进行安全过滤的系统受到压力,因为集群中的所有节点都必须始终知道最新的IP到容器的映射。虽然在一个集群内部这几乎没有什么难度,但它在如果在分布式的跨集群应用中就会很有挑战性。关于这些这儿不在赘述,可以参考docker,k8s等相关的官方文档。
BPF的兴起
近年来,BPF一直以疯狂的速度发展,这次内核将其内置的变化,给BPF可自由飞翔的翅膀,给它带来强大功能和高效的可编程性。以前需要自定义内核开发和重新编译内核的任务现在可以通过高效BPF程序在BPF沙箱安全边界内的来实现。
下面的列出了BPF在各种项目和公司使用实例:
Cilium, 是BPF在容器领域的锋芒初现,提供强大而高效的网络,安全,以及3-7层的负载均衡。
Facebook公司用BPF/XDP负载均衡架构取代了IPVS。该架构还支持缓解DDoS逻辑。尽管与iptables相比,IPVS是一个不错的进步,Facebook则更进了一步从IPVS转移到BPF,使其性能提高了10倍。eBPF性能非常诱人,社区中有人用XDP编写的一个简单的入口防火墙就可以轻松实现每秒处理1100万个数据包的性能。
Netflix,特别是Brendan Gregg,一直利用BPF的功能进行性能分析和跟踪。bbc(github /iovisor/bcc)项目为用户提供了访问BPF的接口。例如,可用于生成令神奇的火焰图:
谷歌,一直致力于bpfd,可使用eBPF实现强大的Linux跟踪远程目标。基于BPF的上游参与,他们也在考虑将各种内部项目都迁移到BPF。
Cloudflare正在使用BPF来缓解DDoS攻击,并发布了多篇博文,并就该主题进行了多次公开演讲。
Suricata是一个入侵检测系统 (Intrusion Detection System,IDS),项目开始使用BPF和XDP来替换老的基于iptables的来监听数据的nfqueue的基础架构。
Open vSwitch一直在使用eBPF驱动的数据路径。
还有大量例子,限于篇幅在此不在多列,可以参考BPF官方指南列表(github:cilium/ )
用BPF统一一切
BPF革命性开发的新进展更令人兴奋,即通过用户完全透明的方式用BPF完全替换iptables的内核部分(netfiter),即现有iptables客户端和库无需任何改动。
感兴趣的同学可以去linux 内核邮件列表中找到相关的讨论。该提案由Daniel Borkmann(Covalent),网络维护部分由David Miller(Red Hat)和Alexei Starovoitov(Facebook)撰写。
Quentin Monnet在FRnOG 30上提供的以下图表显示了与iptables和nftables相比较bpfilter的一些早期测试结果。该结果显示了仅仅通过BPF软件实现以及空载硬件的对比测试:
这些早期的测试数据展示了令人乍舌的性能优势,也是BPF强大的一个实例。我们唯一要注意的是bpfilter和BPF本身并不能解决由iptables使用顺序过滤引起的性能问题,要彻底解决这个问题必须使用BPF底层内核及原生的BPF应用,关于BPF原生应用可以关注Cilium项目。
内核社区响应?
Linux内核邮件列表历来以简单粗暴、火药味浓而闻名。那么在用BPF替换iptables这件事情上,社区吵炸裂了么?没有,实际上,社区反应很平静和积极。核心iptables维护人员立即给BPF提出了建设性的建议。
Florian Westphal提出了一个运行在bpfilter上框架,通过框架并将nftables转换为BPF。框架允许保持特定领域nftables语句,而且还可以带有JIT编译器,硬件卸载和工具集等BPF运行时的所有优点。
Pablo Neira Ayuso似乎一直在研究类似的提案,并发表了一系列将nftables翻译成BPF的系列文章。和Florian Westphal方法的主要区别是Pablo打算将翻译操作放到内核中。任何需要注入BPF的程序都必须通过用户空间进行,并通过BPF验证程序来保证BPF的安全行为。
总结
BPF是很多年以来Linux开发出来最令人兴奋的特性。我们几乎没有错过任何发挥最大潜力的机会,它仍在不断发展进化。用BPF替换iptables的内核部分是合乎逻辑的第一步。真正的转变将是BPF原生工具以及背离传统的IP地址/端口为中心的基础架构彻底更换,但是万里长城要一步一步的走,而不是一口气吃个胖子,导致消化不良,三高啥的!你说呢?