日期:2014-05-16  浏览次数:20881 次

Linux如何做到将外来数据包DNAT到Loopback
前面写了篇文章《使用iptables为何不能将外部进入的包NAT到127.0.0.1》,牵扯到了很多知识,最终的结论就是不能那么做。这个结论让人有些不舒服,说了半天就是阐述它为何做不到,如果我非要将包NAT到loopback呢。比如为了不将端口以及地址暴露给外部,我就是想让代理服务侦听127.0.0.1这个地址,有没有什么办法做到呢?
        因为你用的是Linux,答案无疑是肯定的,所要做的无非就是把路由时候的限制条件给去掉。我们知道,在进和出两个方向,有两个路由查找逻辑,一个是而后一个函数限制在其调用的__mkroute_output中:
我们到底应该怎么绕过去呢?如今我们知道,NAT逻辑已经将目标地址改成了127.0.0.1,而源地址保持不变,这种数据包在进入的时候会被ip_route_input_slow拦截并且由于源地址非loopback而目标为loopback而丢弃,丢弃的时候注意还没有查找路由呢?现在我们假设这个包已经过去了,那么返回包能不能顺利通过ip_route_output_slow呢?答案无疑也是否定的,由于标准路由是基于目标地址的,对于返回包的目标地址其实就是正向包的源地址,路由查找无疑是可以通过的,但是路由前由于还不知道目标设备,因此不能简单地丢弃,只有到了路由之后确定目标设备非loopback之后才能丢弃。
        以上就是Linux协议栈路由模块对待“火星地址”的逻辑。总结一下就是:
1.对于进入包,若是loopback发来的包则不通过路由查找逻辑,凡是通过路由查找的,都是外来包,路由前知道源地址和目标地址,可以根据目标地址是否loopback而判断是否丢弃;
2.对于发出包,无条件(考虑下路由cache,意义是一样的)都要经过路由查找,路由前不一定知道源地址,即使知道源地址也不能确定目标设备是否是loopback设备,只有通过路由查找(local表命中的目标设备为loopback设备)才可以知道全部信息,因此在路由后,生成路由cache之前判断源地址是loopback地址而却是发往非loopback设备而决定丢弃之。

        现在我们已经知道了大致的逻辑,我们分两步来绕开它们。

0.需要做的工作

我们需要修改NAT内核模块,涉及修改的有$KERL/net/ipv4/netfilter/nf_nat_standalone.c这个文件。

1.绕开对进入的正向包的地址限制

此即绕开ip_route_input_slow的限制,当然修改协议栈代码是最有效的,可是那样需要重新编译kernel,于是我们在DNAT之后就直接将一个dst_entry附着在这个skb上,以便协议栈认为已经查找过了路由而不再经过路由查找逻辑从而绕开上述的第一个限制。由于对于所有被DNAT到127.0.0.1的包都使用同一个dst_entry,因此我们只需要在系统中保留一份即可:
注意,如果是准备在insmod的时候,也就是调用module的init时生成这个rtable的话,大可不必使用GFP_ATOMIC标志。
然后我们在nf_nat_standalone_init中填充其字段:
接下来,我们需要在每一个被DNAT到loopback的数据包上应用上述的rtable,那么在哪里应用呢?很显然是在PREROUTING这个HOOK点的NAT之后了,也就是nf_nat_in钩子函数里面:
以上就完成了正向包的路由前火星地址检查,注意我们并不打算将上述的dummy_rth插入到系统的路由cache哈希表中,大多数的字段也没有填充,这是因为我们从来不想把它用作什么路由cache,也从不想将其删除,也不想被常规的管理,我们只是在NAT模块还在的时候使用它将数据包路由到本地传输层或者以上而已。

2.绕开对返回包的地址限制

和第1点一样,我们也可以通过修改__mkroute_output的源码来做到这一点,然而这不是什么好方法,比较好的方法的思路和第1点类似,那就是通过增加一个rtable来绕开标准的路由查找,然而却比上述第1点要难很多,这是因为这个rtable不能上上