中兴 G7615V2 Hairpin NAT 排查记录
太阳作者太阳
原创内容采用 CC-4.0 协议发布,转载请注明出处
光猫NATHairpin NAT端口映射网络排障

中兴 G7615V2 Hairpin NAT 排查记录

问题背景

内网服务器通过光猫做端口映射后,外网手机流量访问公网域名和端口可以成功,但同一局域网 Wi-Fi 下访问同一个公网域名和端口失败。这个现象不是服务监听问题,也不是普通公网端口映射失败,而是典型的 Hairpin NAT 缺失。

Hairpin NAT 也常被叫作:

NAT Loopback
NAT Reflection
NAT Hairpinning
内网回流
NAT 回环
NAT 回流

它解决的是这条路径:

内网客户端 -> 光猫公网 IP:端口 -> 回流到内网服务器

而普通端口映射或 DMZ 主要解决的是:

公网客户端 -> 光猫 WAN -> 端口映射/DMZ -> 内网服务器

这两类路径不同,所以手机流量能访问并不代表 Wi-Fi 下访问公网域名也能访问。

现象和初步判断

外网测试中,部分端口返回 Connection refused,部分端口成功。服务端主机上查看监听端口:

ss -lntp | grep -E '5666|5667|3000[2-5]'

关键现象是主机确实监听了相关端口:

0.0.0.0:5666
0.0.0.0:5667
0.0.0.0:30002
0.0.0.0:30004
0.0.0.0:30005

这排除了“服务没有启动”这一类根因。后续通过局域网访问内网 IP 可以访问,手机流量访问公网域名也可以访问,进一步把问题收敛到内网访问公网地址的回流路径。

Windows 侧可用以下命令区分内网直连和公网域名访问:

$ports = 30002,30004,30005,5666,5667

$ports | ForEach-Object {
  [PSCustomObject]@{
    Port = $_
    PublicDomain = Test-NetConnection example.home.domain -Port $_ -InformationLevel Quiet
    LanIP = Test-NetConnection 192.168.1.71 -Port $_ -InformationLevel Quiet
  }
}

如果 LanIP=TruePublicDomain=False,排查重点应转向 NAT 回环、内网 DNS 覆写或光猫路由行为。

端口范围映射的误区

光猫网页上的端口范围规则容易误解。界面中“广域网起始端口 / 广域网结束端口 / 虚拟主机端口”看起来像:

公网 30000-30100 -> 内网 30000-30100

但实际语义可能是:

公网 30000-30100 -> 内网 30000

也就是整个公网端口范围都转发到同一个内部端口。这会导致访问公网 300023000430005 时,实际都落到内网 30000,如果内网 30000 没有服务,就会出现拒绝连接。

更稳的做法是避免使用端口段规则,改成单端口逐条映射:

公网 30002 -> 内网服务器:30002
公网 30004 -> 内网服务器:30004
公网 30005 -> 内网服务器:30005
公网 5666  -> 内网服务器:5666
公网 5667  -> 内网服务器:5667

G7615V2 的运行时规则证据

进入 G7615V2 的 Telnet 后,系统环境是 Linux/Buildroot:

uname -a
cat /proc/version
ip route

端口映射生成的 NAT 规则类似:

iptables -t nat -S

关键证据是端口映射只对公网接口生效:

-A portmapp -d <WAN_IP>/32 -i ppp0 -p tcp --dport 5666 -j DNAT --to-destination 192.168.1.71:5666
-A portmapp -d <WAN_IP>/32 -i ppp0 -p tcp --dport 5667 -j DNAT --to-destination 192.168.1.71:5667

这里的 -i ppp0 表示规则只处理从公网口进来的流量。局域网 Wi-Fi/LAN 侧的流量从 br0 进入,内网客户端访问公网域名时不会命中这些 DNAT 规则。因此外网可用、内网访问公网域名不可用是符合规则行为的。

DMZ 也是类似问题。DMZ 可以把外网入站流量默认转给某台内网主机,但它不等于 Hairpin NAT。删除端口映射后只启用 DMZ,依然可能存在同一 Wi-Fi 下访问公网域名失败的问题。

