Linux系统如何平滑生效NAT

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介:

1.Linux实现的NAT概述以及问题所在

Linux的NAT基于ip_conntrack。iptables设置的nat规则仅仅对一个流的第一个数据包有效。当然xtables-addons实现的rawnat除外!即便是rawnat,它也必须设置两条规则。真正好的设计是,有个选项可以自定义nat的行为,而不是依赖配置者的能力,否则就可能酿成大祸,在我自身的产品研发以及实施过程中,就曾经碰到了这种情况,同样的用户场景,有些人可以完全HOLD住,有些人则完全不知所措,这正像孬种的语文老师提供给你一本字典,而好的语文老师教你遣词造句一样,经典文章和粗口同样都使用同样的文字!我认为好的设计就应该让人不容易出错,就像对的不那么舍我其谁,起码不会错!
        关于NAT的Linux实现概览我就不说了(不必了解详情,概览即可),可以看源码,如果这都不知道,就别往下看了。现在试想一个场景:
1).Linux box起初什么NAT都没有配置;
2).Linux box或者后面的机器访问一个server,使用的源地址是内网的不可公网路由的地址;
3).数据正常发出,可是别指望会返回任何东西(除了毫无用处的ICMP!);
4).Linux box的ip_conntrack记录了一个syn_sent状态的conntrack记录,期待返回,并且给与了一个X的过期时间;
5).客户端因为没有得到回复,不断重连,不断touch Linux box那脆弱的syn-sent状态的conntrack记录,导致其永不过期;
6).此时即便设置了iptables的nat规则,由于conntrack不再过期,就永远都匹配不到新建流的第一个数据包;
7).上述的过程僵持了下来,数据访问永远不通!

2.问题分析以及好的设计

看一下上述的过程,其实很简单,只要双方有一方让一点,就解决了,比如客户端在尝试N次重连不成功后,指数退避,那就总能touch到Linunx box的conntrack到最长过期时间,从而解锁,或者反过来,Linux box的conntrack发现已经在一个初始状态停留的总时间超过了一个阀值,那么当即释放,也能解决,然而这也不是好的设计,因为这依赖双方对对方行为的足够理解,需要二者联动!这种思想在以太网以及WIFI的CSMA/CD,CSMA/CA中获得了巨大成功,但是并不能用于第七层的数据通信,因为第七层太乱了!
        那么怎么办?我认为好的设计应该是算法本身的平滑!Linux在实现NAT的时候其实偷了一个懒,那就是:如果你第一个包没有匹配到NAT规则,则之后的包我会自动帮你创建一个NAT规则,那就是把你NAT成你自身,相当于不NAT,为了代码的统一,给你创造一个NULL NAT!姑且不分析深层次的理论,这种强制行为本身就是不合理的,我现在不NAT并不是我不想,而是根本就没有我想要的规则,即我匹配不上任何规则!但是按照应用程序的逻辑,虽然路不通,我还是要继续尝试,等待我能匹配到的那条规则,在它到来之前,我会一直尝试,也许我也可能自己放弃,但是那也是我的自由!
        因此,我做的新版NAT的逻辑就在于,只要数据包没有到达ESTABLISH状态,即它是NEW的话,那么就一直都有NAT的机会,但是如果它收到了返回包,即变成了ESTABLISH状态,那么就不管了(这也是我偷的一个懒,你要知道,一大群女人在家里叽叽喳喳是什么滋味)。

3.对Linux内核代码的修改

