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等它过期,应用并不知情,依然期待下一秒的成功,不断重试,大家就僵持在那里了!

原文链接: https://blog.csdn.net/dog250/article/details/9397943

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

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

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/408863

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午11:05
下一篇 2023年4月26日 上午11:05

相关推荐