通过 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
- 访客访问
服务器公网 IP:443 - 服务器
PREROUTING做 DNAT:改目标地址为10.8.0.195:443,从tun0转发出去 - 若 不做 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 也可能照样不通)。
解决办法(两件事一起做):
rp_filter=0(至少tun0)——允许「从隧道进、从隧道回」- 策略路由——从 tun0 进来的 80/443 连接,回包强制走
tun0 → 10.8.0.1(CONNMARK+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- 端口转发勾选 保留真实 IP(
preserve_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"
三句话总结:
- 入站:DNAT 进隧道,且 不要 对 195 做 SNAT → Nginx 见真实 IP
- 出站:195 回包 必须经 tun0 回服务器,再由服务器出公网
- Web 机:
rp_filter=0+ 策略路由(vpn-port-policy.sh)

