junkurihara/rust-rpxy-l4

GitHub: junkurihara/rust-rpxy-l4

一个用 Rust 编写的高性能四层反向代理,支持在单端口上对多种 TCP/UDP 协议进行多路复用和智能路由。

Stars: 115 | Forks: 7

# rpxy-l4:一个用 Rust 编写的、带有协议多路复用器的四层(TCP+UDP)反向代理 [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) ![单元测试](https://static.pigsec.cn/wp-content/uploads/repos/cas/26/269c25936b9dc27f3854a08a4b0359a281bd914cc90cd4126da6a9b900d8a69b.svg) ![容器构建](https://static.pigsec.cn/wp-content/uploads/repos/cas/4d/4d2c3529e45f1521aaf1dadb936bc75b7a37a97d4c5ad7dc22e369d777da60f0.svg) ![发布](https://static.pigsec.cn/wp-content/uploads/repos/cas/42/42ba98a60a0bb3b0ad908f024db145f9c5b831eb7df822f56ac578ee7d7215b3.svg) [![Docker 镜像大小 (最新)](https://img.shields.io/docker/image-size/jqtype/rpxy-l4)](https://hub.docker.com/r/jqtype/rpxy-l4) ## 简介 `rpxy-l4` 是一个支持 TCP 和 UDP 协议的四层反向代理,其设计理念与 [`rpxy`](https://github.com/junkurihara/rust-rpxy)(HTTP 反向代理)相同。它使用 Rust 编写,旨在为四层协议提供高性能且易于使用的反向代理。 ## 功能 - **基础的 L4 反向代理功能**:`rpxy-l4` 可以将 TCP 和 UDP 数据包转发到后端服务器。 - **协议多路复用**:`rpxy-l4` 可以在同一个端口上对 TCP/UDP 上的多个协议进行多路复用,这意味着 `rpxy-l4` 会将特定协议路由到相应的后端服务器。目前支持以下协议: - TCP:HTTP(明文)、TLS、SSH、Socks5 - UDP:QUIC(IETF QUIC[^quic])、WireGuard - **负载均衡**:`rpxy-l4` 可以基于几种简单的负载均衡算法,将传入的连接分发到多个后端服务器。 - **协议净化**:利用协议多路复用特性,`rpxy-l4` 可以对传入的数据包进行净化,通过禁止默认路由,来防止客户端和后端服务器之间的 TCP/UDP 上的协议不匹配。(直接丢弃不符合预期协议的数据包)。 - **TLS/QUIC 转发器**:`rpxy-l4` 可以根据 Server Name Indication (SNI) 和 Application Layer Protocol Negotiation (ALPN) 的值,将 TLS/IETF QUIC 流转发到相应的后端服务器。 - **支持 HAProxy PROXY protocol**:`rpxy-l4` 支持 TCP 的出站 PROXY protocol(在后端连接前添加 header,全局/按协议配置)和入站 PROXY protocol(解析来自受信任上游代理的 header)。(需要启用 `proxy-protocol` Cargo 特性,该特性默认已启用。) - **[实验性] TLS Encrypted Client Hello (ECH) 代理**:`rpxy-l4` 可以作为代理[^ech_proxy] 来处理带有 IETF-Draft Encrypted Client Hello 的 TLS/QUIC 流。换言之,`rpxy-l4` 会托管 ECH 私钥并解密经过 ECH 加密的 Client Hello,从而将流路由到对应的后端服务器。 [^quic]: 不是 Google QUIC。同时支持 QUIC v1([RFC9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC9001](https://datatracker.ietf.org/doc/html/rfc9001)) 和 QUIC v2 ([RFC9369](https://datatracker.ietf.org/doc/html/rfc9369))。 [^ech_proxy]: 在 [ECH Split Mode](https://www.ietf.org/archive/id/draft-ietf-tls-esni-24.html#section-3) 语境下面向客户端的服务器 ## 安装说明 ### 从源码构建 你可以通过检出这个 Git 仓库来自己构建可执行二进制文件。 ``` # 克隆 git repository % git clone https://github.com/junkurihara/rust-rpxy-l4 % cd rust-rpxy-l4 # 构建 % cargo build --release ``` 然后你会得到一个可执行二进制文件 `rust-rpxy-l4/target/release/rpxy-l4`。 如果要在不包含 PROXY protocol 特性的情况下构建: ``` % cargo build --release --no-default-features ``` ### Linux 包安装(RPM/DEB) 你可以在 [./.build](./.build) 目录中找到 `rpxy-l4` 的 Jenkins CI/CD 构建脚本。 由 [@Gamerboy59](https://github.com/Gamerboy59) 提供的预编译 Linux RPM 和 DEB 软件包可在 [https://rpxy.gamerboy59.dev](https://rpxy.gamerboy59.dev) 获取。 ## 用法 `rpxy-l4` 总是会引用一个 TOML 格式的配置文件,例如 `config.toml`。你可以在这个仓库中找到配置文件的示例 `config.example.toml`。 你可以像下面这样使用配置文件运行 `rpxy-l4` ``` % ./target/release/rpxy-l4 --config config.toml ``` `rpxy-l4` 始终以实时方式跟踪 `config.toml` 的变更,并立即应用更改而无需重启进程。 完整的命令行选项如下: ``` % ./target/release/rpxy-l4 --help Usage: rpxy-l4 [OPTIONS] --config Options: -c, --config Configuration file path like ./config.toml -l, --log-dir Directory for log files. If not specified, logs are printed to stdout. -h, --help Print help -V, --version Print version ``` 如果你设置了 `--log-dir=`,日志文件将在指定的目录中创建。否则,日志将输出到 stdout。 - `${log_dir}/access.log` 用于访问日志 - `${log_dir}/rpxy-l4.log` 用于系统和错误日志 ## 基本配置 ### 1. 第一步:基础的 TCP/UDP 反向代理场景 以下是 TCP/UDP 反向代理场景的基础配置示例。 ``` # Listen port,必须设置 listen_port = 8448 # TCP 连接的默认目标。[default: empty] # Format: [":", ":", ...] tcp_target = ["192.168.0.2:8000"] # UDP 连接的默认目标。[default: empty] # Format: [":", ":", ...] udp_target = ["192.168.0.3:4000"] ``` 上述配置的工作方式如下。 - 将端口 `8448` 上接收到的 TCP 数据包转发到后端服务器 `192.168.0.2:8000`; - 将端口 `8448` 上接收到的 UDP 数据包转发到后端服务器 `192.168.0.3:4000`。 ### 2. 第二步:负载均衡 `rpxy-l4` 允许你基于几种简单的负载均衡算法,将传入的 TCP/UDP 数据包分发到多个后端服务器。对于多个 TCP/UDP 目标,你可以如下设置负载均衡算法。 ``` # Listen port,必须设置 listen_port = 8448 # TCP 连接的默认目标。[default: empty] # Format: [":", ":", ...] tcp_target = ["192.168.0.2:8000", "192.168.0.3:8000"] # 默认目标的 load balancing method [default: none] tcp_load_balance = "source_ip" # source_ip, source_socket, random, or none # UDP 连接的默认目标。[default: empty] # Format: [":", ":", ...] udp_target = ["192.168.0.2:4000", "192.168.0.3:4000"] # (可选)默认目标的 load balancing method [default: none] udp_load_balance = "source_socket" ``` 目前,`rpxy-l4` 支持以下负载均衡算法: - `source_ip`:基于源 IP 哈希 - `source_socket`:基于源 IP 和端口哈希 - `random`:随机选择 - `none`:始终使用第一个目标 [默认] ### 3. 第三步:协议多路复用 以下是 TCP/UDP 上协议多路复用场景的示例/用例。对于协议多路复用,你需要在配置文件中设置 `[protocols.]` 字段,如下所示。 ``` listen_port = 8448 ... # 为每个 multiplexed service 设置 [protocols."http_service"] ... ``` 目前,`rpxy-l4` 支持以下用于多路复用的协议: - TCP:HTTP(明文)、TLS、SSH、Socks5 - UDP:QUIC(IETF [RFC9000](https://datatracker.ietf.org/doc/html/rfc9000))、WireGuard #### 3.1. 带有 SNI/ALPN 的 TLS/QUIC 多路复用器示例 `rpxy-l4` 可以通过探测 TLS ClientHello 消息和 IETF QUIC Initial 数据包(包含 ClientHello)来检测并对 TLS/QUIC 流进行多路复用。以下示例演示了将任何 TLS/QUIC 流转发到与默认目标不同的相应后端的场景。 ``` listen_port = 8448 tcp_target = ["192.168.0.2:8000"] udp_target = ["192.168.0.3:4000"] # TLS [protocols."tls_service"] # protocol 名称 tls|ssh|socks5|http|wireguard|quic protocol = "tls" # 检测为 TLS 的连接的目标。 target = ["192.168.0.5:443"] # (可选)特定于此连接的 load balancing method [default: none] load_balance = "source_ip" ##################### # IETF QUIC [protocols."quic_service"] # protocol 名称 tls|ssh|socks5|http|wireguard|quic protocol = "quic" # 检测为 QUIC 的连接的目标。 target = ["192.168.0.6:443"] # QUIC 连接的 load balancing method [default: none] load_balance = "source_socket" # QUIC 连接的空闲存活时间(秒) [default: 30] idle_lifetime = 30 ``` 此外,你可以在 `protocol="tls"` 或 `protocol="quic"` 的情况下设置 `tls_alpn` 和 `tls_sni` 字段。这些是 TLS/QUIC 多路复用器的附加过滤器,用于根据 Application Layer Protocol Negotiation (ALPN) 和 Server Name Indication (SNI) 的值将流路由到相应的后端服务器。这意味着只有带有指定 ALPN 和 SNI 值的流才会被转发到目标。 ``` [protocols."tls_service"] protocol = "tls" target = ["192.168.0.5:443"] load_balance = "source_ip" # (可选)基于 SNI 的 TLS/QUIC 连接路由。 # 如果指定,则只有匹配给定 SNI 的 TLS/QUIC 连接才会被转发到目标。 # Format: ["", "", ...] server_names = ["example.com", "example.org"] # (可选)基于 ALPN 的 TLS/QUIC 连接路由。 # 如果指定,则只有匹配给定 ALPN 的 TLS/QUIC 连接才会被转发到目标。 # Format: ["", "", ...] alpns = ["h2", "http/1.1"] ``` #### 3.2. WireGuard 多路复用器示例 `rpxy-l4` 可以通过探测初始握手数据包来检测并对 WireGuard 数据包进行多路复用。以下示例同样演示了将任何 WireGuard 数据包转发到与默认目标不同的相应后端的场景。 ``` [protocols."wireguard_service"] protocol = "wireguard" target = ["192.168.0.10:51820"] load_balance = "none" # 长于 wireguard tunnel 的 keepalive interval idle_lifetime = 30 ``` #### 3.3. 仅放行预期协议(协议净化) 这在某种程度上是一项安全功能,旨在防止客户端和后端服务器之间在 TCP/UDP 上的协议不匹配。*通过忽略默认路由*,即移除顶层的 `tcp_target` 和 `udp_target`,并仅设置特定的协议多路复用器,`rpxy-l4` 将只处理匹配预期协议的数据包并丢弃其他数据包。 ### 4. PROXY protocol(保留客户端 IP) `rpxy-l4` 支持 TCP 连接的 [HAProxy PROXY protocol](https://www.haproxy.org/download/2.9/doc/proxy-protocol.txt)(v1 和 v2)。这使得当 `rpxy-l4` 处于代理链中时能够保留客户端 IP. #### 4.1. 出站:向后台服务器发送 PROXY header 在发往后端服务器的连接前添加一个 PROXY protocol header,以便后端可以看到原始客户端 IP。 **全局设置** — 适用于所有 TCP 后端连接: ``` listen_port = 8448 tcp_target = ["192.168.0.2:8000"] # 为所有 TCP backend 连接预置 PROXY protocol v2 header tcp_send_proxy_protocol = "v2" # "v1", "v2", or omit/"none" to disable ``` **按协议覆盖** — 每个协议条目都可以覆盖全局设置: ``` tcp_send_proxy_protocol = "v2" # global default [protocols."tls_1"] protocol = "tls" target = ["192.168.0.5:443"] send_proxy_protocol = "v1" # override: use v1 for this protocol [protocols."http_1"] protocol = "http" target = ["192.168.0.6:80"] send_proxy_protocol = "none" # override: disable for this protocol ``` #### 4.2. 入站:从上游代理解析 PROXY header 当 `rpxy-l4` 位于发送 PROXY protocol 的负载均衡器或代理(例如 AWS NLB、HAProxy)之后时,启用入站解析以从 header 中提取原始客户端 IP。 ``` listen_port = 8448 tcp_target = ["192.168.0.2:8000"] # 在每个 TCP 连接上预期入站 PROXY header tcp_recv_proxy_protocol = true # 受信任且允许发送 PROXY header 的来源(启用 recv 时必填) tcp_trusted_proxies = ["10.0.0.0/8", "192.168.0.0/16"] ``` - `tcp_recv_proxy_protocol = true` 要求**所有**传入的 TCP 连接都以 PROXY header 开头。没有有效 header 的连接将被拒绝。 - 启用接收(recv)时,`tcp_trusted_proxies` 是**强制配置项**。来自不受信任源 IP 的连接将被拒绝。如果此字段缺失或为空,启动验证将失败。 - 自动检测 v1 和 v2 header —— 无需进行版本配置。 - 接受 LOCAL (v2) / UNKNOWN (v1) 命令(例如健康检查),而不修改源地址。 #### 4.3. 端到端客户端 IP 保留 当入站和出站同时启用时,`rpxy-l4` 即可通过代理链实现端到端的客户端 IP 保留: ``` Client → [LB with PROXY protocol] → rpxy-l4 (inbound parse → outbound send) → Backend ``` 入站解析器提取原始客户端 IP,出站编码器将其转发给后端。除了同时启用这两者之外,不需要任何特殊配置。 ### 5. 进阶:实验性功能 #### 5.1. TLS Encrypted Client Hello (ECH) 代理 有关 ECH 代理配置以及客户端和后端服务器的示例,请参阅 [./examples/README.md](./examples/README.md)。 ## 容器化 容器、docker 镜像可在 Docker Hub 和 Github Container Registry 上获取。 - Docker Hub: [jqtype/rpxy-l4](https://hub.docker.com/r/jqtype/rpxy-l4) - Github Container Registry: [ghcr.io/junkurihara/rust-rpxy-l4](https://ghcr.io/junkurihara/rust-rpxy-l4) 有关容器的详细配置可以在 [./docker](./docker) 目录中找到。 ## 注意事项 ### `UDP` 伪连接管理 如前所述,`rpxy-l4` 基于套接字地址管理来自每个客户端的 UDP 数据包的伪连接。同时,`rpxy-l4` 通过探测初始/握手数据包来识别特定的协议。这意味着如果伪连接的空闲生存时间太短,且客户端发送数据包的间隔较长,那么即使在通信期间,伪连接也会被移除。随后,来自客户端的数据包(即非初始/握手数据包)将*不会被路由到特定协议的目标,而是被路由到默认目标(如果没有默认目标则会被丢弃)*。为了避免这种情况,你应当将基于 UDP 的协议多路复用器的 `idle_lifetime` 值设置为长于客户端发送数据包的间隔。 ### Encrypted Client Hello (ECH) 代理 #### 功能受限 *目前我们并未完全实现 [IETF draft](https://www.ietf.org/archive/id/draft-ietf-tls-esni-24.html#section-7.1) 中描述的面向客户端的服务器功能。* 它以下述*简化和缩减*的方式运行,这与草案不同: - 如果找不到与给定 ECH 匹配的配置,它将按原样将 client hello 直接转发给后端服务器。 - `rpxy-l4` 不支持面向客户端的服务器的重试机制,即它目前没有关于 ECH 请求的状态,并且不会处理、转发或发送 `HelloRetryRequest` 消息给客户端。 #### 不支持 ECH over QUIC ECH 代理功能仅限于 TLS 协议,目前尚不支持 ECH over QUIC。 ### 其他 待定! ## 许可证 `rpxy-l4` 是免费的开源软件,基于 MIT License 授权。 ## 安全 如果你发现了安全漏洞,**请不要公开 Issue**。 请使用 [GitHub 私密漏洞报告](../../security/advisories/new) 来通知维护者。 ## 贡献 欢迎贡献(issues、功能请求、bug 报告、pull requests)。 请注意,本项目主要基于代码所有者的个人兴趣进行维护,不受任何商业协议的支持。 我们会在能力范围内尽力处理贡献。同时也欢迎赞助,以帮助维持该项目的运转。 有关贡献指南和项目范围的更多详细信息,请参阅 [CONTRIBUTING.md](./CONTRIBUTING.md)。
标签:Python安全, Rust, TCP, UDP, 内核驱动, 协议复用, 反向代理, 可视化界面, 底层编程, 网络协议, 网络流量审计, 请求拦截, 负载均衡, 通知系统