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 点
- 外部主动和本机建连的包,如果在中间处理中没有被丢弃,
