NAT反连穿透实战指南

网络编程关键配置建议

  • SO_REUSEADDR选项:用于解决服务端异常终止后的端口占用问题。该选项允许在TIME_WAIT状态期间重用本地地址,需在调用bind()函数之前设置。但需注意此选项无法解决”Cannot assign requested address”连接错误。
  • SO_REUSEPORT选项:实现端口复用的高级机制,允许同一端口同时作为服务端监听端口和客户端源端口。该配置需在bind()操作完成后设置,否则会导致多个进程共享同一端口监听,违背设计初衷。
  • 动态重绑定机制:服务端可在保持客户端连接的同时,重新建立监听端口,此操作不会影响现有客户端连接的正常通信。
  • SO_BINDTODEVICE绑定限制:启用套接字设备绑定后,套接字将被强制关联至特定网络接口,导致本地回环地址(127.0.0.1)访问被阻断。此时必须显式指定目标网卡的实际IP地址才能建立连接,此特性在需要强制流量走特定网络路径的场景中尤为重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* TCP服务端初始化函数
* 创建TCP套接字并配置地址重用、端口复用等关键选项
*/
int init_tcp_server(int internalPort) {
int sock;
struct sockaddr_in bindAddr;

// 1. 创建TCP套接字
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == -1) {
printf("Socket creation failed: %d", errno);
return -1;
}

// 2. 设置SO_REUSEADDR - 允许TIME_WAIT状态下的地址重用
long reuseaddr = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(char*)&reuseaddr, sizeof(reuseaddr)) == -1) {
printf("SO_REUSEADDR error = %d", errno);
}

// 3. 配置绑定地址结构
bindAddr.sin_family = AF_INET;
bindAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
bindAddr.sin_port = htons(internalPort); // 绑定到指定内网端口

// 4. 绑定套接字到本地地址
if (bind(sock, (struct sockaddr*)&bindAddr, sizeof(bindAddr)) == -1) {
printf("Bind failed: %d", errno);
close(sock);
return -1;
}

// 5. 设置SO_REUSEPORT - 允许端口复用
long reuseport = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
(char*)&reuseport, sizeof(reuseport)) == -1) {
printf("SO_REUSEPORT error = %d", errno);
}

// 6. 开始监听,最大连接队列长度为10
if (listen(sock, 10) == -1) {
printf("Listen failed: %d", errno);
close(sock);
return -1;
}

return sock; // 返回监听套接字描述符
}

非NAT 1状态下的外网可达性分析
在上游单路由器场景下,最终NAT类型=路由器NAT策略∧WAN口网关NAT策略。例如:路由器NAT=3且WAN口为公网IP时,检测结果为NAT Type 3。
参考:https://help.onethingcloud.com/9dd6/e9ec, 部分路由器支持NAT类型调整(可设为NAT1),主流路由器均支持UPnP端口映射——WAN口为公网IP时,UPnp映射成功即可实现外网访问;若无UPnP功能,可启用DMZ将指定LAN设备暴露至外网。核心前提:WAN口需为公网IP或NAT Type 1,运营商级NAT限制为决定性因素。

如何检测WAN口NAT类型
直接探测会叠加路由器NAT策略。精确检测方案:先将路由器设为NAT Type 1(消除本地策略干扰),再通过STUN协议探测——需预先用UPnP映射STUN所需的两组UDP端口,确保探测准确性。

NAT 1状态下,不配置UPnP端口映射能否实现外网访问?
不能。NAT Type 1虽具备公网可达性,但端口映射仍是必要前提。无显式端口映射规则时,NAT设备无法将外部请求转发至内网主机。UPnP IGD协议通过自动配置NAT映射规则,实现内网服务的外网暴露机制:客户端向NAT设备下发端口映射请求,NAT设备依据IGD规则建立内网IP:端口到公网IP:端口的转发映射,从而完成数据包的双向路由。
Upnp

P2P穿透核心机制与NAT技术原理
NAT(Network Address Translation)作为IPv4地址短缺的核心解决方案,通过地址转换实现内网主机与公网的通信。在虚拟化环境中,网络模式选择直接影响NAT行为:

  • 桥接模式:虚拟机与宿主机处于同一二层网络,直接获取DHCP分配的公网/内网IP
  • NAT模式:创建独立的虚拟网段,通过宿主机的NAT服务实现地址转换与路由
  • 主机模式:构建仅包含宿主机与虚拟机的隔离网络,无外部路由能力

