在 OpenWrt 上用 HAProxy 实现源 IP 透传:解决内网、公网、本机三场景访问问题
在 OpenWrt 上用 HAProxy 实现源 IP 透传:解决内网、公网、本机三场景访问问题
本文的创作过程有 AI 参与,全文内容由与 AI 对话做为重要依据,经 Claude Sonnet 4.6 撰写润色而成,本博客作者已对 AI 生成内容进行校对,确保与真实情况一致。作者与广大网友一样痛恨 AI 生产垃圾低质内容,但本文不属于此类文章,如果你也有同样的困扰,无论是自己摸索解决,还是 AI 帮你解答的时候完全可以参考。
1. 场景描述
很多极客玩家会在家里搭建 NAS 或服务器,并通过 OpenWrt 路由器进行端口转发,以便在公网访问家庭服务。
我的环境如下:
- NAS IP:
192.168.1.2 - 路由器 IP:
192.168.1.1(OpenWrt 系统) - 需求: 将路由器的
8888端口流量转发到 NAS 的8888端口(服务运行在 NAS 的 Traefik 中),并让 Traefik 能看到访问者的真实 IP。
2. 问题分析
2.1 传统端口转发的局限
用 LuCI 配置端口转发后,公网访问没问题,但有两个遗留问题:
问题一:NAT 环回
内网设备通过路由器 WAN IP 访问 8888,访问失败。原因是路由器没有针对内网网段做 hairpin NAT。
问题二:本机访问本机
SSH 登录到路由器后,执行
curl https://your-ddns-domain:8888,无法访问到 NAS 的服务。原因是端口转发规则挂在prerouting链,而路由器本机发出的流量走output链,不经过prerouting。
2.2 源 IP 丢失问题
更根本的问题是:传统端口转发无法透传真实源 IP。
LuCI 端口转发生成的 NAT reflection 规则会对内网流量做 SNAT,把源 IP 改成路由器 IP(192.168.1.1),导致 Traefik 永远看不到内网请求的真实 IP。这是结构性限制,无法通过调整防火墙规则解决。
3. 解决方案:HAProxy + PROXY Protocol
用 HAProxy 替代端口转发,通过 PROXY Protocol v2 把真实源 IP 传递给 Traefik。
3.1 整体架构
公网用户 ──→ DDNS DOMAIN:8888 (pppoe-wan) ──→ HAProxy ──→ NAS:8888 (Traefik)
内网用户 ──→ DDNS DOMAIN:8888 ──→ HAProxy ──→ NAS:8888 (Traefik)
路由器本机 ──→ DDNS DOMAIN:8888 ──→ HAProxy ──→ NAS:8888 (Traefik)
三种来源都经过 HAProxy,PROXY Protocol 统一透传源 IP 给 Traefik。
4. 配置步骤
4.1 安装 HAProxy
opkg update && opkg install haproxy
4.2 配置 HAProxy
编辑 /etc/haproxy.cfg:
global
maxconn 1024
defaults
mode tcp
timeout connect 5s
timeout client 30s
timeout server 30s
frontend https_in
bind *:8888
default_backend traefik
backend traefik
server traefik 192.168.1.2:8888 send-proxy-v2
send-proxy-v2 会在每个连接建立时,把真实源 IP 封装进 PROXY Protocol v2 头部发给 Traefik。
4.3 配置 Traefik 信任 PROXY Protocol
在 Traefik 的启动参数中添加:
- --entrypoints.websecure.address=:8888
- --entrypoints.websecure.proxyProtocol.trustedIPs=192.168.1.1
或者在测试阶段临时信任所有来源:
- --entrypoints.websecure.proxyProtocol.insecure=true
确认正常后再改回指定 IP 的方式。
4.4 删除 LuCI 中的端口转发规则
在 LuCI → 网络 → 防火墙 → 端口转发中,删除 8888 端口的转发规则。删除后 dstnat、srcnat_lan、output_custom 里对应的附属规则会自动消失。
4.5 配置 nftables 规则
创建 /etc/nftables.d/10-haproxy-redirect.nft,让三种来源的流量都正确导入 HAProxy:
chain haproxy_prerouting {
type nat hook prerouting priority dstnat - 1; policy accept;
# 公网流量:从 WAN 接口进来的 TCP 8888,DNAT 到路由器内网 IP 上的 HAProxy
iifname "pppoe-wan" meta l4proto tcp th dport 8888 dnat ip to 192.168.1.1:8888
}
chain haproxy_output {
type nat hook output priority dstnat - 1; policy accept;
# 本机流量:目标是本机任意 IP 的 TCP 8888,DNAT 到 HAProxy
fib daddr type local meta l4proto tcp th dport 8888 dnat ip to 192.168.1.1:8888
}
chain haproxy_input {
type filter hook input priority filter - 1; policy accept;
# 放行 8888 端口,避免被 fw4 默认 drop 规则拦截
tcp dport 8888 accept
}
关于 fib daddr type local
这是 nftables 的 FIB(Forwarding Information Base)查询,让防火墙自己判断目标地址是否属于本机,而不需要写死 IP:
- 目标是
127.0.0.1→ 匹配 - 目标是
192.168.1.1(路由器 LAN IP) → 匹配 - 目标是路由器 WAN IP → 匹配
- 目标是
192.168.1.2(NAS IP) → 不匹配
注意:fib daddr type local 在 prerouting 阶段对 pppoe-wan 进来的流量判断不可靠(PPPoE 接口地址识别时机问题),所以公网流量改用 iifname "pppoe-wan" 直接匹配接口。output 链没有这个问题,可以正常使用 fib daddr type local。
关于内网 NAT 环回
内网设备访问路由器 WAN IP:8888 时,走的是 LuCI 的 NAT reflection 机制(dstnat_lan + srcnat_lan)。删除端口转发规则后这条路径消失,但 haproxy_prerouting 中的 fib daddr type local 会捕获这部分流量(目标是路由器 WAN IP,属于 local),转到 HAProxy,同样可以正确处理。
4.6 生效
fw4 restart
service haproxy restart
5. 验证
启动后分别测试三种访问路径:
# 公网,内网,路由器本机执行以下测试命令,都可以通过 DDNS 域名正常返回结果,也可以用其他方法测试。
curl https://your-ddns-domain:8888
在 Traefik 的日志或服务端看到的 IP,应该分别是真实的公网 IP、内网 IP,而不是路由器 IP 192.168.1.1。
6. 总结
| 方案 | 公网访问 | 内网访问 | 本机访问 | 源 IP 透传 |
|---|---|---|---|---|
| 传统端口转发 | ✅ | ✅(需开启 NAT 环回) | ✅(需 output 规则) | ❌ 内网永远显示路由器 IP |
| HAProxy + PROXY Protocol | ✅ | ✅ | ✅ | ✅ 所有来源真实 IP |
HAProxy 在 OpenWrt 上资源占用极低(~2MB),配置简单,是 homelab 场景下解决源 IP 透传问题的最优方案。

浙公网安备 33010602011771号