家里的设备都用私有地址,怎么和公网通信?NAT 到底改了什么,端口会不会变?这篇用 STUN 服务器问“你看到我的源地址和端口是什么”,实测本机出口 NAT 的映射行为,再用一个纸面模型演示 NAPT 表。
本机套接字:地址是私有地址(本机局域网地址),本地端口 56336 服务器 1看到的:地址是公网地址,端口 56336 服务器 2看到的:地址是公网地址,端口 56336 服务器 3看到的:地址是公网地址,端口 56336 3 个服务器有回应:它们看到的公网地址互相相同(不同的值 1 个),端口相同(不同的值 1 个);服务器看到的公网地址与本机地址不同 同一个本地套接字发往 3 个不同的目的地,映射到同一个外部地址和端口:True——这叫“与目的无关的映射”(endpoint-independent mapping) 服务器看到的端口 [56336] 与本地端口 56336相同:NAT 没有改端口(端口保留)
STUN 服务器把收到的 UDP 包的源地址和端口回给你。实测:本机地址是私有的,服务器看到公网地址,端口不变,三个服务器看到的一样,是“与目的无关的映射”。
UDP:40 个套接字,外部端口与本地端口相同 39 个、不同 1 个、没有回应 0 个;不同的:[(4500, 3319)] TCP 服务器 1:没有拿到 STUN 回应(服务器可能不支持 TCP STUN) TCP 服务器 2:没有拿到 STUN 回应(服务器可能不支持 TCP STUN) TCP 服务器 3:没有拿到 STUN 回应(服务器可能不支持 TCP STUN)
40 个 UDP 套接字里 39 个外部端口等于本地端口,只有本地 4500 被改成 3319,原因没有验证;TCP 的 STUN 服务器没有回应,TCP 映射没有测到。
TTL=1:3/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=2:3/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=3:3/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=4:3/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=5:1/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=6:3 个探测都没有回应 TTL=7:1/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=8:3 个探测都没有回应 TTL=9:1/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=10:2/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=11:3 个探测都没有回应 TTL=12:3 个探测都没有回应 TTL=13:3 个探测都没有回应 TTL=14:3/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=15:1/3 个回应;该跳收到的包源地址是 本机局域网地址;源端口与本机发出的相同 TTL=16:3 个探测都没有回应 16 个 TTL 里 10 个有回应(第 [1, 2, 3, 4, 5, 7, 9, 10, 14, 15] 跳);其中引用的源地址是本机局域网地址的有 10 个,不是的有 0 个;引用的源端口与本机发出的相同的有 10 个 第一节明明显示服务器看到的是改写之后的公网地址,但路径上远处的路由器回的差错里引用的却仍是本机局域网地址。我的解释是:NAT 设备在把差错报文送回内网时,把内嵌的原包头也翻译回了内网地址(NAT 处理 ICMP 差错时需要这样做,否则内网主机认不出这是自己的包)——这个解释没有直接验证。结论:用这种方法看不出 NAT 位于哪一跳
路径上远处路由器回的超时报文引用的源地址仍是内网地址,推测是 NAT 把内嵌的原包头翻译了回去。所以不能靠 traceroute 判断 NAT 在哪一跳。
内网连接 → 公网映射
udp 10.0.0.2:5000 → 198.51.100.7:53 → 203.0.113.1:5000
udp 10.0.0.3:5000 → 198.51.100.7:53 → 203.0.113.1:40000
tcp 10.0.0.2:52111 → 198.51.100.9:443 → 203.0.113.1:52111
tcp 10.0.0.4:52111 → 198.51.100.9:443 → 203.0.113.1:40001
udp 10.0.0.2:5000 → 198.51.100.8:123 → 203.0.113.1:40002
NAT 表里 5 条记录;两台主机都用源端口 5000 的 UDP:第一台映射到外部端口 5000(保留),第二台因为 5000 已被占用而换成 40000
回程翻译:来自 198.51.100.7:53 发往 5000 的包 → ('10.0.0.2', 5000);来自没联系过的 198.51.100.99:53 的包 → None(丢弃);发往没有记录的端口 41234 → None(丢弃)
改写源地址 10.0.0.2 → 203.0.113.1 以后的 IP 头校验和:增量更新 0xd473,整头重算 0xd473,一致(TCP / UDP 的校验和还要同样更新,因为伪头部含地址)纸面模型:每个内网连接对应一条记录,优先保留源端口,冲突时分配新端口;入站包必须匹配记录才被翻译,否则丢弃;改地址或端口时用增量公式更新校验和。