LWN:Lua in the kernel!

Posted Linux News搬运工

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LWN:Lua in the kernel!相关的知识,希望对你有一定的参考价值。

关注了就能看到更多这么棒的文章哦~


Lua in the kernel?

By Jake Edge
September 9, 2020
Netdev
原文来自:https://lwn.net/Articles/830154/
DeepL assisted translation

BPF,是 Linux 内核中用于网络(和其他一些)部分的专门定制的语言,但有些人一直在使用 Lua 语言进行网络领域的分析工作。来自 Ring-0 Networks 的两位开发者 Lourival Vieira Neto 和 Victor Nogueira 来到在线会议 Netdev 0x14 上介绍了这项工作。它包括一个允许将 Lua 脚本注入运行中的内核的框架,以及两个针对路由器的项目,其中一个项目已经在 2000 万台设备上得到了部署。

Neto 介绍说,这次演讲也是基于里约热内卢天主教大学(PUC-Rio)的 Ana Lúcia de Moura 和 Roberto Ierusalimschy 的工作。Neto 说,他们自 2008 年以来一直在研究内核脚本,为 Linux 开发了 Lunatik 框架。它允许内核开发者用 Lua 在各自的子系统里面编写脚本,也允许用户在内核中加载和运行他们的 Lua 脚本。

Lua and sandboxes

他说,选择 Lua 是因为它是一种小型、快速的语言。它也被广泛地用作网络工具的脚本语言,如 Wireshark、Nmap 和 Snort。演讲的重点是 Linux 中的两个网络子系统,即用 NFLua 编写 netfilter 脚本和使用 XDPLua 编写 Express data path(XDP)子系统的脚本。

 [Lourival Vieira Neto]

内核中的任何脚本都不能导致内核故障,这是一个很重要的原则。脚本不能使系统崩溃,也不能无限期运行,不能破坏系统的其他部分。他说,为了确保这一点,Lunatik 使用 Lua 虚拟机(VM)的机制对脚本进行沙箱处理,使其在安全的执行环境中运行。

Lua 脚本不能直接进行内存寻址,它们只能通过 Lua data types 数据类型(如字符串和表格)访问内存。所有的 Lua types 都由虚拟机分配,并在不再使用时进行垃圾回收。但这并不足以限制脚本造成破坏,因为它们可以分配大量对象,占用大量的内存来伤害系统的其他部分。为了避免这个问题,专门给它使用了一个专门的内存分配器,从而为 Lua 脚本可用的内存量设置上限。

Lua 提供了 "fully isolated execution states,完全隔离的执行状态",Neto 说。这些状态最初只初始化提供了基本的语言操作符。然后,子系统的开发者可以决定加载哪些库,以便为脚本提供额外的功能。这些库可能是 Lua 标准库,也可能是专用库,如 Luadata 和 LuaRCU。前者可以让 Lua 虚拟机能安全访问外部数据,而后者提供了在多个执行状态之间共享数据的机制。例如,NFLua 和 XDPLua 都使用 Luadata 来访问 packet data。

Lua 提供了一个单线程的执行环境,没有任何用于同步的机制(比如 mutex)。这意味着脚本不可能通过一条指令来阻塞内核,但脚本仍然可以无限期地运行。Lua 有一个机制,可以在脚本运行一定数量的指令后中断脚本执行,NFLua 和 XDPLua 都使用了这个机制。Lunatik 允许通过内核中的多个 execution state 来进行多任务处理。

只有具有 CAP_NET_ADMIN capability 的网络管理员才能加载脚本和访问 execution state。可以使用 Netlink socket 在内核和用户空间之间传输数据。他说,每次访问时都会检查这个 capability,他说。

NFLua

NFLua 是一个 netfilter 扩展,目的是使用 Lua 对第 7 层(application layer,应用层)进行高级过滤操作。可以在第 3 层(network)和第 4 层(transport)使用 iptables 规则将数据包发送到 NFLua,然后调用脚本来检查这些更高层级的内容。Lua 已经被网络运营商广泛用于各种任务,包括用于安全和网络监控,所以 Lua 很适合这种过滤工作。

NFLua 是作为一个 loadable kernel module 来实现的,它包含了 Lunatik 框架、Lua 解释器以及各种提供给 execution state 的库。它被加载之后,就可以使用 nfluactl 命令来创建一个 Lua state,并在其中加载 Lua 代码。

他举了一个例子:对基于 HTTP 请求发送的 User-Agent 进行简单的过滤。先用一个 iptables 规则来根据名字引导数据包到一个 execution state,以及该状态下的一个函数。与规则相匹配的数据包(被发送到 80 端口)会被传递给 NFLua,NFLua 会用调用相应函数来处理此数据包。该函数根据 HTTP 请求中的 User-Agent,查找是否在屏蔽名单列表中,来决定是否阻止。Lua 函数的返回值表示 netfilter 是否应该终止连接,还是允许连接继续。

XDPLua

