背景
透明代理
透明代理是一种网络中间件,它能够在用户不知情的情况下拦截和转发网络流量。与传统代理不同,透明代理不需要在用户端配置特定的代理设置,而是通过在网络层面拦截流量来实现代理功能。透明代理通常被用于网络管理、安全策略实施、流量监控和优化等方面。它可以用于实现诸如内容过滤、缓存加速、流量控制、负载均衡等功能。常见的 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
查询原始