iptables nat&conntrack的特殊之处
问题与解释
我在PREROUTING
上做了一个REDIRECT
端口改写,相应的服务处理请求后会返回应答。
按照我以往的认识,认为回包的流量应该先后经过OUTPUT
和POSTROUTING
,所以我利用iptables -t nat -nvL
去查看NAT
表在OUTPUT
链和POSTROUTING
链上的packge
计数器,结果发现没有上涨,这让我陷入了沉思。
经过谷歌后找到了完美的解释:linux-netfilter-how-does-connection-tracking-track-connections-changed-by-nat。
实际上我是知道OUTPUT
链会先过conntrack
表恢复原始IP关系的,但是超出我理解的是NAT
表压根就不会再执行。
上述URL中给出了解释:NAT
表只在连接状态是NEW
的时候(也就是TCP的第一个握手包)才会执行计算,一旦改写关系存入了conntrack
,那么这条连接后续的通讯就不会再过POSTROUTING
和OUTPUT
上面的NAT
表了,而是直接换成了匹配conntrack
来复原连接之前的改写状态。
因此,如果我们想看到回包的package计数器增长,就应该去看OUTPUT
或者POSTROUTING
上面的filter
表计数,一定会看到上涨。
再次梳理流程
如果我们是服务端,那么SYN包到达的时候,在PREROUTING
链的NAT
表执行过之后(可能做DNAT
或者REDIRECT
),路由表将决定是FORWARD
还是INPUT
:
- 如果
INPUT
,那么conntrack
记录就此生成(原理如下),当回包的时候会首先根据conntrack
作地址复原,并且是不会经过OUTPUT/POSTROUTING
链NAT
表(但是会经过filter
表)的。 - 如果
FORWARD
,那么conntrack
记录不会立即生成,需要经过POSTROUTING
之后才知道是否做了SNAT/MASQUERADE
,此时才会生成conntrack
记录(原理如下)。当收到上游回包的时候,不会过PREROUTING
的NAT
表,而是直接根据conntrack
复原为原始IP地址,然后直接FORWARD->POSTROUTING
(不会过NAT
表)送回原始客户端。
具体原理如下:
如上图所示,Netfilter 在四个 Hook 点对包进行跟踪:
PRE_ROUTING
和LOCAL_OUT
:调用nf_conntrack_in()
开始连接跟踪, 正常情况下会创建一条新连接记录,然后将conntrack entry
放到unconfirmed list
。为什么是这两个
hook
点呢?因为它们都是新连接的第一个包最先达到的地方,`PRE_ROUTING` 是外部主动和本机建连时包最先到达的地方
`LOCAL_OUT` 是本机主动和外部建连时包最先到达的地方
POST_ROUTING
和LOCAL_IN
:调用nf_conntrack_confirm()
将nf_conntrack_in()
创建的连接移到confirmed list
。同样要问,为什么在这两个
hook
点呢?因为如果新连接的第一个包没有被丢弃,那这 是它们离开netfilter
之前的最后hook
点:- 外部主动和本机建连的包,如果在中间处理中没有被丢弃,
LOCAL_IN
是其被送到应用(例如 nginx 服务)之前的最后 hook 点 - 本机主动和外部建连的包,如果在中间处理中没有被丢弃,
POST_ROUTING
是其离开主机时的最后 hook 点
- 外部主动和本机建连的包,如果在中间处理中没有被丢弃,