内网穿透完全指南:从 NAT 原理到方案选型与实战
一、问题的起点:为什么外网访问不到我家这台机器?
1. 你的机器只有一个"小区内部门牌号"
IPv4 地址一共约 43 亿个,早就分完了。为了让几十台设备共享一个公网 IP,NAT(网络地址转换)成了标配。你的机器拿到的是私有地址:
| 地址段 | 常见场景 |
|---|---|
10.0.0.0/8 |
企业内网、运营商分配 |
172.16.0.0/12 |
Docker 默认网桥、部分内网 |
192.168.0.0/16 |
家用路由器(你最熟悉的那个) |
192.168.1.100 这个地址,在你家局域网里独一无二,但全世界有几千万台机器都叫这个名字。公网路由器看到这个目的地址,只会直接丢弃——它根本不知道该往哪儿送。
2. NAT 的核心规则:出去时建表,回来时查表
NAT 网关维护一张转换表。关键在于它是单向触发的:
- 你的机器主动访问外部 → NAT 创建一条映射
(192.168.1.100:54321) ↔ (公网IP:60001),外部回包可以顺着这条映射回来; - 外部主动发一个包进来 → NAT 表里没有对应条目,直接丢弃。
这就是所谓的"未经请求的入站流量会被丢弃"。不是防火墙拦你,是 NAT 压根不知道这个包该给谁。
3. 更糟的情况:CGNAT
很多宽带运营商自己也没那么多公网 IP,于是在你之上又套了一层 NAT,叫 CGNAT(运营商级 NAT)。这时候你在路由器 WAN 口看到的 IP 是 100.64.x.x——这是 RFC 6598 专门划给 CGNAT 的地址段。
一旦身处 CGNAT,连在路由器上配置端口映射的资格都没有了,因为你那个"公网 IP"本身也是内网地址。判断方法很简单:
# 查本机/路由器看到的公网 IP
curl ifconfig.me
# 再对比百度搜索"IP"显示的结果;或者看 WAN 口是否以 100.64 开头
如果两者不一致,或者 WAN 口是 100.64.x.x,那你就在 CGNAT 后面。
二、穿透的三条技术路线
所有内网穿透方案,本质上逃不出这三种思路:
路线 A:端口映射(要求有公网 IP)
在路由器上写一条规则:凡是访问我公网 IP 的 8080 端口的,都转给内网的 192.168.1.100:80。
- ✅ 速度最快,能跑满家里上行带宽,是真·直连
- ❌ 必须要有公网 IP;且等于把服务直接裸奔在公网上
配套 DDNS(动态域名)解决家宽 IP 会变的问题。
路线 B:反向代理 / 中转隧道(无需公网 IP)
既然"外部进不来",那就让内网机器主动出去,主动和一台有公网 IP 的服务器建立一条长连接,然后所有流量都沿着这条隧道倒流回来。
外网用户 ──▶ 公网服务器(frps / cloudflared edge)
▲
│ 长连接(由内网机器主动发起)
│
内网机器(frpc) ──▶ 本地服务 :8080
这是 frp、ngrok、Cloudflare Tunnel 的核心模型。代价是所有流量都要过一道中转,速度和带宽受限于中转服务器。
路线 C:点对点打洞(P2P)
前两条路都要依赖一台中间服务器转发。有没有可能两台都在 NAT 后面的机器直接连上?有,这就是 NAT 打洞,也是 Tailscale、ZeroTier、WebRTC 的底层魔法。
三、深入一层:打洞到底是怎么做到的?
这是整个内网穿透话题里最优雅的部分,值得单独讲。
1. 一个死锁
两台都在 NAT 后面的机器 A 和 B,面临一个死局:
- A 想连 B,但 B 的 NAT 会丢弃 A 的包(因为 B 还没主动发出去过);
- B 想连 A,同理也被丢弃;
- A 要等 B 先发包才能连上,B 要等 A 先发包才能连上。
2. 破解:同时敲一拳
打洞利用了 NAT 的一个可预测行为:一旦内网主机发出了一个 UDP 包,NAT 就会创建一个映射,并且(大多数情况下)允许任何外部主机从这个映射的外部端口回包进来。
流程是这样的,需要一个双方都能访问到的会合服务器(rendezvous server):
- A 和 B 各自向会合服务器发一个 UDP 包;
- A 的 NAT 创建映射,
203.0.113.10:5000;B 的 NAT 创建映射,198.51.100.20:6000;服务器记录下这两个公网端点; - 服务器把对方的端点告诉 A 和 B;
- A 和 B 同时向对方的公网端点发 UDP 包。
关键一步来了:
- A 发给 B 的那个包,大概率会被 B 的 NAT 丢弃(因为 B 的 NAT 还没为 A 建表)——但这一发不要紧,A 自己的 NAT 上被打出了一个洞,
203.0.113.10:5000现在开始接受入站了; - 与此同时 B 也发了同样的包,同样在 B 自己的 NAT 上打了个洞;
- 于是 B 的包到达 A 时,正好撞上 A 刚打好的洞,进去了。反之亦然。
两个包都是为了被拒绝而发的,但正是这个"被拒绝"的动作,创造了对方被接受的条件。几毫秒之内,双向通路建立,会合服务器功成身退。
3. 三个标准化协议:STUN / TURN / ICE
IETF 把这套流程标准化了:
| 协议 | 作用 | 特点 |
|---|---|---|
| STUN (RFC 5389/8489) | 告诉设备"你在公网上看起来是哪个 IP:端口" | 极轻量,只反射信息,绝不转发数据 |
| TURN (RFC 5766/8656) | 打洞失败时的兜底中继服务器 | 永远能用,但吃带宽、加延迟 |
| ICE (RFC 8445) | 总调度框架 | 收集所有候选地址(本地/STUN/TURN),并行测试,选最直的那条 |
ICE 的思路是"全都试一遍,谁通用谁":本地直连优先 → STUN 辅助的打洞次之 → TURN 中继最后兜底。
4. 成功率与失败原因
打洞不是万能的:
- UDP 打洞在现实网络中的成功率约 70–82%;非对称 NAT 环境下可达 80–95%,加上 TURN 兜底后接近 100% [citation:3][citation:6]
- 对称 NAT(Symmetric NAT):对不同的目标地址分配不同的外部端口。你从 STUN 拿到的端口,用来连 B 时根本不是同一个——信息直接失效。约 11% 的用户在这种 NAT 后面 [citation:3]。CGNAT 也常常表现为对称 NAT,这是国内打洞成功率偏低的主因之一。
- 端口受限 NAT:只接受你主动联系过的地址回包,稍微严苛一点就失败。
- 企业防火墙:直接封掉 UDP,或在应用层做深度检测。
- TCP 打洞:TCP 的三次握手导致"同时打开(simultaneous open)",多数 NAT 实现处理得很糟糕,打洞本质上是一项 UDP 技术 [citation:3]。
另外,NAT 映射是有寿命的,通常 30–300 秒不通信就会被回收,所以需要周期性 keepalive 保活 [citation:6]。
顺带一提:你每天都在用打洞。微信/Teams 的视频通话、联机游戏、WebRTC,背后都是 ICE 在帮你选路。当你的游戏提示"NAT 类型严格",八成是悄悄降级到中继了。
四、主流方案横评
| 方案 | 需要公网 IP | 协议支持 | 隐私性 | 速度 | 成本 | 上手难度 | 最适合 |
|---|---|---|---|---|---|---|---|
| 端口映射 + DDNS | ✅ 必须 | 全协议 | 取决于你自己 | ⚡ 最快 | 免费/极低 | 简单 | 有公网 IP 的极速直连党 |
| frp(自建) | 需要一台 VPS | 全协议(TCP/UDP/HTTP/HTTPS/STCP) | ✅ 数据自己掌握 | 🚗 取决于 VPS 位置 | VPS 月费 | 中等 | 技术党、要全协议、要可控 |
| Cloudflare Tunnel | ❌ 不需要 | 主要 HTTP/HTTPS | ⚠️ CF 会终止 TLS | 🚗 中等 | 免费 | 简单 | 建站、博客、Web 服务 |
| Tailscale / ZeroTier | ❌ 不需要 | 全协议(组网后 IP 直连) | ✅ 端到端加密 | 🚀 打洞成功即直连 | 免费额度充足 | 极简 | 自己远程访问自己的设备 |
| WireGuard 自建 | 需要公网 IP(或配合穿透) | 全协议 | ✅ 完全自控 | 🚀 快 | VPS 月费 | 中等 | 追求可控的组网 |
| ngrok / cpolar / 花生壳 | ❌ 不需要 | HTTP/TCP | ⚠️ 走厂商 | 🚗 中等 | 免费有额度限制 | 极简 | 临时调试、给客户演示 |
几个补充说明:
速度实测参考(国内访问视角):DDNS 直连 > 近机房 VPS 中转 > Cloudflare Tunnel > Tailscale Funnel 绕国外节点 [citation:2]。不过记住一个铁律——家宽的上行带宽才是最终瓶颈,很多家宽上行只有 30–50Mbps,再快的方案也跑不过这个天花板。
Tailscale vs Cloudflare Tunnel 的本质区别:前者是组网(把你的设备拉进同一个虚拟局域网,用 WireGuard 端到端加密,设备间直连),后者是反向代理(把你的某个 Web 服务发布到公网)。一个是"私有网络互联",一个是"安全的公网发布",别搞混 [citation:5]。
Cloudflare Tunnel 的两个已知代价:一是它作为反向代理会在边缘终止 TLS,即 Cloudflare 能看到明文流量(这是设计使然,不是 bug)[citation:11];二是免费套餐条款上对大流量视频/大文件传输有限制 [citation:11]。另外国内访问 Cloudflare 的连通性时好时坏,别把它当唯一依赖。
五、实战:三种典型配置
方案 1:frp 自建(最自由)
frp 从 v0.52.0 起已废弃 INI 配置格式,现在推荐 TOML / YAML / JSON。下面用 TOML,示例版本 v0.69.x,请以官方 Release 为准 [citation:7]。
公网服务器(frps) /etc/frp/frps.toml:
bindPort = 7000
[auth]
method = "token"
token = "<用 openssl rand -hex 24 生成>"
[webServer]
addr = "127.0.0.1" # 别直接暴露到公网
port = 7500
user = "admin"
password = "<强密码>"
内网机器(frpc) /etc/frp/frpc.toml:
serverAddr = "<公网服务器IP>"
serverPort = 7000
[auth]
method = "token"
token = "<与服务端一致>"
[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
用 systemd 托管(/etc/systemd/system/frps.service):
[Unit]
Description=frp server
After=network-online.target
[Service]
Type=simple
ExecStart=/opt/frp/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now frps
journalctl -u frps -f # 看日志
安全组放行 7000(frpc 连接)和你映射出去的端口(如 6000)。然后:
ssh -p 6000 user@<公网服务器IP> # 直接 SSH 进家里那台机器
进阶姿势:如果是 HTTP 服务,更好的做法是让 frps 的 vhostHTTPPort 只监听 127.0.0.1,由 Nginx 监听 80/443 做反向代理和 TLS 终止。这样防火墙只需放行 7000 和 80/443,frps 的 HTTP 端口完全不对公网开放 [citation:4]。
proxyBindAddr = "127.0.0.1"
vhostHTTPPort = 8080
方案 2:Cloudflare Tunnel(建站最省事)
# 安装 cloudflared 并登录授权
cloudflared tunnel login
# 创建隧道
cloudflared tunnel create my-homelab
# 配置 /etc/cloudflared/config.yml
tunnel: my-homelab
credentials-file: /root/.cloudflared/<隧道ID>.json
ingress:
- hostname: nas.example.com
service: http://localhost:8096
- service: http_status:404
# 跑起来
cloudflared tunnel run my-homelab
零端口开放、自带 DDoS 防护和免费证书,还有 WAF 挡在前面,对个人建站来说性价比极高 [citation:14]。
方案 3:Tailscale(自己访问自己的设备,首选)
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
装完即用。之后你的每台设备都有一个 100.x.x.x 的虚拟内网 IP,SSH、RDP、访问 NAS 全都走这条加密隧道,且打洞成功后是设备间直连,不经过任何中转。
个人用途我几乎总是推荐这个——它把"远程访问自己设备"这件事从"开一个公网端口挨打"变成了"只有我的设备能进来"。不想依赖官方控制面?还有开源的 Headscale 可以自建。
六、安全红线(这一段请认真看)
内网穿透最大的风险不是"配不通",而是配通了,然后把一个没有鉴权的服务挂在了公网上。下面这些是底线:
绝对不要直接暴露到公网的端口:
| 端口 | 服务 | 为什么危险 |
|---|---|---|
| 3306 / 5432 | MySQL / PostgreSQL | 暴力破解 + 拖库 |
| 6379 | Redis | 未授权访问可直接拿 shell |
| 22 | SSH | 全天被机器人扫(换非标准端口只能降噪,不能防攻击) |
| 9443 | Portainer | 拿到它=拿到 Docker =拿到宿主机 |
docker.sock |
Docker 套接字 | 等于把 root 交出去 |
必须做的几件事:
- 最小暴露面:只放行 80/443,其余服务全部由反向代理转发,顺便统一 HTTPS 和访问控制 [citation:14]。
- token 要够随机:
openssl rand -hex 24,别用123456——那是教程里的占位符,不是让你照抄的 [citation:7]。 - SSH 强制密钥登录:确认密钥可用后
PasswordAuthentication no,再加 fail2ban 防爆破。 - frp Dashboard 别裸奔:
webServer.addr保持127.0.0.1,需要时通过 SSH 隧道或反向代理访问。 - 别把配置文件传进公开仓库:
/etc/frp/*.toml里有 token。 - 中转节点的信任问题:Cloudflare Tunnel 之类的托管方案,服务商在边缘终止 TLS,理论上看得到明文。敏感数据要么端到端再加密一层,要么改用 Tailscale 这类端到端方案 [citation:11]。
- 合规提醒:在国内,把宽带线路用于对外提供经营性/公开服务,可能涉及运营商条款与 ICP 备案要求;家庭宽带通常不允许开设对外服务。自用调试、个人远程访问一般没问题,长期公开建站建议用合规的云服务器。
七、选型决策树
别纠结了,照着走:
你的服务需要公开给「不装任何客户端的普通人」访问吗?
├─ 是 ──▶ 你有公网 IP 吗?
│ ├─ 有 ──▶ 端口映射 + DDNS + Nginx(速度最快)
│ └─ 没有 ─▶ Web 服务?
│ ├─ 是 ──▶ Cloudflare Tunnel
│ └─ 否 ──▶ 买台 VPS 跑 frp
│
└─ 否(只有你自己/团队成员访问)
├─ 追求极简 ──────────▶ Tailscale / ZeroTier(首选)
├─ 追求完全自控 ──────▶ WireGuard 自建 / Headscale
└─ 临时演示/调试 ─────▶ ngrok / cpolar
一句话总结:想省事就 Tailscale,建网站就 Cloudflare Tunnel,想自由就 VPS + frp,有公网 IP 就 DDNS 直连。
八、排错清单
端口不通?从上到下逐层排查:
- 服务真的在监听吗? →
ss -ltnp | grep :8080 - 本机防火墙放行了吗? →
sudo ufw status/firewall-cmd --list-all - 云服务器安全组放行了吗?(最常见的坑,尤其是轻量云)
- 路由器端口映射规则写对了吗?内网 IP 是不是变了?
- ISP 是不是封了 80/443?(家宽很常见)→ 换端口或用 Cloudflare Tunnel 绕开 [citation:14]
frpc 连不上 frps?
nc -vz <公网IP> 7000 # 先测控制端口通不通
journalctl -u frpc -n 50 # 再看日志
99% 是三个原因:安全组没放行、token 两边不一致、frps 没起来。
打洞总是失败、速度很慢?
说明降级到了中继(Tailscale 的 DERP、ZeroTier 的 planet)。常见原因是对称 NAT 或 CGNAT。可以检查 NAT 类型,或者干脆接受中转——稳定比快重要。
速度跑不满?
先看家宽上行带宽。100M 宽带的上传往往只有 30M 甚至更低,这是物理天花板,换什么方案都没用。
- 点赞
- 收藏
- 关注作者
评论(0)