Linux NAT基本流程与实现技巧
基于matches的NAT
Linux的NAT是基于match的,即在满足一系列条件的前提下执行SNAT或者DNAT,因此要求也就比较宽松,唯一的约束就是路由,即路由动作发生的时候,必须是基于最终的目标IP地址,因此DNAT必须发生在路由之前(对于本机发出的数据包,则在路由之后,然后重新路由),如下图所示
附:Netfilter与ip_conntrack
Netfilter
Linux的协议栈仅仅实现了基本的协议操作,对应TCP/IP标准,Linux的协议栈仅仅实现了一个最小集。其余的所有扩展几乎(并非所有!还有一部分由net schedule实现)均由Netfilter来实现,包括:IP Firewall,IP NAT,IPSec,IPVS等。
ip_conntrack ip_conntrack是NAT实现的重中之重,Linux的NAT完全依赖ip_conntrack,依附于ip_conntrack之上。
基本数据结构:
值得注意的是,两个方向的五元组节点在confirm之后统一处在一个哈希表中,并不区别对待,只要使用一个五元组作为键查找到不管哪个方向的五元组节点,都可以找到ip_conntrack结构体本身。五元组节点除了包含五元组信息之外,还包含方向信息。
NAT流程
NAT如何依附于ip_conntrack之上的呢?如上图所示,NAT修改了ip_conntrack中的反方向的五元组,对正方向的五元组并没有影响,正是这个特点使得在Linux实现NAT的时候可以使用一种非常巧妙的方法。
NAT的流程如下所示:
注意:由于仅仅一个流的第一个包会创建ip_conntrack结构体,因此也是第一个包会去匹配你用ipptables配置的NAT规则,这是个局限性
实现技巧
在2.6的早期内核中,NAT的数据,包括要转换到的IP地址等数据都是保存在CT的extension中的,叫做ip_nat_info,而该info可以从CT中取出来,在到达nf_nat_fn的时候执行NAT的时候使用里面的ip_nat_info_manip来做NAT的依据。但是在后期的版本中,使用了一种更加高效得方式,需要转换到的IP地址不再保存在ip_nat_info中,而是直接使用上述流程图中的计算方法得到,即获取反向五元组,取逆,按结果执行。这么做是有依据的,因为在NAT规则匹配成功后,会直接修改掉反方向五元组的tuple,因为NAT规则匹配成功肯定是针对正方向五元组的,毕竟只有第一个包才会去匹配NAT规则,无来怎能有回呢?所以根据NAT规则修改的既然是反方向五元组,那么标准取逆后得到就是NAT后的正方向五元组了,在这个过程中,正方向的五元组一直保存不变!对于反方向回来的包,它的反向就是正方向,由于正方向从来没有被改过,取逆后即成为原始的反方向的五元组,返回包的五元组得以还原!正因为如此,Linux NAT对方向才如此敏感:匹配规则的正方向的数据包,修改的是反方向的五元组!注意正向,正方向以及反向,反方向并不是同一个意思,正向和反向是针对当前的方向来讲的,正方向和反方向是针对数据流的发起方到接收方的方向来讲的。
可以从这个实现看出Linux内核的发展,在2.6的早期版本中,实际上在NAT规则匹配成功后也是去修改反方向五元组,但是那时怎么就不是通过反向五元组取逆来作为地址转换依据的呢?实际上那时一定可以这么做!事实证明,Linux内核中布满的逐步逐步被认识被发现的技巧。这也正体现了网络社区开发的“90%特性”,即甚至90%的可用性即可,不要求100%的完美。