无公网 IP 访问内网的第一百零一种方法 - IPv4 TCP 打洞

 

是的, 你没有看错, 我也没有写错, 家庭宽带 IPv4 的 TCP 也是可以打洞的… 如果是我火星请原谅我. Anyway, 分享如下.

为了在浪急风高, 晦暗莫测的公网建立起 笔记本 -> HomeLab 快速安全的通道, 我已经研究过很多很多种方法了… 简要列举如下:

  • 申请公网 IPv4

    放弃: 我是移动用户, 没 IPv4

  • 使用公网 IPv6 + DDNS

    目前正作为备用方案, 但很多地方 IPv6 支持+连通性仍堪忧

  • 使用自己的中转服务器 / 公共的中转服务器

    放弃: 带宽受限, 国内带宽价格过高, 国外中转要过墙

  • 白嫖群晖的 QuickConnect 中转

    目前正作为备用方案, 延迟较高, 带宽仍受限(~30Mbps)

  • ZerotierTailscale

    放弃: UDP 打洞虽然能通且延迟相对较低, 但容易被运营商 QoS, 带宽难以长时间跑满, 而且 Tailscale 客户端在 Windows 配置不太优雅

  • …还有更多不太有普适性的离谱方案(比如蹭学校 VPN), 不再赘述

最终我现在正在使用的方案, 正如标题所说是 IPv4 TCP 打洞.

不过与我之前有关 IPv6 TCP 打洞 的文章不同, IPv6 本身就是公网 IP, 之前的打洞只是骗过了 OpenWRT 的防火墙. 这次的 v4 下的 TCP 打洞是真正打穿了 CGNAT, 暴露了公开的, 可直接访问的端口.

具体地, 我是用的实现是 natmap, 这个工具可以帮助我们实现在公网 IP 上直接暴露一个局域网的端口.

原理就是部分 ISP 侧的路由器建立 TCP 的 NAT 映射后, 也不会追踪 TCP 的状态或拒绝外来的 SYN 数据包, 所以可以按照以下流程实现暴露公网端口.

  1. 在路由器某端口发起对某外部 IP 的长连接, 假设本地 100.64.1.1:1234 访问 www.qq.com:443
  2. 该连接被保持, 所以对应的 NAT 映射也会被保留, 假设本地 100.64.1.1:1234 被映射到了公网上的 1.2.3.4:40001
  3. …且要求这个映射与关系只与源端口有关, 所以我们作为客户端可以去访问某个 stunserver 获得我们映射到公网的 IP 和端口
  4. 从外部访问 1.2.3.4:40001所有数据包都能被转发100.64.1.1:1234, 此处可以认为是 TCP Fullcone (据我简单测试, 很多地区运营商都能得到这种 Fullcone, 可以用 Natter 测试)
  5. 上述即实现了将 100.64.1.1:1234 的内网端口公开暴露到公网 1.2.3.4:40001 端口, 且我们能通过 stunserver 知道了 1.2.3.4:40001 这个地址+端口

实现了暴露端口, 就可以实现从公网访问内网了. 比如开一个 SSH 服务器进行 dynamic forwarding 或者假设基于 TCP 的 VPN 都是可行的.

不过目前还有两个小问题:

  1. 端口映射不可控, 公网 IP 和 NAT 后的端口都是不可控的
  2. 只有路由器有 100.64.1.1 这个我们拿到的 NAT 过后的 IP, 路由器下的所有机器均经过二次 NAT, 这种方法不能打穿多层 NAT, 但在路由器上操作打洞较为繁琐, 如果用的是傻瓜路由还不能直接操作 (还容易把路由器搞炸)

解决问题 1 natmap 作者提出了一种非标准&自定义的 IP4P 地址, 不过我个人不太喜欢, 我使用了 DDNS 更新 SRV 记录来解决该问题. 自己写个脚本更新域名的 SRV 记录, 更新为当前 natmap 程序获取的公网 IP 和端口即可. 虽然 SSH 客户端不支持 SRV 记录, 但可以使用简单的 shell 脚本对 SSH 进行包装或使用网上其他 sshsrv 实现.

解决问题 2 可以使用 DMZ 主机功能, 将路由器所有 TCP 端口均转发至内网某主机, 然后在内网主机上运行 natmap, 这样路由器的二次 NAT 对打洞不影响, OpenWRT 或者品牌傻瓜路由均可操作.


据我目前使用两个月的体验, TCP 连接可以跑满双向的最大带宽, 且映射不会频繁变动, 不重新拨号一般可以一个月不变动, 连接稳定, 使用体验应该算是没有公网 IP 的情况下的最优解了, 体验接近公网 IP (除了不能自定义暴露的端口)

本文采用 CC BY-NC-SA 4.0 许可协议发布.

作者: lyc8503, 文章链接: https://blog.lyc8503.net/post/ipv4-tcp-hole-punching/
如果本文给你带来了帮助或让你觉得有趣, 可以考虑赞助我¬_¬