接下来 Nogueira 接着进行介绍。他将 XDPLua 描述为 XDP 的扩展,允许在 data path 中使用 Lua。它代表了 NFLua 的自然演进,在网络栈处理数据包之前对其进行处理。它为每个 CPU 创建一个 Lua execution state,因此它可以利用现代系统的并行性。该项目的目标之一是在 data path 上增加 "expressiveness and dynamism, 表现力和动态性",这样程序员就可以创建更复杂的应用程序加载到内核中。

XDP 使用的是 BPF,所以 XDPLua 为 Lua C API 封装成为 BPF helper 的形式,从而允许 BPF 程序调用 Lua 脚本。XDPLua 的开发者希望 BPF 和 Lua 合作,"这样我们就可以拥有两者的优点",Nogueira 说。他们希望在保持 Lua 的表现力(expressiveness)的同时,还能获得 BPF 的性能。

 [Victor Nogueira]

他很快就介绍了和 Neto 一样的例子。Lua 程序被加载到 XDPLua 中,而一个 BPF 程序被加载到 XDP 中。当一个数据包到达 XDP 时,BPF 程序可以调用 Lua 函数来决定是阻止还是允许处理这个请求,如果允许,那么这个数据包将被传递到网络栈。

他展示的另一个例子是在数据包到达网络服务器之前处理 cookie 值,cookie 值被用来区分机器人和合法流量。在处理来自某个客户端的第一个请求时,网络服务器会回复一个 cookie 值和一些 javascript(用来将 cookie 值附加到后续请求中)。由于机器人通常不会运行 JavaScript,所以它们不会带有正确的 Cookie 值。

他还简要介绍了一个访问控制(access-control)的例子,在 TLS 连接请求中使用 server name indication(SNI)来限制可以连接到哪些 domain。这可以用来禁止网络中的本地用户访问一个被禁止的网站。通过使用一个简单的屏蔽列表和 Lua 函数,以及一个 BPF 程序来识别 TLS 客户端的 hello 消息并调用 Lua 函数,就可以从 XDP 中检查 SNI 数据。

Benchmarks

为了收集一些数据,分别用 NFLua、XDPLua 和 XDP(完全使用 BPF 不用 Lua)实现了 access-control 的例子。BPF 版本很难编写,用起来很麻烦,他说。而同样的 Lua 脚本在 NFLua 和 XDPLua 之间是共享的。测试中使用 trafgen 来发送 TLS 客户端 hello 数据包,其 SNI 值是在屏蔽列表中的,数据包用最快速度来生成并发送。测量主要关注两个数据:服务器上每秒丢掉多少个连接(drop rate),以及 CPU 使用率。这是一个完全虚拟化的测试环境,客户端和服务器都运行在 8 核 3GHz 的 CPU 上,每个 CPU 有 32GB 的内存,它们通过一个 10Gbps 的 virtio 网络接口互相连接。

NFLua 每秒可以丢掉大约 0.5 万个数据包(0.5-million per second),而 XDPLua 和 XDP/BPF 都可以达到大约三倍于这个速度(1.5Mpps)。此外,XDPLua 和 XDP/BPF 都使用了大约 0.1%的可用 CPU,而 NFLua 使用了 50%。Nogueira 表示,NFLua 只能在数据包经过 network stack 后才能得到数据包,它也并没有利用多核的优势,这可能有助于解释 这个 500 倍的差异。值得注意的是,让 XDP 转为通过 BPF 调用 Lua ,并没有增加多少 CPU 的占用率,他说。

Neto 又回到演讲中,在演讲者接受与会者提问之前进行了总结。这 45 分钟时间中,大约有一半的时间用于 Q&A。他指出,NFLua 已经用在 2000 万台家庭路由器中,网络运营商正在将其用于安全和监控任务。从 NFLua 中吸取的经验被融入到 XDPLua 中,XDPLua 从设计之初就与 BPF 合作,使开发者既得到 Lua 的易用性又能有 BPF 的性能。XDPLua 目前用于 Ring-0 Networks 的防火墙产品,这些产品作为基础设施的一部分部署在 10Gbps 网络上的 internet point of presense(POP)本地接入点。

他们面临的一个问题是,XDP 不支持通过可加载的内核模块(loadable kernel module)来扩展。而 Netfilter 支持,这个功能对开发基于 Lua 的过滤机制很有利。为了支持 XDPLua 而维护标准 kernel 外的代码是比较麻烦的。

Lua 环境没有像 BPF 那样使用内核内验证机制(in-kernel verifier),而是采取沙盒的方式来保护内核。BPF verifier 可能会很难用,正如他们在开发 XDP/BPF 版本的 access-control benchmark 时的感受,Neto 说。随之,他们开始了观众提问。

Questions

Tom Herbert 是 Netdev 的负责人,他开门见山地说,要想把这项工作合并到主线上,将是一场艰苦的斗争。BPF verifier 是让内核开发者能够放心使用 XDP 的部分原因,所以将 Lua 加入内核也需要同样的努力,让他们相信 Lua 也是安全的。例如,内核不能因为 Lua 不适当地访问了内存而崩溃,如何防止这种情况的发生?Neto 重申,Lua 不会直接访问内核内存——它没有指针类型。它可以分配内存,但这可以(并且确实)是有限制的。此外,指令的数量可以被限制,所以不可能出现无限循环。

