症状
同一台 NAS、家庭服务或受授权的工作室服务,在蜂窝网络或外部网络能通过 service.example 访问;连接到家庭 LAN 后,仍解析到公网地址但连接超时、返回错误服务,或页面能打开而上传和回调失败。把设备 IP 直接填进浏览器偶尔“能用”,却不能解释同一名称在不同网络的差异。
这通常不是“公网端口映射坏了”的充分证据。内网客户端访问公网地址时,报文可能需要网关在 LAN 侧做 NAT hairpin(也常被称为 NAT loopback);也可能应该由内部 DNS 将名称回答为内部地址。RFC 4787 将 hairpinning 列为 NAT 的一个行为要求,但它不规定任何路由器品牌的界面开关、也不替代服务自身的访问控制。
判断层级
按 名称回答 → 客户端下一跳 → 网关的 NAT/防火墙状态 → 服务实际监听地址 → 回程路径 逐层确认。不要在一次测试里同时新增端口转发、静态路由和 hosts 条目;那会让后续无法判断问题是被修好还是被掩盖。
先把测试分成两个对照:外部网络访问同一完整域名,以及内部 LAN 访问同一完整域名。若外部失败,应先处理 DNS、上游地址或入站发布;只有外部稳定而内部失败,才进入 hairpin/DNS 分支。
只读诊断命令
以下命令只观察名称、路由和监听状态。<public-name>、<lan-service-ip> 只替换为自己管理或明确获授权的资源,输出中不要保存家庭公网地址、令牌或请求正文。
# 在 LAN 客户端:比较名称结果与到该结果的路由
nslookup <public-name>
ip route get <resolved-address>
# OpenWrt/Linux 网关:确认转发/NAT 计数器是否随一次测试增长
nft list ruleset | sed -n '/table ip nat/,/}/p'
conntrack -L 2>/dev/null | grep -F '<lan-service-ip>'
# 服务主机:确认它在预期 LAN 地址和端口监听
ss -lntup
ip route get <lan-client-ip>
conntrack 或 nft 不存在时,不要安装工具来完成本页;先用现有防火墙状态页或日志做同样的只读观察。单次连接没有命中 NAT 计数器,说明还需确认客户端到底是否向该网关发送了流量,而不是可以直接推断为 NAT 不支持 hairpin。
判断分支
- LAN 与 WAN 得到不同的名称回答:这是 split-horizon DNS 或本地覆盖的设计,应检查该覆盖是否指向正确的内部服务地址和证书名称,而非强行改 NAT。
- LAN 和 WAN 都回答公网地址,LAN 路由经主网关,外部正常而内部失败:检查网关是否支持并已为这一条端口映射建立 hairpin 的 DNAT/SNAT 回程;必须同时看正向和回程,而不是只看端口转发条目存在。
- 服务主机收到请求却将响应交给另一台默认网关:这是非对称回程。先恢复服务主机到 LAN 客户端的预期路由,避免以额外 MASQUERADE 隐藏地址规划问题。
- 内部 DNS 指向私网地址后仍失败:回到服务监听、防火墙区域或证书 SNI;DNS 绕行只改变目标地址,不能自动开放服务端口。
最小修复
对单一名称选定一种明确策略:由获得授权的局域网 DNS 返回服务的 LAN 地址,或在边界网关上为该已有服务启用、验证 hairpin 行为。两者都应有回滚点,并且不要为“让内网也能打开”而把服务直接暴露到所有 VLAN。
若采用内部 DNS,需确认 TLS 证书中的名称仍匹配;若采用 hairpin,需在网关变更前导出原有防火墙配置,并限制测试到一个 LAN 客户端和一个服务端口。Wi-Fi 信号、网线故障和 NAS CPU 饱和会造成另一类症状,不能归因于 NAT hairpin。
验证
从 LAN、访客 VLAN(若策略允许)和一个外部网络分别使用同一完整域名测试。记录每个网络看到的 DNS 回答类别、请求是否到达服务主机、返回流量是否走回原客户端,以及变更后的防火墙计数器。验证完成后移除临时 hosts 覆盖,防止它在下次 DNS 变更时制造假象。
证据与边界
NAT hairpin 的协议行为背景见 RFC 4787,本页证据由 Research Engine 静态抓取并保存哈希。RFC 不证明任一 ISP、CGNAT、路由器型号或端口映射必然可用;若外部入口受 CGNAT 或运营商限制,应先确认发布条件,不能把问题错误归为局域网 DNS。