源地址转换:SNAT
SNAT 主要应用于下列场景:这种情况下,我们只有一个公网地址A,而又有三台主机需要同时上网,这时就需要SNAT了。它的主要作用是将那些由私网发来的数据包skb的源地址改成防火墙的公网地址A,这是因为目的主机在响应源地址为私网地址的数据包时,私网地址不能在网络上路由的缘故。
SNAT仅可以在LOCAL_OUT和POSTROUTING点生效,这也说明了为什么用户空间的iptables命令在配置SNAT规则时只能配置到nat表的OUTPUT和POSTROUTING链的缘由。
我们现在假设的情形是:其他四个HOOK点都没配任何规则,且内置链的缺省处理策略为ACCEPT,然后在防火墙上配置了一条SNAT规则,私网地址B1要访问公网地址C的情况。
和前面DNAT类似,当第一个由B1发往C的数据包到达POSTROUTING点时,在连接跟踪阶段就已经为该连接建立好了ip_conntrack实例并进行过适当的初始化。SNAT主要是在ip_nat_out()函数中完成,而该函数本质上也调用了ip_nat_fn(),所以流程和DNAT一样,但执行的操作有所差别。
还是先回顾一下 ip_nat_fn() 的流程图:注意,当执行ip_nat_out()函数时,该skb已经被正确路由过了。此时,在ip_nat_fn()里执行的是ip_nat_rule_find()分支,然后进入ip_nat_setup_info()函数中。
接下来我们简单说一下get_unique_tuple()函数。
如果属于某条连接的数据包之前已经被执行过 NAT 了,则其连接跟踪记录会被添加到 bysource 链表中。对于 SNAT 操作,如果是第一个数据包,其流程和 DNAT 非常相似,在函数 find_best_ips_proto() 中完成对临时 tuple 的源地址的修改。然后,以修改后的 tuple 计算其响应 tuple ,最终用该响应 tuple 替换掉连接跟踪记录中原来的响应 tuple 。替换的结果是:此时,连接跟踪记录中响应tuple的源地址已经被替换成防火墙的公网地址A了。等会儿当服务器C回应时,它所发出的报文目的地址就是A,这样防火墙就可以正确接收到服务器C的回应报文。最后,会根据该连接跟踪记录实例的初始tuple来计算一个hash值,然后将其插入到bysource里,并对ip_conntrack.status状态进行适当设置。
和DNAT类似,最后也是在manip_pkt()函数中完成对skb源地址、IP校验和进行修改。对skb里端口的修改也是在manip_pkt()里完成的。
OK,关于SNAT我们第一阶段的分析就完成了,接下来我们来看一下当服务器C收到这个请求报文后,在对其响应的后续流程里防火墙是如何实现所谓的自动De-SNAT功能。
服务器的响应报文其源地址为自己的公网地址C,目的地址为防火墙的公网地址A。当该响应报文到达防火墙后,连接跟踪系统可以准确地识别该数据包所欲的连接跟踪记录,因为存在一个源地址是C,目的地址是A的响应tuple(如上图所示)与其匹配。该响应数据包还是会先到达PREROUTING点的ip_nat_in()函数,因为没有配置任何DNAT规则,同时ct.status字段又设置了IPS_SRC_NAT和IPS_SRC_NAT_DONE_BIT标志位,所以在进入到ip_nat_in()函数的ip_nat_fn()里时就直接调用ip_nat_packet()接口。
在ip_nat_packet()中以连接跟踪记录的初始tuple计算原来旧的响应tuple:源地址是C,目的地址是B1。因为在PREROUTING点上要做DNAT,所以此时skb中的目的地址A就被改成了原来的响应tuple中的目的地址B1了。后面的流程和DNAT完全一样。
当该响应报文来到POSTROUTING点时,又调用ip_nat_out(),和前面分析SNAT一阶段时的流程一样。
现在我们就明白了,De-SNAT功能是基于manip_pkt()函数实现的。在结尾之际,我们来解释一下上篇博文中关于NAT的一段描述:“只有每条连接的第一个数据包才会经过nat表,而属于该连接的后续数据包会按照第一个数据包则会按照第一个报所执行的动作进行处理,不再经过nat表”。
从用户空间的iptables规则来看,每条规则都有一个counter计数器,该计数器记录的是被该规则成功匹配了数据包的数目。而内核中对该计数器的修改是在ipt_do_table()函数。从ip_nat_fn()函数的执行流程可以看出,当连接跟踪记录项被设置了IPS_SRC_NAT_DONE_BIT状态位,或者连接跟踪的状态不再是IP_CT_NEW状态时,ipt_do_table()函数就不再被调用了,反应在用户空间所看到的直观现象就是,nat表的规则计数器不再增长了。
基于连接跟踪的 NAT ,其特殊之处就在于初始和响应 tuple 不再一致了,而这也是 NAT 得以正确运行的关键所在。通过如下这张图,让大家再复习一下 NAT 的初始和响应 tuple 之间的关系:至此,Linux中Netfilter框架下的nat子系统我们就全部学习完了。用了十个章节基本将Netfilter框架的各个功能子模块、架构、原理、流程等作了初步简单的分析了解。由于知识有限,本人的分析难免存在疏漏,还请各位大侠不吝指正。在接下来后续的文章中,主要探讨以下主题:
1、 用户空间的iptables是怎样识别传递给它的每个参数?
2、 通过iptables配置的每条规则是如何进到内核里的?它们又有什么关系?
3、 内核是如何判断数据包到底匹配还是不匹配某条具体规则?
4、 如何自己动手扩展iptables的功能模块?
未完,待续…