通过 VPN 隧道让内网 Web 机拿到访客真实 IP

发布于 6 天前  8 次阅读


通过 VPN 隧道让内网 Web 机拿到访客真实 IP

公网入口在 转发服务器(iptables DNAT),网站跑在 OpenVPN 客户端(如 10.8.0.195)。目标:Nginx / WordPress 日志里是访客公网 IP,而不是 10.8.0.1


一、结构说明

1.1 三台角色

角色 典型地址 干什么
访客 手机 / 宽带公网 IP 访问 https://域名(解析到服务器公网 IP)
转发服务器 公网 IP + tun0=10.8.0.1 OpenVPN 服务端、DNAT、把流量送进隧道
Web 机 tun0=10.8.0.195 跑 Nginx / WordPress

访客 只和服务器公网 IP 建连,不会直连 10.8.0.195
服务器和 Web 机之间 一律走 OpenVPN(tun0)

1.2 结构示意

                    ┌─────────────────────────────┐
  访客 (公网) ──────►│ 转发服务器                  │
                    │  eth0: 公网 IP  :443       │
                    │       │ DNAT               │
                    │       ▼                    │
                    │  tun0: 10.8.0.1 ───────────┼──► tun0: 10.8.0.195
                    └─────────────────────────────┘         │
                                                            ▼
                                                      Nginx :443

OpenVPN 容器建议 host 模式tun0 和 iptables 都在宿主机上,DNAT 才能直接转发进隧道。


二、原理说明

2.1 入站:怎么看到真实 IP

  1. 访客访问 服务器公网 IP:443
  2. 服务器 PREROUTINGDNAT:改目标地址为 10.8.0.195:443,从 tun0 转发出去
  3. 不做 SNAT(保留真实 IP),到达 Nginx 时源地址仍是 访客公网 IP$remote_addr 正确
# 转发服务器上(示意)
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 \
  -j DNAT --to-destination 10.8.0.195:443
# 保留真实 IP 时:不要 -d 10.8.0.195 --dport 443 -j MASQUERADE

若加了 MASQUERADE,Nginx 只能看到网关 IP,日志不对,但往往 更容易通

2.2 出站:回包为什么要再走隧道

Nginx 回复时:源 10.8.0.195:443,目的 访客公网 IP

  • 访客只认服务器的公网 IP,回包必须 经服务器再从公网出去(服务器侧 MASQUERADE 成公网 IP)
  • 195 → 访客 这一段,包要先 从 tun0 回到 10.8.0.1,不能从 Web 机自己的宽带直接出去(否则路径对不上,握手失败)
入站:访客 ──公网──► 服务器 ──tun0──► 195
出站:195 ──tun0──► 服务器 ──公网 SNAT──► 访客

所以:不是 195 和访客直连,而是 195 ↔ 服务器走隧道,服务器 ↔ 访客走公网

2.3 为啥会「只有 SYN、没有 SYN-ACK」

很多 Web 机还有 国内外 IP 分流(国内 IP 走 eth0,国外走别的出口)。

步骤 实际发生的事
包从 tun0 进来,源 IP = 访客(如手机 112.97.x)
内核查路由:去 112.97.x 应走 eth0(分流)
rp_filter=1:从 tun0 进来,却要从 eth0 回去 → 认为路径非法,丢包
抓包只见访客 SYN 重传,看不到 SYN-ACK

这和防火墙无关(停 firewalld 也可能照样不通)。

解决办法(两件事一起做)

  1. rp_filter=0(至少 tun0)——允许「从隧道进、从隧道回」
  2. 策略路由——从 tun0 进来的 80/443 连接,回包强制走 tun0 → 10.8.0.1CONNMARK + ip rule fwmark + 路由表 100)

2.4 策略路由在干什么(脚本原理)

PREROUTING:从 tun0 进、访问本机 80/443  → 给连接打 CONNMARK
OUTPUT:带该标记的回复包              → MARK → 查表 100 → default via 10.8.0.1 dev tun0

比单纯 OUTPUT --sport 443 更稳:尤其在国内 IP 分流场景下,能绑住「从隧道进来的整条连接」。


三、打个比方(串起来)

  • 访客只找大厦 前台(公网 IP)
  • 前台把事转给 内网工位 195,且 别给访客换马甲(入站不 SNAT)→ 登记本才有真实身份
  • 195 回信必须先交回前台,前台再寄给访客(回包经隧道 + 服务器出公网)
  • 195 若设了 国内信走东门,访客却从 北门(tun0) 进来,回信走错门 → rp_filter 丢信(只有 SYN)

四、配置

4.1 转发服务器

  • net.ipv4.ip_forward = 1
  • 端口转发勾选 保留真实 IPpreserve_real_ip: true
  • 检查:
iptables -t nat -S PREROUTING | grep 443
iptables -t nat -S POSTROUTING | grep 10.8.0.195   # 保留真实 IP 时不应有 443 的 MASQUERADE

4.2 Web 机(195)

chmod +x vpn-port-policy.sh
VPN_GW=10.8.0.1 VPN_DEV=tun0 bash vpn-port-policy.sh apply

等价于:CONNMARK + 路由表 100 + rp_filter=0(持久化在 /etc/sysctl.d/99-vpn-port-policy.conf)。

仅验证 rp_filter 时:

sysctl -w net.ipv4.conf.tun0.rp_filter=0
sysctl -w net.ipv4.conf.all.rp_filter=0

其他端口:PORTS=80,443,8080 VPN_GW=10.8.0.1 VPN_DEV=tun0 bash vpn-port-policy.sh apply


五、验证

手机 4G 访问(勿用与 195 同出口 WiFi),在 195 上:

tcpdump -ni any port 443 -c 10    # 必须同时有 [S] 和 [S.]
tail -f /var/log/nginx/access.log # $remote_addr 应为手机公网 IP

六、不通时

现象 原因 处理
只有 SYN rp_filter + 分流 rp_filter=0,执行 vpn-port-policy.sh apply
能通 IP 不对 做了 SNAT 开保留真实 IP,去掉对 195 端口的 MASQUERADE
有 CDN 源站只见 CDN IP CDN 开传递真实 IP 或读 X-Forwarded-For

七、Windows Web 机(可选)

rp_filter,分流 + VPN 时可用 vpn-default-route.ps1 开启 WeakHostReceive/Send(类似允许非对称路由):

.\vpn-default-route.ps1 -Mode Apply -VpnIfAlias "OpenVPN" -VpnGateway "10.8.0.1"

三句话总结

  1. 入站:DNAT 进隧道,且 不要 对 195 做 SNAT → Nginx 见真实 IP
  2. 出站:195 回包 必须经 tun0 回服务器,再由服务器出公网
  3. Web 机rp_filter=0 + 策略路由(vpn-port-policy.sh