也许说得不是很恰当,其实我并不需要修改什么内核,我修改的只是Netfilter代码,曾几何时,Netfilter由于其太过强大,合并到了内核。一共就修改了两个文件,不多,但是很精!我从来不回避大段大段的写代码,但是我觉得那是迫不得已才做的,如果有现成的,直接用就是了,能做到对既有代码最少的修改达到最大的效果也是一种能力,我认为,大段大段地写代码和大段大段地整理数学式子根本就不是档次,后者我是赞同的。代码只是一种想法的实现,你要先有想法,然后再实现,只要你有了想法,发现有人替你实现了,那么就直接用!
1).修改$K/net/ipv4/netfilter/nf_nat_rule.c的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经过测试!)                         clear_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);                 }         }         return ret; }
2).修改$K/net/ipv4/netfilter/nf_nat_standalone.c的nf_nat_fn函数
static unsigned int nf_nat_fn(unsigned int hooknum,           struct sk_buff *skb,           const struct net_device *in,           const struct net_device *out,           int (*okfn)(struct sk_buff *)) {        ......         nat = nfct_nat(ct);         if (!nat ) {                 /* NAT module was loaded late. */                //原来的实现就是:只要在confirm之后加载的NAT模块,就不管了!                 if (/*设置一个开关,平滑过渡模式时打开*/ 0 && nf_ct_is_confirmed(ct))                         return NF_ACCEPT;                 nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);                 if (nat == NULL) {                         pr_debug("failed to add NAT extension\n");                         return NF_ACCEPT;                 }         }         switch (ctinfo) {         case IP_CT_RELATED:         case IP_CT_RELATED+IP_CT_IS_REPLY:                 if (ip_hdr(skb)->protocol == IPPROTO_ICMP) {                         if (!nf_nat_icmp_reply_translation(ct, ctinfo,                                                            hooknum, skb))                                 return NF_DROP;                         else                                 return NF_ACCEPT;                 }                 /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */        //只要没有返回包到来,能保证一直都是NEW         case IP_CT_NEW:                 /* Seen it before?  This can happen for loopback, retrans,                    or local packets.. */                 if (!nf_nat_initialized(ct, maniptype)) {                         unsigned int ret;                          if (hooknum == NF_INET_LOCAL_IN)                                 /* LOCAL_IN hook doesn't have a chain!  */                                 ret = alloc_null_binding(ct, hooknum);                         else                                 ret = nf_nat_rule_find(skb, hooknum, in, out,                                                        ct);                          if (ret != NF_ACCEPT)                                 return ret;                         //以下新添加的是要点:                         //如果是已经完全经过此BOX的数据包,且从来没有成功被iptables规则NAT,                         //则继续尝试匹配iptables的NAT规则,因为可能在数据包重传期间,有新的                         //iptables规则加入进来。                         if (nf_ct_is_confirmed(ct)) {                                 struct net *net = nf_ct_net(ct);                                 //如果匹配到了新的规则,则更新tuple在chian中的位置。                                 hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode);                                 hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_REPLY].hnnode);                                 //如果不进行这个del and reInsert操作,那么就会出现返回包无法被转换                                 //成原始包的情形!                                 nf_conntrack_hash_insert(ct);                         }                         //以上没有优化!!优化点在于:只有在非alloc_null_binding调用成功的情况                         //下才会尝试更新tuple的chain位置,否则不做无用功!                 } else                         pf_debug("Already setup manip %s for ct %p\n",                                  maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",                                  ct); ...... }
就这样就改完了,这就不是个事儿!但是我的测试程序比较简单,很可能经不起推敲!

4.测试逻辑

1).socket
2).reuseaddr
3).bind secondary IP address
4);while true;do connect server; if success;then break;fi; done
5).read and write

在上述第5步期间键入iptables规则,将secondary IP转换成primary IP,结果成功。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1268848

相关文章
|
1天前
|
Ubuntu Linux
Linux(Ubuntu)系统临时IP以及静态IP配置(关闭、启动网卡等操作)
请注意,以上步骤是在临时基础上进行配置的。如果要永久保存静态IP地址,通常还需要修改 `/etc/network/interfaces`文件,以便在系统重启后保持配置。同时,确保备份相关配置文件以防止出现问题。
13 1
|
2天前
|
Linux 数据安全/隐私保护
Linux系统忘记密码的三种解决办法
这篇博客介绍了三种在Linux忘记密码时重置登录密码的方法:1) 使用恢复模式,通过控制台界面以管理员权限更改密码;2) 利用Linux Live CD/USB启动,挂载硬盘分区并使用终端更改密码;3) 进入单用户模式,自动以管理员身份登录后重置密码。每个方法都提供了详细步骤,提醒用户在操作前备份重要数据。
|
2天前
|
JSON Unix Linux
Linux系统之jq工具的基本使用
Linux系统之jq工具的基本使用
30 2
|
2天前
|
数据采集 监控 安全
linux系统被×××后处理经历
linux系统被×××后处理经历
|
3天前
|
监控 安全 Linux
Linux系统之安装ServerBee服务器监控工具
【4月更文挑战第22天】Linux系统之安装ServerBee服务器监控工具
41 2
|
3天前
|
缓存 Linux
linux系统缓存机制
linux系统缓存机制
|
3天前
|
存储 Linux Android开发
RK3568 Android/Linux 系统动态更换 U-Boot/Kernel Logo
RK3568 Android/Linux 系统动态更换 U-Boot/Kernel Logo
18 0
|
3天前
|
Linux 开发工具 Android开发
Docker系列(1)安装Linux系统编译Android源码
Docker系列(1)安装Linux系统编译Android源码
7 0
|
4天前
|
资源调度 JavaScript Ubuntu
Linux系统之部署briefing视频聊天系统
【4月更文挑战第21天】Linux系统之部署briefing视频聊天系统
40 2
|
5天前
|
Linux Perl
Linux系统替换字符串常用命令
请注意,`sed`命令可以非常强大,可以根据不同的需求使用不同的选项和正则表达式来进行更复杂的字符串替换操作。
18 0