Neto 说,你可以在不同的地方实施安全保证:在编译时、加载时或运行时。BPF 用 verifier 进行加载时检查,而 Lua 在运行时用其虚拟机对程序进行沙箱检查。当然,虚拟机的实现可能存在 bug,但这也是 BPF verifier 同样面临的真实问题。

Herbert 说,另一个可能会被问到的问题是,为什么不能创建一个 Lua-to-BPF 编译器;已经有 C 和 P4 的编译器了,为什么不为 Lua 做呢?Neto 说,你也许可以把 Lua 语法变成 BPF,但你不能写一个运行在 BPF 上的 Lua 虚拟机,所以你不会得到 Lua 能提供的所有功能。BPF verifier 特意限制了可以运行的 BPF,所以你不能写通用代码(general-purpose code)。你也许可以拥有 Lua 的一些特性,但如果你的目标是用 BPF 来执行,则肯定不能得到全部功能(full package)。

Shrijeet Mukherjee 说,在内核中拥有两个虚拟机很可能会有问题。他建议尽量减少内核中的 Lua 虚拟机组件,并尽可能地将其移到用户空间。BPF VM 已经有了成果和很大的接受度,从社区的角度来看,再增加一个虚拟机会很困难。Neto 说,让 Lua 工作合入 upsteam 不一定是我们该走的方向,如果 XDP 能提供一个允许动态扩展(dynamic extension)的机制,就像 netfilter 一样,那也可以。Herbert 说,这个观点很难推销:XDP 一开始的想法是,它将是可以随意插入和卸载的(pluggable),但现在它只是一个 BPF hook。

Netdev 组织者 Jamal Hadi Salim 表示,网络方面的工作既需要脚本,也需要编译代码。但要在内核中加入另一种编程环境,存在政治和技术上的障碍。安全方面很重要,但他认为 Lua 可以满足要求,只是与 BPF 的做法有些不同而已。

Mukherjee 建议,可能有一种方法可以把事情分开,比如内核中的数据包处理是在 XDP 中完成的,而策略处理(policy handling)可以在用户空间中用 Lua 完成,通信可以通过共享的 BPF map 来完成。他说,数据包处理确实属于内核的范畴,但 policy 方面可能不属于内核的职责了。但正如 Neto 指出的那样,这样做会增加 latency。他们过去曾尝试过这种方法,但由于性能问题,他们转而使用 NFLua,然后再使用 XDPLua。

但 Mukherjee 想知道,在内核中缓存这些 policy decision 是否可以避免那些采用用户空间方案增加的 latency 中的很大一部分?基本的东西可以在内核中处理,而 "真正复杂的" 部分则在用户空间中处理——以某种方式将决策结果缓存在内核中。他不确定这是否是一个合理的方法,但也许可以找到一个中间地带,使 Lua 所提供的大部分功能仍然可以在不放到内核中的情况下也得到利用。

一位与会者问到 XDPLua 的成熟度。Neto 说,它正在产品中正常运行,但也同时仍在开发中。目前还没有准备好向 upstream 提交的补丁,还需要进行清理工作。一位与会者表示,用于 benchmark 的系统在 10Gbps 的链路速度下来看的话,所用的 CPU 有些过于强大了,因此 XDP/BPF 和 XDPLua 之间的 CPU 使用率的差异并没有真正显示出来。Neto 同意,需要做更多的测试,可以使用更慢的虚拟 CPU。

Neto 在回答另一个问题时说,他们正在使用标准的 Lua,而不是 LuaJIT fork 分支。后续计划来调查一下用 "typed Lua" 来编译,这是主流的 Lua 项目所采取的方法,以便在性能上与 LuaJIT 竞争。Lunatik 的开发人员避免直接使用 LuaJIT,因为它是基于旧版语言的,但他们有兴趣尝试编译和优化后的 Lua ,希望能看到性能提升。

BPF 和 BPF VM 的巩固地位,使得人们很难想出来如何将 Lua 真正加入到内核里。将其他 pluggable programming environment 的 hook 添加到 XDP 中,可能是一个更可行的方法,尽管 Herbert 似乎并不太乐观。他(和其他一些人)认为 Lua 的方法很有趣,而且可能很有用。但是,"这只是一个 moonshot 类似的不太可能实现的项目",Herbert 说。XDPLua 开发者能否克服阻力,还有待观察。但似乎很明显,至少有一部分开发者对 BPF 编程环境的限制,感到不安。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



以上是关于LWN:Lua in the kernel!的主要内容,如果未能解决你的问题,请参考以下文章

Kernels and image sets for an operator and its dual

lua入门之二:c/c++ 调用lua及多个函数返回值的获取

linux kernel的cmdline參数解析原理分析

DRM简介

LinearSVC 和 SVC(kernel="linear") 有啥区别?

the evolution of Lua 全文翻译