P2P穿透的本质是在NAT限制下建立端到端直连,通过STUN/TURN协议识别NAT类型并选择对应的穿透策略。
虚拟机桥接
虚拟机桥接模式设置
宿主机IP
虚拟机设置DHCP

在IPv4地址资源枯竭的背景下,NAT(Network Address Translation)成为解决地址短缺的核心技术。当内网主机通过NAT网关与外部网络通信时,网关执行IP地址与端口的转换操作:外部服务器仅感知到NAT网关的公网地址,而非内网主机的真实私有地址,此过程即为NAT映射。
当知道这台电脑的外界ip和端口后,其他人就有可能来和你通信了,这里需要一台中间服务器,注意p2p并不是完全不需要第三方服务器,只是传输数据的时候不需要第三方服务器参与,是peer2peer端对端的,这些转换后的ip和端口需要第三方服务器来传达给其他人,其他人知道ip和端口就可以来连接你了。
当然,并不是知道ip和端口就一定能够连接得上了,路由器也有一定的策略来防范这些,这就是所说的nat类型,nat为1时,所有人都可以来连接,nat为2时,只有之前通信过的ip才可以来连接。
这里有一个比喻,假设nat网关就是女生宿舍的宿管阿姨,你要去803寝室给一个女生修电脑,如果nat为1,则你给宿管阿姨说要去803,她就让你去了,如果是nat为2,需要你是女生的家人(一系列的人)才能进去,如果nat为3,需要你是女生的男朋友(之前女生交代给阿姨过的)才能进去,如果nat为4,则好比这个女生有很多朋友,每个朋友都要有自己的口令才能进去。

NAT打洞原理

  1. 受限锥型NAT打洞原理
    主机A、B分别与服务器C建立连接,获取对方的公网地址。A向B的公网地址发送UDP包触发NAT A建立映射,同时通过C邀请B向A的公网地址回包。当B收到邀请后也向A发送UDP包触发NAT B建立映射,双方完成会话建立后即可直接通信。此类型仅要求源IP匹配,端口不限。

  2. 端口受限锥型NAT打洞原理:
    与受限锥型类似,但需同时匹配源IP和端口,只能使用已建立映射的外部Tuple进行通信。
    是否可以打洞

对称型NAT穿透方式(4只能和1,2打洞)

  1. 同时开放TCP(SimultaneousTCP open)策略
    如果 Client A-1和 Client B-1能够彼此正确的预知对方的NAT将会给下一个TCP连接分配的公网TCP端口,并且两个客户端能够同时地发起一个面向对方的“外出”的TCP连接请求,并在对方的 SYN 包到达之前,自己刚发送出去的SYN包都能顺利的穿过自己的NAT的话,一条端对端的TCP连接就能成功地建立了。
    问题:时钟严格一致,很难做到。
  2. UDP端口猜测策略
    通常,对称NAT分配端口有两种策略,一种是按顺序增加,一种是随机分配。如果这里对称NAT使用顺序增加策略,那么,ClientB-1将两次被分配的Tuples发送给Server后,Server就可以通知ClientA-1在这个端口范围内猜测刚才ClientB-1发送给它的socket-1中被NAT映射后的Tuples,ClientA-1很有可能在有效期内成功猜测到端口号,从而和ClientB-1成功通信。
    问题:不能为随机分配端口的对称型NAT打洞。

为啥nat4只能和nat1/2通信
突然想明白为啥nat4只能和nat1/2通信了,对于nat4:内网主机每次访问不同的外网主机时,都会生成一个新的洞(我测试,访问同一个外网主机,外部端口也会变)。而前面3种类型(nat1/2/3)使用的是同一个洞。所以nat4哪怕洞打成功了,后续真正发起请求时外部端口(对于对端来说是源端口)可能就变了,如果对端是也是nat3/4,源端口变了,就请求不上了,而如果是nat1/2,源端口变了也没关系。
扩展:当两端路由器的NAT类型是(端口限制锥型,对称型)或者(对称型,对称型)时,使用STUN协议就无法穿越,这个时候就需要使用TURN协议。TURN协议简单来说就是进行数据转发,两个内网主机分别把内容发给TURN服务器,TURN服务器再将内容分别转发给对端,以此来实现通信。