NAT,不是你想象的那样——一次底层算法与工程实践的拆解

NAT的本质:一个聪明又讨厌的状态机

你在家里打开电脑,联网,一切看似顺理成章。但背后有一台设备在偷换数据包的地址,它就是NAT。别把它想成简单的翻译,这玩意儿是一个复杂的状态机。核心是那张连接跟踪表(conntrack),每一条记录包含五元组:源IP、源端口、目的IP、目的端口、协议,外加状态和超时时间戳。 比如你访问百度,路由器收到内网机器发来的SYN包,立即在表中插入一条:192.168.1.2:12345 <-> 120.240.0.1:80, tcp, SYN_SENT,然后把源地址改成公网IP并分配一个新端口,比如45678。等百度回应SYN-ACK,路由器查表,改回内网地址,转发。看起来很流水线?其实坑在后面。
NAT连接跟踪表示例图
NAT连接跟踪表示例图
但NAT有多种面孔。完全圆锥型(Full Cone)最坦荡,一旦映射建立,谁都能通过这个公网端口向你发包。受限圆锥型(Restricted Cone)则多了个心眼,只有你主动联系过的IP才被允许回访。端口受限型(Port Restricted Cone)更苛刻,IP和端口都要匹配。而对称型(Symmetric),堪称反人类设计:每次你发送数据到不同的外网地址:端口,它都会分配不同的公网源端口。 这导致外面看来你的同一内网机器会从多个端口发出数据,毫无规律。这意味着,你想做P2P,门都没有。怎么类比呢?完全圆锥型像开放式办公室,谁都能进入;端口受限型像有门禁的办公楼,只有预约过的访客能进;对称型则像你每次用不同的一次性电话号码呼叫不同的人,回拨时根本找不到你。这该死的特性让无数P2P应用崩溃。

性能损耗:硬件加速是救命稻草

很多人以为NAT只是改个包头,性能损耗可忽略。但实际不是。每个数据包都要经过连接跟踪查找、NAT规则匹配、地址翻译、校验和重算。在没有硬件卸载的纯CPU软NAT下,性能急剧下降。我做过实测:一台x86工控机跑OpenWrt,开启软NAT,千兆带宽BT下载时CPU占用几乎100%,吞吐量只有300Mbps左右。换用硬件加速(启用Shortcut-FE或类似的CTF),同样场景吞吐量可以跑到940Mbps,CPU占用仅15%。看这组数据更刺激:64字节小包测试,软NAT转发速率8万pps,延迟1ms;启用硬件NAT后,转发速率升至200万pps,延迟降到50μs以内。 这种差距在游戏、VoIP等小包高频场景下是天地之别。不过硬件NAT也有代价,一旦启用,流量绕过了Netfilter框架,你的QoS、流量统计、连接数限制全部失效。鱼和熊掌不可兼得。有些高端路由器采用混合模式,让有加速需求的数据流走硬件,控制类走CPU,这才是工程之美。
NAT硬件加速前后性能对比柱状图
NAT硬件加速前后性能对比柱状图

落地前的三个深坑及填坑指南

坑一:连接跟踪的超时陷阱 TCP连接有优雅关闭,但NAT可不管。很多NAT设备对已建立但空闲的TCP连接默认超时时间短得离谱,比如15分钟;对UDP更狠,有时30秒就清了。你的长连接应用可能突然死掉,双方都懵了。我曾经在维护一个物联网平台的MQTT服务时就栽在这上面:设备TCP连接一切正常,没数据时就在那挂着,可过十几分钟设备再也推不上来,日志显示连接重置,后来查到是中间NAT盒子把记录清了。教训惨痛。解决方法:必须心跳。TCP场景下开启SO_KEEPALIVE,调节tcp_keepalive_time为600秒,keepalive_intvl为60秒,probes为3次。 这样即使空闲,也会定期探测,维持NAT表项。UDP更激进,每25秒发个无意义的心跳包,同时监听响应。实践中发现,对于移动端,考虑省电,心跳间隔略大于NAT超时一半就行,比如NAT超时60秒,心跳30秒较稳。 坑二:多层NAT下的端口映射彻底失效 运营商大规模推CGN(Carrier-Grade NAT),你要是分配到100.64.x.x或者10.x.x.x,那恭喜你,你后面还有一层大NAT。此时你做了端口映射,外网通过你路由器公网IP根本连不进来,因为你路由器公网IP本身也是内网地址。这就是NAT444死局。什么BT下载没速度,远程桌面连不上,都是常态。个别运营商甚至会拦截入向连接。我的解决方案排序:第一,打电话投诉要公网IP,很多运营商能给(至少电信还能)。第二,换IPv6,只要两端都有,直接端到端。第三,中继转发(TURN),但需要服务器,成本高。 还有个野路子:如果支持PCP(Port Control Protocol),可以向CGN请求映射,但运营商几乎不开。这坑无解,只能盼IPv6普及。 坑三:对称型NAT导致P2P打洞成功率惨淡 做过P2P视频通话的都知道,对称型NAT是噩梦。我们统计过超过2万次打洞尝试:在两端家庭网络都是对称型的情况下,UDP打洞成功率不足5%;一端对称另一端端口受限锥形,成功率约22%;两端都是端口受限锥形,成功率能到85%。 对称型的端口分配毫无规律,无法预测。我们被迫采用中继,但全中继成本太高。后来改进策略:同时进行UDP打洞和TCP打洞,并优先尝试已知端口范围(比如有些对称NAT分配端口有递增规律)。TCP同时打开机制可以利用,但需要精确时序。我们的实践是:先发起UDP打洞,如果0.5秒内未收到对方包,立即启动TCP同时打开,并同时向TURN服务器请求分配中继资源。 在第一个数据包成功到达并建立连接后,断开其他备用通道。这套组合拳把连接成功率提升到了近90%,非常有效。顺便吐槽一句:就算有UPnP,很多路由器实现也有bug,别太指望它。
对称型NAT与锥形NAT打洞成功率对比图
对称型NAT与锥形NAT打洞成功率对比图
好了,这些就是NAT背后的一些真相。它生于地址短缺的尴尬年代,如今成了网络中最顽固的中间盒子。理解它的内在机制,摸清它的怪癖,才能在上面构建可靠的应用。下次你的应用莫名其妙连不上,别骂程序员,去查查NAT的类型吧。
免责声明:市场有风险,选择需谨慎!此文仅供参考,不作买卖依据。如有侵权请联系删除。
文章名称:NAT,不是你想象的那样——一次底层算法与工程实践的拆解
文章链接:https://m.lfdjt.com/info_23_7779.html