实现 NAT (Masquerade) TCP 状态管理的极简方案

2020-03-20 19:57:01 +08:00
 feng32
如果要自己实现一个 NAT (Masquerade) 服务器,除了分配端口,改字段和校验和,另一个难题可能是 TCP 的状态管理: 一般的技术资料会告诉你,FIN 的发起方需要 TIME_WAIT,而接收方则不需要

比如资料 1:

http://tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm

比如资料 2:

https://blog.51cto.com/11859650/1917938

于是如果要在一个合适的时候释放掉端口,我原本以为要在程序中大致实现 TCP 状态机,并且跟踪连接状态

今天为了保险起见,看了下 Linux 内核 conntrack 的实现逻辑,却有了个新发现。Linux 内核的 conntrack 虽然实现了完整的状态管理,并且滤掉了一些明显错误的包,但是我发现无论是主动发起 FIN 还是被动发起,conntrack 状态变化的过程都是:

- sES -> sFW (Established -> FinWait)
- sFW -> sCW (FinWait -> CloseWait)
- sCW -> sLA (CloseWait -> LastAck)
- sLA -> sTW (LaskAck -> TimeWait)

最终都是要 TIME_WAIT 的!源代码在这里:

https://elixir.bootlin.com/linux/v2.6.39.4/source/net/netfilter/nf_conntrack_proto_tcp.c#L141

为了确认不是自己看走眼了,还找到了一篇描述该现象的提问:

https://unix.stackexchange.com/questions/67479/why-tcp-time-wait-state-is-present-at-both-ends-after-a-connection-termination

问题来了:既然无论如何都要 TIME_WAIT 一段时间,NAT TCP 的处理是否可以退化成 NAT UDP 的处理,也就是连接静止一段时间就触发清除?

看起来这个和 Linux 原生的 iptables masquerade 并没有多少区别,最多放一些错误的包进来,而最终这些错误的包会被底层的设备以正确的方式处理掉
1407 次点击
所在节点    程序员
1 条回复
qakito
2020-03-21 10:37:28 +08:00
NAT 表对于 TCP 和 UDP 的表项维持时间是不同的; UDP 表项通常维持在 2 分钟-5 分钟; TCP 表项通常维持在 30 分钟-60 分钟。
通过 TCP 状态(比如 RST 标识等),能够较及时的清除已经断开的 TCP NAT 表项;否则的话,要么失效的 TCP 表项会长时间残留,要么一段时间空闲的 TCP 会话会因中间的 NAT 表项超时断开。

UDP 会话没有状态,只要看到一来一回,conntrack 就是 establish 状态了;对于一个需要长时间保持的 UDP 会话(比如 IPSec ESP 穿越 NAT),如果一段时间内没有流量,NAT 表失效就意味着 UDP 会话断开,因此对于这种情况,通常靠协议发送 keepalive 来维持 NAT 表

TCP 会话有状态,如果没有开启 keepalive 选项,且会话闲置了一段时间,NAT 表超时就会导致 TCP 会话断开(比如 ssh 连接)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/654693

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX