浅谈 Linux 的 conntrack 流
本文通过一个简单的 NAT 例子,介绍 Linux 的 conntrack 流。
本文只讨论 IPv4、TCP 和 SNAT,不展开 conntrack 状态机、Netfilter Hook、DNAT、NAT Hairpin 等内容。
What
先把核心概念说清楚:
conntrack 流是 Linux 内核为一次双向网络通信维护的一条状态记录。
假设内网客户端访问 Internet 服务器:
1 | 内网客户端 Internet 服务器 |
Linux 会把两个方向关联成同一条 conntrack 流:
1 | ORIGINAL 方向: |
所以:
1 | 请求方向 + 返回方向 |
conntrack 记录中可以包含:
1 | 原始方向 |
Linux 内核的 conntrack 接口也明确区分了 tuple-orig 和 tuple-reply 两个方向。
需要注意:
conntrack 流不是一个数据包,也不等于应用程序的 socket。
它是 Linux 内核对一次双向通信维护的状态记录。
Why
假设一台 Linux 机器作为 NAT 网关:
1 | 内网客户端 |
内网客户端发送:
1 | 10.0.0.2:50000 -> 203.0.113.10:443 |
经过 SNAT 后,源地址被修改为网关的公网地址:
1 | 198.51.100.1:62000 -> 203.0.113.10:443 |
服务器看到的客户端是:
1 | 198.51.100.1:62000 |
因此服务器返回:
1 | 203.0.113.10:443 -> 198.51.100.1:62000 |
问题来了:
Linux 网关收到这个返回包后,怎么知道应该把它转发给哪个内网客户端?
网关必须记住下面这组关系:
1 | 198.51.100.1:62000 |
这就是 conntrack 的作用。
它为这次通信保存 NAT 映射,使返回包可以执行反向转换:
1 | NAT 前: |
如果没有 conntrack,Linux 网关只看到:
1 | 目的地址:198.51.100.1:62000 |
却不知道它原来对应:
1 | 10.0.0.2:50000 |
返回包就无法正确送回内网客户端。
基础 Example
完整过程如下:
1 | 1. 内网客户端发送 |
conntrack 在其中保存:
1 | 10.0.0.2:50000 |
这样,正向数据包和返回数据包就能使用同一套 NAT 映射。
How
简化地说:
1 | 第一个数据包 |
对于 nftables 的有状态 NAT,通常只有一条连接的第一个数据包经过 NAT 规则并建立 NAT 绑定,后续数据包根据 conntrack 中已经保存的信息完成转换。
记忆图
1 | 内网客户端 |
可以简单记成:
NAT 修改了地址,conntrack 记住了地址是怎么修改的。
正向报文使用 NAT 映射:
1 | 内网地址 -> 公网地址 |
返回报文使用反向映射:
1 | 公网地址 -> 内网地址 |
这就是 conntrack 流在 NAT 中最核心的作用。
参考资料: