在 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 端口的转发规则。删除后 dstnatsrcnat_lanoutput_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 localprerouting 阶段对 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 透传问题的最优方案。

posted @ 2026-03-04 21:21  zhuxiaoxi  阅读(45)  评论(0)    收藏  举报