`
hulianwang2014
  • 浏览: 690527 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

Linux系统如何平滑生效NAT-DNAT改进以及解释

 
阅读更多
在《Linux系统如何平滑生效NAT》中,我介绍了如何在Linux中让NAT瞬间生效的patch,提到了那个patch只在SNAT环境中测试过,没有在DNAT环境中测试过,实际上,DNAT中也是可以使用的,只需要将nf_nat_rule_find做以下修改即可:
int nf_nat_rule_find(struct sk_buff *skb,
                     unsigned int hooknum,
                     const struct net_device *in,
                     const struct net_device *out,
                     struct nf_conn *ct)
{
        struct net *net = nf_ct_net(ct);
        int ret;

        ret = ipt_do_table(skb, hooknum, in, out, net->ipv4.nat_table);

        if (ret == NF_ACCEPT) {
                if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum)))
                {
                        /* NUL mapping */
                        ret = alloc_null_binding(ct, hooknum);
                        //Linux偷了个懒,我不偷懒!我并不把alloc_null_binding
                        //作为成功的NAT,因为它只是一个小技巧,为了避免常见
                        //的NULL指针!因此我清除DONE位,表示以后可能还是会继续
                        //尝试NAT(仅仅对SNAT经过测试!)
                        if (hooknum == NF_INET_PRE_ROUTING) {
                //如果是PREROUTING中没有找到NAT规则,则clear SNAT标志
                                clear_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);
                        } else if (hooknum == NF_INET_POST_ROUTING) {
                //如果是POSTROUTING中没有找到NAT规则,则clear DNAT标志
                                clear_bit(IPS_DST_NAT_DONE_BIT, &ct->status);
                        }
                }
        }
        return ret;
}
上述的所有修改均没有加锁,不知道会不会有影响,特别是在conntrack将被timeout或者event删除的边界上,不过我自己在LoadRunner下做稍微大的压力测试,没有问题,所以也就不引入lock了,我的测试机拥有4个核心,内核开启了内核抢占...

一点解释:

在Netfilter的conntrack中,有个概念特别重要,那就是tuple,一个tuple就是一个五元组:
{
source address;
destination address;
source port;
destination port;
protocol;
} tuple;
{
original tuple;
reply tuple;
} conntrack;

一个流由两个tuple构成,一个正方向的tuple,一个反方向的tuple,比如以下一个连接:
1.1.1.1:1234-TCP->2.2.2.2:4321
正方向的tuple就是:
{1.1.1.1,2.2.2.2,1234,4321,TCP}
反方向的tuple就是:
{2.2.2.2,1.1.1.1,4321,1234,TCP}
之所以要有反方向的tuple,就是因为当数据包返回时经过BOX的时候,BOX可以将识别该tuple,然后对应到一个流。
我们来看一下NAT对tuple的影响,首先看一下DNAT,DNAT虽然发生在路由之前,不管怎么说也是在流识别之后,数据包进入IP层的第一件事就是根据五元组来识别一个tuple,而此时DNAT还没有发生,那么很显然该tuple就是原始的五元组,紧接着发生了DNAT,对目标地址进行了修改,这件事的效果就是对reply tuple产生了影响,因为reply tuple的源就是original tuple的目标,因此上面的例子,如果将目标转换为了3.3.3.3,那么它的正方向tuple保持不变,反方向tuple变成了:
{3.3.3.3,1.1.1.1,4321,1234,TCP}
仅仅reply tuple发生了改变,因此需要做的就是仅仅将reply tuple从tuple哈希表删除,重新计算哈希值,入队即可!
下面看一下SNAT,实际上情形和DNAT一样,SNAT发生在数据包离开BOX之前,效果是对源地址进行改变,因此在数据包刚刚进入BOX的时候,tuple不会改变,因此其源tuple保持不变,改变的依然是reply tuple,original tuple的源就是reply tuple的目标,因此上述例子,如果源地址变成了4.4.4.4的话,其reply tuple将会变成:
{2.2.2.2,4.4.4.4,4321,1234,TCP}
和DNAT一样,仅仅需要将reply tuple重新计算哈希值并插入即可。
综上,在NAT发生后,不管什么NAT,都不要操作其original tuple,都需重新计算reply tuple的哈希。可是看Linux的实现,上述这么显而易见的事实,在实现的时候因为过于追求和谐,竟然引出了alloc_null_binding这样的事情,我觉得这可能和conntrack的接口有关,因为conntrack并没有导出诸如单tuple入队,计算哈希等接口,极少的几个接口之一就是nf_conntrack_hash_insert,而这个接口是将两个方向的tuple都入队。因此我没有办法,也只能将两个tuple都出队,再入队了,实际上根本就没有这个必要。要是接口提供得再方便一点,我会直接将alloc_null_binding调用删除,另行实现延迟NAT,当下的NAT实现中,NAT结构体的填充必须在confirm之前,confirm全权包揽了所有的tuple入队操作,所以为了都有两个tuple,即便根本就不需要做NAT,也要伪造一个:alloc_null_binding...一旦conntrack tuples被confirm了,后面再操作它就难了,只能等待其过期或者人工删除,否则就永远无法操作它,要是连续触动这个conntrack,就别想让它过期了,Netfilter等它过期,应用并不知情,依然期待下一秒的成功,不断重试,大家就僵持在那里了!
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics