NAT 机制与内网穿透
2011 年,全世界最后一个 /8 公网 IPv4 地址块被分配完毕。
此后,新入网的设备再也无法获得公网 IP。但你可能没有感觉——手机能用 WiFi,电脑能上网,云服务器也能访问。
这一切的功臣,就是 NAT(Network Address Translation)——网络地址转换。
NAT 的诞生背景
IPv4 地址不够用,这是老生常谈了。但问题来了:既然地址不够,为什么我们还能正常上网?
答案是:不是每台设备都需要公网 IP。
私有 IP 地址(10.x.x.x、172.16.x.x-31.x.x、192.168.x.x)可以无限使用,它们不能直接上公网,但可以通过 NAT 转换,共享少量公网 IP。
没有 NAT: 每台设备需要一个公网 IP → 地址不够
有 NAT: 无数私有 IP 共享几个公网 IP → 勉强够用NAT 的工作原理
基本概念
NAT 在路由器(或防火墙)上工作,把私有 IP + 私有端口映射为公网 IP + 公网端口。
内网主机(192.168.1.100:5000)想访问百度(8.8.8.8:80)
┌─────────────┐
私有 IP ──────────────>│ │
192.168.1.100:5000 ──>│ 路由器 │───> 公网 IP 114.114.114.114:6000 ──> 百度
│ (NAT) │
│ 192.168.1.1│
└─────────────┘
返回时:百度 → 114.114.114.114:6000 → 路由器 → 192.168.1.100:5000 → 内网主机转换过程
# NAT 转换表(简化示例)
┌────────────────────────────────────────────────────────────────┐
│ NAT 转换表 │
├─────────────────────────────┬──────────────────────────────────┤
│ 内网 (IP:Port) │ 公网 (IP:Port) │
├─────────────────────────────┼──────────────────────────────────┤
│ 192.168.1.100:5000 │ 114.114.114.114:6000 │
│ 192.168.1.101:5001 │ 114.114.114.114:6001 │
│ 192.168.1.102:5002 │ 114.114.114.114:6002 │
└─────────────────────────────┴──────────────────────────────────┘出方向转换:
- 内网主机发送数据包:源 IP=192.168.1.100,源端口=5000
- NAT 设备从公网 IP 池中选一个可用端口(如 6000)
- 把源 IP 改为公网 IP,源端口改为 6000
- 在转换表中记录这个映射
入方向转换:
- 收到返回数据:目的 IP=114.114.114.114,目的端口=6000
- 查 NAT 表,找到对应内网 IP=192.168.1.100,端口=5000
- 把目的 IP 和端口改为内网地址
- 转发给内网主机
NAT 的类型
静态 NAT(Static NAT)
一对一映射,公网 IP 和私有 IP 永久绑定。
192.168.1.100 ↔ 114.114.114.114:8000
192.168.1.101 ↔ 114.114.114.114:8001特点:
- 公网可以直接访问内网服务器
- 需要多个公网 IP
- 适用于需要固定公网 IP 的服务器
动态 NAT(Dynamic NAT)
从公网 IP 池中动态分配,用完释放。
首次请求:192.168.1.100 → 分配 114.114.114.114:8000
二次请求:192.168.1.101 → 分配 114.114.114.114:8001
...
IP 池用完:新请求必须等待旧映射过期特点:
- 不需要多个公网 IP
- 公网无法主动访问内网
- 适用于普通上网用户
PAT(端口地址转换)
最常用的 NAT 类型,也叫端口复用 NAT。
多个内网 IP 共享一个公网 IP,靠不同端口区分。
192.168.1.100:5000 → 114.114.114.114:6000
192.168.1.101:5000 → 114.114.114.114:6001
192.168.1.102:5000 → 114.114.114.114:6002特点:
- 最节省公网 IP
- 目前家庭和企业网络的主流方案
- 通常说的「NAT」指的就是 PAT
NAT 的问题
1. P2P 连接困难
NAT 的设计初衷是让内网设备主动访问公网,公网设备无法主动连接内网。
这对于 P2P 应用(BT 下载、视频通话、游戏)是灾难性的。
解决方案:
- NAT 穿透技术(如 STUN、TURN、ICE)
- 中继服务器(牺牲连接效率,但保证可用)
2. 端到端连接破坏
NAT 隐藏了端点的真实地址,破坏了互联网的端到端透明性。
理想的互联网应该是「任何主机可以和任何其他主机直接通信」,NAT 打破了这个设计哲学。
3. 日志追踪困难
由于多个用户共享 IP,当发生网络攻击或违法内容传播时,溯源变得困难。
内网穿透:让公网访问内网
NAT 虽然保护了内网,但也带来了「外网无法访问内网服务」的问题。内网穿透就是解决这个问题的。
方法一:端口映射(最简单)
在路由器上配置端口映射,把内网端口暴露到公网。
配置:内网 192.168.1.100:8080 → 公网 114.114.114.114:80
效果:访问 http://114.114.114.114 就能访问内网服务# 路由器端口映射配置示例
# WAN 接口:任意
# 内部主机:192.168.1.100
# 内部端口:8080
# 外部端口:80
# 协议:TCP方法二:DMZ 主机
把内网的一台主机设为 DMZ(Demilitarized Zone),它的所有端口都对公网开放(除了已映射的端口)。
DMZ 主机:192.168.1.200
效果:公网可以直接访问 192.168.1.200 的所有端口
风险:DMZ 主机暴露在公网,安全性降低方法三:NAT 穿透技术(STUN/TURN/ICE)
适用于 P2P 应用,核心思想是:
- STUN:让内网主机知道自己被 NAT 后的公网地址
- TURN:当 P2P 直连失败时,通过中继服务器转发
- ICE:综合 STUN 和 TURN,优先尝试直连,失败后中继
NAT 穿透流程:
1. 两端都连接 STUN 服务器,获取自己的公网地址
2. 双方交换地址信息
3. 尝试直连(同时发送 UDP 包穿透 NAT)
4. 如果直连失败,使用 TURN 服务器中继方法四:内网穿透工具
市面有很多成熟的内网穿透工具:
frp(Go):高性能内网穿透工具,支持 TCP/UDP/HTTP
ngrok:云服务,开箱即用,免费版有限制
花生壳:老牌工具,商业化成熟
cpolar:新兴工具,免费额度友好frp 使用示例
服务端(公网机器):
# frps.ini
[common]
bind_port = 7000 # frp 服务端口
vhost_http_port = 8080 # HTTP 服务监听端口./frps -c frps.ini客户端(内网机器):
# frpc.ini
[common]
server_addr = 你的公网IP
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000./frpc -c frpc.ini效果:
- 内网 SSH:localhost:22
- 公网访问:
ssh -p 6000 user@公网IP
方法五:反向代理(VPN)
通过 VPN 隧道,把公网机器拉进内网。
VPN 隧道
公网机器 ════════════════════════════ 内网
│ │
│ ├── 192.168.1.100:8080
│ ├── 192.168.1.101:3306
└── 可以直接访问内网任意服务 └── ...常见方案:OpenVPN、WireGuard、Tailscale(ZeroTier 的竞品)
实际案例
家庭网络拓扑
Internet
│
│ 光纤
▼
┌────────┐
│ 光猫 │ (可能内置路由功能)
└───┬────┘
│
│ LAN (192.168.1.x)
▼
┌────────────┐
│ 路由器 │ ← NAT + DHCP + 端口映射
│ 192.168.1.1│
└───┬────────┘
│
├── 手机 (192.168.1.101) ─── NAT 转换 ──> 公网 IP:随机端口
├── 电脑 (192.168.1.102) ─── NAT 转换 ──> 公网 IP:随机端口
└── NAS (192.168.1.200) ← 端口映射 21870 ──> 公网 IP:21870Java 获取本机公网 IP
import java.net.HttpURLConnection;
import java.net.URL;
public class PublicIpFinder {
public static void main(String[] args) {
try {
// 使用第三方服务获取公网 IP
URL url = new URL("https://api.ipify.org");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
try (java.io.BufferedReader br = new java.io.BufferedReader(
new java.io.InputStreamReader(conn.getInputStream()))) {
String publicIp = br.readLine();
System.out.println("公网 IP: " + publicIp);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}面试追问方向
- NAT 的工作原理是什么?为什么需要 NAT?
- NAT 有哪几种类型?区别是什么?
- NAT 会带来哪些问题?
- 什么是端口映射?和 DMZ 的区别是什么?
- 内网穿透有哪些方法?
- STUN、TURN、ICE 分别解决什么问题?
- frp 是如何实现内网穿透的?
