在《 Linux系统如何平滑生效NAT》中,代码有两处问题。这只是目前发现的,没有发现的还有很多很多,这就是我为何不一开始把代码搞复杂的原因。

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 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)) {
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的更多相关文章

  1. Linux系统修改/etc/sysconfig/i18n文件,桌面无法正常显示

    在Windows环境下使用SSH Secure Shell Client登陆VMware Workstation中Linux系统查询hive表时,中文显示乱码:数字和url显示为NULL,网上说: 1 ...

  2. Linux系统(四)负载均衡LVS集群之NAT模式

    序言 提到LVS,就从章文嵩博士开始吧,反正也不知道如何下笔来写这一篇.章大博士,读博时候创建这个lvs软件项目,但是他提倡开源精神,在用户的建议和反馈中,这个花了他两周时间开发的开源软件不断得到改建 ...

  3. Linux系统(四)LVS集群负载均衡NAT模式

    序言 提到LVS,就从章文嵩博士开始吧,反正也不知道如何下笔来写这一篇.章大博士,读博时候创建这个lvs软件项目,但是他提倡开源精神,在用户的建议和反馈中,这个花了他两周时间开发的开源软件不断得到改建 ...

  4. 1、win10下连接本地系统上的Linux操作系统(分别以Nat方式和桥接模式实现)

    1.win10下连接本地系统上的Linux操作系统(分别以Nat方式和桥接模式实现) 一.准备知识:win10下打开Administrator的方式 在win10操作系统中,Administrator ...

  5. 关于windows和linux系统更换JDK版本后,修改环境变量也无法生效的原因和解决办法

    今天遇到了一个问题: 我linux系统之前安装JDK12,今天将其改成了JDK1.8,并修改了环境变量,但是通过java -version命令显示的依旧是JDK12的版本. 这是因为,当使用安装版本的 ...

  6. Linux实战教学笔记06:Linux系统基础优化

    第六节 Linux系统基础优化 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 基础环境 第2章 使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 第一步:先备份 ...

  7. linux系统日常管理

    笔者在前面介绍的内容都为linux系统基础类的,如果你现在把前面的内容全部很好的掌握了,那最好了.不过笔者要说的是,即使你完全掌握了,你现在还是不能作为一名合格的linux系统管理员的,毕竟系统管理员 ...

  8. Linux系统(五)负载均衡LVS集群之DR模式

    序言 DR模式是lvs集群中三种负载均衡模式的其中一种,那么上一篇中我写啦关于NAT模式的搭建与原理,为什么还要有DR模式与IP隧道模式呢? 首先我们来看3张图.LVS/NAT模式如下图: LVS/I ...

  9. Jenkins:VMware虚拟机Linux系统的详细安装和使用教程

    jenkins:VMware虚拟机Linux系统的详细安装和使用教程 (一) 不是windows安装虚拟机可跳过 1.Windows安装VMware 2.VMware安装linux系统 3.windo ...

随机推荐

  1. 为Fitnesse-20140630定制RestFixture代码

    摘要:Fitnesse插件RestFixture在最新版Fitnesse输出测试结果为html文本,而非html.本博文记录RestFixture定制代码的过程. 准备开发环境 假定你已经正确安装JD ...

  2. 告别where 1=1 最佳方案分享

    已经有2年没有用过where 1=1了,没想到换了家公司后,又让我看到了它.在网络上面搜索了一下,发现没有人提供一个比较好的方案来解决这一问题.很多人说可以让数据库的优化机制去处理,但是,我想对于大部 ...

  3. qt 获取天气的接口

    博客来源:http://blog.csdn.net/lzqwebsoft/article/details/7054045 网站api接口:http://smart.weather.com.cn/wzf ...

  4. 获取iOS设备信息(内存/电量/容量/型号/IP地址/当前WIFI名称)

    1.获取电池电量(一般用百分数表示,大家自行处理就好) 1 2 3 4 -(CGFloat)getBatteryQuantity {         return [[UIDevice current ...

  5. Linux后台运行程序

    Linux后台运行程序 最近写的程序需要部署到Linux服务器上,按照以前的方式,在运行后面增加&,程序会切换为后台运行.但因为Linux一般是通过ssh远程登录的,等到退出当前session ...

  6. labview 中activex的初步使用方法

    1.在前面板放置一个activex容器 2.插入activex控件 3.百度找到这个activex控件的属性和方法介绍 4.程序框图中右键activex控件,创建xxx类的方法或者xxx的属性:act ...

  7. HDU 4432 Sum of divisors (水题,进制转换)

    题意:给定 n,m,把 n 的所有因数转 m 进制,再把各都平方,求和. 析:按它的要求做就好,注意的是,是因数,不可能有重复的...比如4的因数只有一个2,还有就是输出10进制以上的,要用AB.. ...

  8. K - 最少拦截系统

    Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不 能超过前一发的高度.某天,雷达 ...

  9. WinForm中的DataGridView控件显示数据字典方案2

    winform代码分析object数据库 做这部分功能的时候,上网搜索了很多资料,发现很少涉及到这方面的解决方案,找了相关的问题帖子,很多人都叫使用视图去处理,当然,用视图是可以解决这个问题,但是,这 ...

  10. iOS开发-为程序添加应用设置

    一.设置捆绑包 设置捆绑包是应用自带的一组文件,用于告诉设置该应用期望得到用户的哪些偏好设置. 新建设置捆绑包:Command+N,在iOS部分中的Resource,选择Settings Bundle ...