中兴 DB 表检查

G7615V2 的中兴配置库可以用 sendcmd 查看。先列出相关表名:

sendcmd 1 DB all | grep -i -E 'port|nat|dmz|fw|wan|map|loop|hair|upnp'

本次重点表包括:

FWPM
FWDMZ
UPnPCfg
UPnPPortMap
LoopbackGlobalConf
LoopbackEthConf
LoopbackVlanConf

端口映射表是 FWPM

sendcmd 1 DB p FWPM

它保存的是外部端口、内部主机、内部端口、协议和 WAN 连接等字段,没有 Hairpin/NAT Loopback 开关:

Enable
Protocol
Name
MinExtPort
MaxExtPort
InternalHost
MinIntPort
MaxIntPort
WANIndex

DMZ 表是 FWDMZ

sendcmd 1 DB p FWDMZ

它保存 DMZ 是否启用、WAN 连接和内部主机地址,也没有 Hairpin 字段。

名字看起来相关的 Loopback* 表实际不是 NAT 回环,而是二层以太网环路检测。判断依据是字段中出现:

DMACAddr
EtherType
VlanTag
PortDownPolicyEnable
PortID
AlarmEn
PortDisableEn
PortDetectionStatus

这些字段用于检测交换网络环路或关闭端口,不处理 公网 IP:端口 -> 内网服务器 的 NAT 回流。

因此,sendcmd 1 DB save 可以保存端口映射、DMZ 等 DB 配置,但不会自动保存手工添加的运行时 iptables Hairpin NAT 规则。

清理 DMZ 的方法

如果网页关闭 DMZ 后仍然残留数据,可以直接清理 FWDMZ 表并刷新运行时链:

sendcmd 1 DB set FWDMZ 0 Enable 0
sendcmd 1 DB set FWDMZ 0 WANCViewName ""
sendcmd 1 DB set FWDMZ 0 WANCName ""
sendcmd 1 DB set FWDMZ 0 InternalHost 0.0.0.0
sendcmd 1 DB set FWDMZ 0 MacEnable 0
sendcmd 1 DB set FWDMZ 0 InternalMacHost 00:00:00:00:00:00
sendcmd 1 DB save

清掉当前运行时 DMZ NAT 链:

iptables -t nat -F dmzmapp

检查:

sendcmd 1 DB p FWDMZ
iptables -t nat -S dmzmapp
iptables -t nat -L dmzmapp -n -v

期望结果是 Enable=0InternalHost=0.0.0.0,并且 dmzmapp 链里没有额外 DNAT 规则。必要时重启光猫,让运行时规则重新从 DB 生成。

临时实现 Hairpin NAT

Hairpin NAT 需要两类规则:

  1. DNAT:内网客户端访问公网 IP:端口时,转到内网服务器。
  2. SNAT/MASQUERADE:让服务器回包经过光猫,避免服务器直接回给内网客户端造成连接状态不匹配。

同端口映射到内网服务器的示例:

WAN_IP="$(ip -4 addr show ppp0 | awk '/inet / {print $2}' | cut -d/ -f1)"

iptables -t nat -I portmapp 1 \
  -i br0 \
  -s 192.168.1.0/24 \
  -d "$WAN_IP/32" \
  -p tcp \
  -m multiport --dports 5666,5667,5005,30002,30004,30005 \
  -j DNAT --to-destination 192.168.1.71

iptables -t nat -I POSTROUTING 1 \
  -o br0 \
  -s 192.168.1.0/24 \
  -d 192.168.1.71/32 \
  -p tcp \
  -m multiport --dports 5666,5667,5005,30002,30004,30005 \
  -j MASQUERADE

外部端口和内部端口不同的规则要单独写。例如外部 SSH 端口映射到内网 22:

iptables -t nat -I portmapp 1 \
  -i br0 \
  -s 192.168.1.0/24 \
  -d "$WAN_IP/32" \
  -p tcp --dport 27122 \
  -j DNAT --to-destination 192.168.1.71:22

