使用 Pipy 和 eBPF 实现透明代理
背景
透明代理
透明代理是一种网络中间件,它能够在用户不知情的情况下拦截和转发网络流量。与传统代理不同,透明代理不需要在用户端配置特定的代理设置,而是通过在网络层面拦截流量来实现代理功能。透明代理通常被用于网络管理、安全策略实施、流量监控和优化等方面。它可以用于实现诸如内容过滤、缓存加速、流量控制、负载均衡等功能。常见的 TPROXY、NAT、Divert 等技术都可以用来实现透明代理。
eBPF
eBPF 是一种高效、灵活的内核技术,允许用户空间程序安全地执行预编译和限制在 Linux 内核空间中运行的程序(即 eBPF 程序),而不需要更改内核源代码或加载内核模块。近几年 eBPF 在网络方面的广泛应用,使其成为实现透明代理的可选技术之一。
Pipy 从 0.99.1 之后加入了对 BPF 的支持,可以加载和解析 BPF 程序;1.0 的语法升级,让其在实现控制逻辑方面更加得心应手。今天我们就来介绍如何使用 Pipy + eBPF 实现一个简单的透明代理。
注:文中所有的代码都可以在 Pipy 仓库中找到:https://github.com/flomesh-io/pipy/tree/main/samples/bpf/transparent-proxy。
快速开始
克隆代码。
git clone https://github.com/flomesh-io/pipy.git
cd samples/bpf/transparent-proxy
编译 BPF 程序,之后可以在目录中找到编译好的 .o
文件。
make
启动透明代理。
sudo pipy main.js
成功运行后,代理会监听端口 18000
。接着我们发送请求测试一下:
curl -L bing.com
在代理的控制台,我们可以找到请求和相应的日志。
GET / bing.com
301 Moved Permanently
GET / www.bing.com
200 OK
在整个过程中,代理对客户端保持透明,无需进行任何代理配置。
接下来,我们将深入探讨透明代理的实现方式。
实现
eBPF 程序设计
在实现中用到了三个 eBPF 程序,每个程序负责不同的网络拦截与转发任务:
- 连接建立时的地址替换:第一个 eBPF 程序 cg_connect4 附加到
connect
系统调用上。当客户端尝试与目标服务器建立连接时,这个程序将目标的 IP 地址和端口替换为 Pipy 代理的地址(通常是本地地址 127.0.0.1)和端口。同时,它将原始的目标地址和端口保存到struct sock
中,并将 socket 的 cookie 与该sock
结构的映射关系保存在map_socks
中,用于之后的查询和数据转发。 - 连接成功后的源地址记录:第二个 eBPF 程序 cg_sock_ops 在连接成功建立后执行,负责记录源地址和端口,并将这些信息更新到
map_socks
里对应的sock
中。此外,它还将源端口和 socket 的 cookie 的映射关系保存在map_ports
中,为后续数据转发提供必要的信息。 - 基于原始目的地信息的连接与转发:第三个 eBPF 程序 cg_sock_opt 触发于 Pipy 通过
getsockopt
查询原始目的地信息的操作。该程序利用源端口从map_ports
中获取 socket 的 cookie,进而从map_socks
中获取原始目的地信息,然后与原始目标建立连接并转发客户端的请求。
eBPF 程序的加载
Pipy 支持 BPF 之后,开发者可以直接在 PipyJS 中处理 BPF 程序,这意味着对 BPF 的操作不再依赖于 bpftool
这类工具,或是需要使用如 BCC
、GoBPF
等其他语言库开发的控制面。这种集成提供了显著的便利性和灵活性,允许在同一个进程中运行 eBPF 控制面和代理逻辑,从而简化了部署和管理。
以下是使用 Pipy 进行 eBPF 操作的关键代码解释,基于 main.js
文件中的示例:
在 PipyJS 中,你可以通过 bpf
模块提供的 API 来加载和操作 eBPF 程序。这里是一种典型的使用方式:
使用 bpf.object()
API 来加载一个编译好的 eBPF 程序对象;这一步骤需要指定 eBPF 程序文件的路径;然后,使用 load() 方法将 eBPF 程序加载到内核中。这一步是必要的,因为只有加载到内核后,eBPF 程序才能开始工作。
var obj = bpf.object(pipy.load('transparent-proxy.o'))
var progCgConnect4 = obj.programs.find(p => p.name === 'cg_connect4').load('BPF_PROG_TYPE_CGROUP_SOCK_ADDR', 'BPF_CGROUP_INET4_CONNECT')
var progCgSockOps = obj.programs.find(p => p.name === 'cg_sock_ops').load('BPF_PROG_TYPE_SOCK_OPS')
var progCgSockOpt = obj.programs.find(p => p.name === 'cg_sock_opt').load('BPF_PROG_TYPE_CGROUP_SOCKOPT', 'BPF_CGROUP_GETSOCKOPT')
将配置写入到 Map map_config
中,这里的配置就是代理所监听的端口。
obj.maps.find(m => m.name === 'map_config').update(
{ i: 0 }, {
proxy_port: PROXY_PORT,
pipy_cgroup_id: bpf.cgroup(CGRP_PIPY)
}
)
接下来就可以将 eBPF 程序,挂载到指定的钩点。
bpf.attach('BPF_CGROUP_INET4_CONNECT', progCgConnect4.fd, CGRP)
bpf.attach('BPF_CGROUP_SOCK_OPS', progCgSockOps.fd, CGRP)
bpf.attach('BPF_CGROUP_GETSOCKOPT', progCgSockOpt.fd, CGRP)
还有,别忘记在代理退出时对 eBPF 程序进行清理。
pipy.exit(
function() {
bpf.detach('BPF_CGROUP_INET4_CONNECT', progCgConnect4.fd, CGRP)
bpf.detach('BPF_CGROUP_SOCK_OPS', progCgSockOps.fd, CGRP)
bpf.detach('BPF_CGROUP_GETSOCKOPT', progCgSockOpt.fd, CGRP)
os.write(`${CGRP}/cgroup.procs`, pipy.pid.toString())
os.rmdir(CGRP_PIPY)
}
)
完成 eBPF 控制面的逻辑后,接着就是代理转发的实现了。
使用 Pipeline API onStart
在与客户端成功连接后,通过 socket.getRawOption
来获取客户端的原始目的地信息,这里就会触发上面提到的第三个 eBPF 程序。
pipy.listen(PROXY_PORT, $=>$
.onStart(
function (ib) {
var od = new Data
ib.socket.getRawOption(SOL_IP, SO_ORIGINAL_DST, od)
var sa = sockaddr_in.decode(od)
var addr = sa.sin_addr
var port = sa.sin_port
$targetAddr = addr.join('.')
$targetPort = (port[0] << 8) | port[1]
}
)
...
总结
这个透明代理示例虽然简单,但它有效地展示了 Pipy 与 eBPF 两种可编程技术的结合力量。尽管离实现理想中的透明代理存在差距——例如,目标服务器看到的是代理地址而非客户端的真实 IP,这一点对于需要记录真实客户 IP 的应用场景尤为关键——但通过 eBPF 技术,我们依然能够达成目标。具体做法是,利用 eBPF 程序调整 Pipy 转发的网络包中的源地址和端口,同时修改返回数据包中的目标地址和端口,以实现对目标服务器的透明性。
与其他技术方案相比,eBPF 在实现透明代理方面提供了无与伦比的灵活性和可编程性。它不仅减少了对内核网络栈处理的依赖,而且大幅提升了性能。
通过将 eBPF 程序的操作集成进 Pipy,我们能够实现应用层与内核层可编程功能的无缝集成,进一步强化了这一解决方案的实用性和效率。
关于 Flomesh
Flomesh(易衡科技)成立于 2018 年,自主研发并开源了高性能可编程代理 Pipy(https://github.com/flomesh-io/pipy)。以 Pipy 为基础,Flomesh 研发了软件负载均衡、服务网格两款软件产品。为工信部认证的可信云产品、可信开源项目。
Flomesh 核心竞争力来自完全自研的核心组件 Pipy,该组件高性能、高可靠、低延迟、可编程、可扩展、低依赖,采用 C++ 开发,内置自研的 JS 引擎,支持适用 JS 脚本做扩展开发。支持包括 x86、arm、龙芯、海光等硬件 CPU 架构;支持 Linux、FreeBSD、macOS、Windows、OpenWrt 等多种核心的操作系统。
Flomesh 成立以来,以技术为根基、以客户为导向,产品被应用在头部股份制商业银行总行、大型保险公司、运营商总部以及研究院等众多客户和多个场景。
- 点赞
- 收藏
- 关注作者
评论(0)