Linux系统如何平滑生效NAT-BUGFIX
来源:互联网 发布:淘宝宝贝怎么编辑尺寸 编辑:程序博客网 时间:2024/06/10 17:16
在《Linux系统如何平滑生效NAT》中,代码有两处问题。这只是目前发现的,没有发现的还有很多很多,这就是我为何不一开始把代码搞复杂的原因。
在NAT已经设置进conn的情况下,仅仅打印了一行日志,而判断NAT是否已经设置进conn的nf_nat_initialized实现正是两个bit位,不同的HOOK点判断位不同,而在我的原始实现中,是清除了alloc_null_binding设置的bit位,进而如果是alloc_null_binding将NAT设置到了conn,也算没有匹配到NAT规则,最终继续匹配,这样就达到了“如果开始没有配置任何NAT时已经confirm了数据流,当配置好NAT后能瞬时生效”的目的。
然而却存在一个问题,那就是当原来的NAT规则改变了的时候,无法瞬时生效新NAT规则的问题。这个问题比较难以解决,我先放置一边,即使不考虑这个这个问题,光上述的代码也有问题。何必在alloc_null_binding之后清除bit位呢?直接把“继续查找的逻辑放在那个else打印日志的位置不就可以了么?因此我还原了$K/net/ipv4/netfilter/nf_nat_standalone.c文件的修改,也就是说nf_nat_rule_find函数不必再修改,而只需要修改nf_nat_fn,在else打印日志的地方添加一个goto即可,goto到if (!nf_nat_initialized(ct, maniptype))判断的下一行,其余的修改保持不变。
现在可以考虑已经是ESTABLISHED状态的conntrack的新NAT规则及时生效的问题了,刚才之所以说它比较难,是因为这需要让nf_nat_fn函数知道什么时候有一条新的NAT规则代替了旧的,而这种行为对于计算机而言,最终无非落实到的就是一系列的查找与比较操作,这种事情做下来的话,还不如不再区分NEW与ESTABLISHED状态,干脆每一个数据包都执行一次nf_nat_rule_find算了,最终将完全失效Linux NAT实现的有状态语义以及效率。因此这种高度策略化的配置应该由调用者来决定,所以我的修改方案就是增加一个sysctl参数,如果用户管理员想即使生效被改变的NAT,那么就将该参数设置为非0,以此来改变NAT规则的匹配行为:
如果想对于已经生效NAT的数据流改变NAT策略的话,请设置sysctl_nf_nat_slowpath为1,持续$max_time后,将其设置回0。这是为何?因为不能破坏Linux NAT的原始逻辑以及不能影响效率!那么max_time该怎么选择呢?当然是所有conntrack超时时间的最长者+10了,因为如果这么长时间没有数据包,conntrack超时被释放,下一个到来的包就是NEW了,如果恰好在这段时间期间有包,直接生效新的NAT,加上5到10秒时间作为用户态程序的时钟校正,故而max_time设置的不多也不少。还有问题!
conntrack默认最长的时间是TCP的establish状态,5天之久,这也太久了,因此就将所有的conntrack超时时间都缩短,最长的时间缩短为120秒,同时将nf_ct_tcp_be_liberal和nf_ct_tcp_loose两个sysctl参数设置为1,撤销TCP的详细语义。
本来我还想做的更完美一些的,就是说将delete/insert操作全部pending到一个RCU序列里面,因为我怕在delete和insert之间的空隙,同一流的数据包进入协议栈的conntrack_in,发生了find操作,这样就会认为没有找到tuple,进而重建一个NEW状态的conntrack,所以我想用RCU锁进行保护,保证在没有任何执行绪find这个哈希的时候,才进行delete/insert操作,毕竟find操作真的是RCU锁保护着呢!然而很快我就发现自己杞人忧天多此一举了,如果发生上述的情况,新创建的NEW状态的conntrack在离开协议栈的时候是不会被成功confirm的,因为在confirm的时候会进行一次find,如果已经find到了,就DROP!如果进入confirm的时候,find之前,被delete的node还没有insert进去怎么办?如果是我最初的那个版本,就完蛋了,然而刚刚进行了bugfix,不是有nf_conntrack_lock保护吗,所以上面的情况是不会发生的。
1.一个bug附带一个优化:
注意以下的代码:if (!nf_nat_initialized(ct, maniptype)) { //NAT还没有设置进conn的情况 ... } else //NAT已经设置进conn的情况 pf_debug("Already setup manip %s for ct %p\n", maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", ct);
在NAT已经设置进conn的情况下,仅仅打印了一行日志,而判断NAT是否已经设置进conn的nf_nat_initialized实现正是两个bit位,不同的HOOK点判断位不同,而在我的原始实现中,是清除了alloc_null_binding设置的bit位,进而如果是alloc_null_binding将NAT设置到了conn,也算没有匹配到NAT规则,最终继续匹配,这样就达到了“如果开始没有配置任何NAT时已经confirm了数据流,当配置好NAT后能瞬时生效”的目的。
然而却存在一个问题,那就是当原来的NAT规则改变了的时候,无法瞬时生效新NAT规则的问题。这个问题比较难以解决,我先放置一边,即使不考虑这个这个问题,光上述的代码也有问题。何必在alloc_null_binding之后清除bit位呢?直接把“继续查找的逻辑放在那个else打印日志的位置不就可以了么?因此我还原了$K/net/ipv4/netfilter/nf_nat_standalone.c文件的修改,也就是说nf_nat_rule_find函数不必再修改,而只需要修改nf_nat_fn,在else打印日志的地方添加一个goto即可,goto到if (!nf_nat_initialized(ct, maniptype))判断的下一行,其余的修改保持不变。
现在可以考虑已经是ESTABLISHED状态的conntrack的新NAT规则及时生效的问题了,刚才之所以说它比较难,是因为这需要让nf_nat_fn函数知道什么时候有一条新的NAT规则代替了旧的,而这种行为对于计算机而言,最终无非落实到的就是一系列的查找与比较操作,这种事情做下来的话,还不如不再区分NEW与ESTABLISHED状态,干脆每一个数据包都执行一次nf_nat_rule_find算了,最终将完全失效Linux NAT实现的有状态语义以及效率。因此这种高度策略化的配置应该由调用者来决定,所以我的修改方案就是增加一个sysctl参数,如果用户管理员想即使生效被改变的NAT,那么就将该参数设置为非0,以此来改变NAT规则的匹配行为:
static unsigned intnf_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)) {retry:renew: 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, but retry\n", maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", ct); goto retry; } break default: /* ESTABLISHED */ NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY)); //对于ESTABLESHED状态的连接在sysctl参数为1的状态下,强行进行NAT规则查找! if ((ctinfo == IP_CT_ESTABLISHED && ctinfo != (IP_CT_ESTABLISHED+IP_CT_IS_REPLY)) && nf_nat_slowpath) { pf_debug("Already setup manip %s for ct %p, but renew\n", maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", ct); //换个名字! goto renew; } ...}
如果想对于已经生效NAT的数据流改变NAT策略的话,请设置sysctl_nf_nat_slowpath为1,持续$max_time后,将其设置回0。这是为何?因为不能破坏Linux NAT的原始逻辑以及不能影响效率!那么max_time该怎么选择呢?当然是所有conntrack超时时间的最长者+10了,因为如果这么长时间没有数据包,conntrack超时被释放,下一个到来的包就是NEW了,如果恰好在这段时间期间有包,直接生效新的NAT,加上5到10秒时间作为用户态程序的时钟校正,故而max_time设置的不多也不少。还有问题!
conntrack默认最长的时间是TCP的establish状态,5天之久,这也太久了,因此就将所有的conntrack超时时间都缩短,最长的时间缩短为120秒,同时将nf_ct_tcp_be_liberal和nf_ct_tcp_loose两个sysctl参数设置为1,撤销TCP的详细语义。
2.一个bugfix
以上代码的hlist_nulls_del_rcu,nf_conntrack_hash_insert等链表操作涉及到了增和删,一定要有锁保护,但是我的代码中没有,这就是一个明显的BUG,因此需要nf_conntrack_lock锁的保护:if (nf_ct_is_confirmed(ct)) { struct net *net = nf_ct_net(ct); spin_lock_bh(&nf_conntrack_lock); //如果匹配到了新的规则,则更新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); spin_unlock_bh(&nf_conntrack_lock);...
本来我还想做的更完美一些的,就是说将delete/insert操作全部pending到一个RCU序列里面,因为我怕在delete和insert之间的空隙,同一流的数据包进入协议栈的conntrack_in,发生了find操作,这样就会认为没有找到tuple,进而重建一个NEW状态的conntrack,所以我想用RCU锁进行保护,保证在没有任何执行绪find这个哈希的时候,才进行delete/insert操作,毕竟find操作真的是RCU锁保护着呢!然而很快我就发现自己杞人忧天多此一举了,如果发生上述的情况,新创建的NEW状态的conntrack在离开协议栈的时候是不会被成功confirm的,因为在confirm的时候会进行一次find,如果已经find到了,就DROP!如果进入confirm的时候,find之前,被delete的node还没有insert进去怎么办?如果是我最初的那个版本,就完蛋了,然而刚刚进行了bugfix,不是有nf_conntrack_lock保护吗,所以上面的情况是不会发生的。
- Linux系统如何平滑生效NAT-BUGFIX
- Linux系统如何平滑生效NAT
- Linux系统如何平滑生效NAT
- Linux系统如何平滑生效NAT-DNAT改进以及解释
- 虚拟机如何配置linux系统nat上网
- Linux的NAT即时生效问题
- bugfix
- linux系统中linux虚拟机NAT模式如何连网
- 终于搞定Linux的NAT即时生效问题
- 修改完linux bashrc文件之后,如何不重启系统使其生效
- android系统里的配置文件如何生效
- linux系统java环境变量不生效
- 修改 Linux /etc/profile 以后如何生效
- Linux下如何使环境变量文件生效
- linux如何让环境变量永久生效
- linux系统做NAT网关服务器
- 如何使windows系统环境变量的改变即时生效
- Linux系统环境变量和别名设置(永久生效和临时生效)
- hdu1575之矩阵快速幂入门
- 软件工程之开发模型及其选择
- mmc线性0-1规划问题
- 畅通工程-并查集-hdu 1232
- hdu4099(斐波纳契数列+字典树)
- Linux系统如何平滑生效NAT-BUGFIX
- 在世界坐标系中放置物体SetTransform函数
- quagga 中 命令框架
- 如何学习IOS
- 设计模式之装饰模式(Decorator)
- java学习笔记(一)
- C是只能使用标量的语言
- [日常学习]灵感来自MergeSort的求逆序数...
- maven2下pom.xml的json-lib的配置