iptables -t nat -I POSTROUTING 1 \
  -o br0 \
  -s 192.168.1.0/24 \
  -d 192.168.1.71/32 \
  -p tcp --dport 22 \
  -j MASQUERADE

验证规则是否命中:

iptables -t nat -L portmapp -n -v --line-numbers
iptables -t nat -L POSTROUTING -n -v --line-numbers

如果计数器增长,说明内网访问公网域名已经命中回流规则。

DMZ 全端口回流测试

后续尝试过在 DMZ 模式下不指定端口,直接让内网访问光猫公网 IP 的所有 TCP/UDP 流量回流到 DMZ 主机。测试规则如下:

WAN_IP="$(ip -4 addr show ppp0 | awk '/inet / {print $2}' | cut -d/ -f1)"
LAN_NET="192.168.1.0/24"
DMZ_IP="192.168.1.71"

iptables -t nat -C dmzmapp \
  -i br0 \
  -s "$LAN_NET" \
  -d "$WAN_IP/32" \
  -p tcp \
  -j DNAT --to-destination "$DMZ_IP" 2>/dev/null || \
iptables -t nat -I dmzmapp 1 \
  -i br0 \
  -s "$LAN_NET" \
  -d "$WAN_IP/32" \
  -p tcp \
  -j DNAT --to-destination "$DMZ_IP"

iptables -t nat -C POSTROUTING \
  -o br0 \
  -s "$LAN_NET" \
  -d "$DMZ_IP/32" \
  -p tcp \
  -j MASQUERADE 2>/dev/null || \
iptables -t nat -I POSTROUTING 1 \
  -o br0 \
  -s "$LAN_NET" \
  -d "$DMZ_IP/32" \
  -p tcp \
  -j MASQUERADE

iptables -t nat -C dmzmapp \
  -i br0 \
  -s "$LAN_NET" \
  -d "$WAN_IP/32" \
  -p udp \
  -j DNAT --to-destination "$DMZ_IP" 2>/dev/null || \
iptables -t nat -I dmzmapp 1 \
  -i br0 \
  -s "$LAN_NET" \
  -d "$WAN_IP/32" \
  -p udp \
  -j DNAT --to-destination "$DMZ_IP"

iptables -t nat -C POSTROUTING \
  -o br0 \
  -s "$LAN_NET" \
  -d "$DMZ_IP/32" \
  -p udp \
  -j MASQUERADE 2>/dev/null || \
iptables -t nat -I POSTROUTING 1 \
  -o br0 \
  -s "$LAN_NET" \
  -d "$DMZ_IP/32" \
  -p udp \
  -j MASQUERADE

执行后检查计数器:

iptables -t nat -L dmzmapp -n -v --line-numbers
iptables -t nat -L POSTROUTING -n -v --line-numbers

关键输出特征是:

Chain dmzmapp
DNAT udp -- br0 ... <WAN_IP> to:192.168.1.71  pkts 0
DNAT tcp -- br0 ... <WAN_IP> to:192.168.1.71  pkts 22
DNAT all -- ppp0 ... <WAN_IP> to:192.168.1.71

Chain POSTROUTING
MASQUERADE udp -- * br0 192.168.1.0/24 192.168.1.71  pkts 0
MASQUERADE tcp -- * br0 192.168.1.0/24 192.168.1.71  pkts 0

这个结果说明:内网访问公网 IP 的 TCP 包已经进入 dmzmapp 并命中 br0 入口 DNAT,但没有继续走到 POSTROUTING MASQUERADE。因此失败点不是“没有命中 DNAT”,而是 DNAT 之后没有完成常规转发和源地址转换。

进一步加过 FORWARD 放行规则:

iptables -I FORWARD 1 \
  -i br0 \
  -o br0 \
  -s 192.168.1.0/24 \
  -d 192.168.1.71 \
  -j ACCEPT

