如果想在Linux上配置NAT,那么大家众所一言的就是使用iptables的NAT表来配置,iptables提供了灵活丰富的配置来配置SNAT和DNAT,然而我们知道iptables的NAT依赖了ip_conntrack,也就是说,凡是一个命中了NAT表规则的流就会有一条连接追踪生成,由于ip_conntrack追踪了所有的数据包,因此当有大量连接经过了本地设备时,ip_conntrack空间将被撑满,这个在接入区特别容易重现,在骨干网反而不容易重现,然而骨干网却又几乎不可能使用Linux。
如果Linux支持无状态的NAT就好了,这样就不怕conntrack爆满了,也不会因为conntrack查找而深深影响效率。然而如此的无状态NAT也有自己的缺点,那就是你必须为返回流量显式配置NAT规则,这样配置规则就整整增加了一倍,毕竟是无状态的嘛,NAT工作在无状态的IP层,而IP层是单向的。iptables正是依赖了ip_conntrack才能自动的转换返回包的地址信息的,因为ip_conntrack根据五元素追踪了每一个IP流。
幸运的是,Linux一直都实现了无状态的NAT,只是其很少被人了解罢了。在Linux 2.4内核上,使用iproute2可以实现无状态NAT,只是在2.6内核上其实现被取消了,这是因为Linux的路由模块是基于目的地址hash或者trie tree的,然而对于同一目的地址而言,其作为负载均衡或者其它目的的多个表项却是线性查找的,NAT的实现就是采用多个这样的表项来完成的,这样在大量NAT规则存在的情况下,就会增加查找时间而影响效率, 因此自2.6.24开始,Linux使用TC来实现无状态的NAT,虽然这样的实现有点变态,然而却很好的借助了TC固有的高效的查询匹配方式。
本文不准备详述Linux的TC机制,仅仅解析其配置NAT的方法。具体来讲,NAT分为SNAT和DNAT,如果应用在网络接口即网卡上而不是应用在协议栈的Netfilter链上的话,SNAT将绑定在egress方向,而DNAT将绑定在ingress方向。需要注意的是,由于基于TC的NAT是无状态的,因此当你配置了ingress的DNAT之后,必须在相反的egress方向显式配置SNAT,你的配置量因此将增加一倍。以下我们先来配置ingress上的DNAT规则,效果是将访问本机的一个网卡上的一个IP装换为该网卡上的另一个IP地址。
IP地址规划:eth2上的两个IP:192.168.40.249/24 12.12.12.5/24
和往常一样,首先建立一个规则队列,也就是qdisc:
tc qdisc add dev eth2 ingress handle ffff