iptables -I FORWARD 1 \
  -i br0 \
  -o br0 \
  -s 192.168.1.71 \
  -d 192.168.1.0/24 \
  -m state --state RELATED,ESTABLISHED \
  -j ACCEPT

检查 FORWARD 后,新加的两条规则计数仍为 0:

Chain FORWARD
ACCEPT all -- br0 br0 192.168.1.71   192.168.1.0/24 state RELATED,ESTABLISHED  pkts 0
ACCEPT all -- br0 br0 192.168.1.0/24 192.168.1.71                             pkts 0

因此 FORWARD 不是被规则拒绝后失败,而是这类 DMZ 回流包没有进入普通 br0 -> br0 转发链。对这台 G7615V2 来说,DMZ 模式下的全端口 Hairpin NAT 不能只靠在 dmzmapp 添加 DNAT 和在 POSTROUTING 添加 MASQUERADE 完成。

这个测试不影响一个已确认事实:手机流量从外网访问 DMZ 主机可以成功,公网入口是通的。失败范围只限于同一局域网内访问公网域名/公网 IP 的回流路径。

持久化判断

本机检查到 /etc/init.d/rcS 会挂载 /userconfig,但没有现成的用户脚本钩子:

. /etc/default/S01userconfig

for i in /etc/rcS.d/S??*
do
  ...
done

/userconfig 看起来是持久化分区,适合放脚本;但是否能让启动流程自动执行,还取决于 /etc/rcS.d 是否可写且重启后保留。

可检查:

ls -l /etc/rcS.d
mount
df -h

如果 /etc/rcS.d 可写且持久化,可以考虑:

/userconfig/cfg/hairpin_nat.sh
/etc/rcS.d/S99hairpin

如果 /etc 不持久化,更稳的方案是在内网服务器上用 expect 或其他自动化方式定时 Telnet 登录光猫补规则。这样不改光猫系统文件,重启或重拨后也可以自动恢复。

更稳的替代方案:内网 DNS 覆写

如果目标只是让家里 Wi-Fi 下访问同一个域名,内网 DNS 覆写通常比改光猫 NAT 更稳:

example.home.domain -> 192.168.1.71

这样:

外网访问域名 -> 解析到公网 IP
内网访问域名 -> 解析到内网服务器 IP

可以在路由器、AdGuard Home、Pi-hole、OpenWrt dnsmasq 或 Windows hosts 中配置。

Windows 临时验证:

C:\Windows\System32\drivers\etc\hosts

加入:

192.168.1.71 example.home.domain

刷新 DNS 并测试:

ipconfig /flushdns
Resolve-DnsName example.home.domain
Test-NetConnection example.home.domain -Port 30002

如果解析成内网 IP 后可访问,说明公网映射和服务都没有问题,缺失的是光猫的 NAT 回流能力。

结论

这次排查的核心结论是:中兴 G7615V2 当前网页端口映射和 DMZ 只覆盖公网入口,不自动生成内网回流规则。手机流量可以访问而同 Wi-Fi 访问公网域名失败,是 Hairpin NAT 缺失的典型表现。

FWPMFWDMZ 可以通过 sendcmd 1 DB save 保存,但它们没有 NAT Loopback 字段;Loopback* 表是二层环路检测,不是 NAT 回环。DMZ 全端口回流测试中,dmzmappbr0 DNAT 计数会增长,但 FORWARDPOSTROUTING MASQUERADE 计数不增长,说明这台设备没有按普通 Linux 路由链路完成 DMZ Hairpin NAT。

要解决这个问题,有三条实际路线:

  1. iptables 临时添加 br0 入口 DNAT 和 POSTROUTING MASQUERADE,再通过启动脚本或外部自动化持久化。
  2. 做内网 DNS 覆写,让内网访问域名时直接解析到内网服务器。
  3. 长期上把光猫桥接给更完整的路由器或 OpenWrt/软路由处理拨号、NAT、防火墙和回流。

刷固件不应作为优先解法。运营商光猫固件存在 PON 注册、区域配置、Web/Telnet 权限和救砖风险,而且新固件也不保证补齐 Hairpin NAT。