一点牢骚和希望

一直以来,一直对Linux的NAT很不满,也写过《Linux系统如何平滑生效NAT》系列文章中的patch进行修补,还写过一些类Cisco实现的patch,然而都效果不大好,暴雨的夜晚,长假的倒数第二晚,虽然没有10月7日晚雨量大,可是10月6日晚上到7日凌晨,上海嘉定那边的雨也可以堪称暴雨了。一直想看却一直没有时间看的《斯巴达克斯 第三季》终于看完了,雨越大越兴奋,可是巴拉巴西的《链接》也看完了,《罗马人的故事》最后一卷也看完了,《黑天鹅》还没有到货,剩下的只有写点代码了...于是半瓶竹叶青陪我到天蒙蒙亮,修改了几个内核代码文件,debug了几小时,小睡了两小时后,起床去买新鲜的肉类和蔬菜以及海鲜,因为长假最后一天要一起在家吃火锅。火锅很爽,外面大雨如注,屋里热气腾腾...就这样,雨一直下到第二天早上。
        10月7日第一天上班,我正常出门,可是到了公司已经12点多,在上地铁的时候,涉水到膝盖,转弯,突然发现暗黄色漂浮物,臭气迎面而来,素不相识的一行人为了安全走在了一起,我在头阵...听说是附近的厕所出问题了,污水粪便就从地下涌了上来...继续前行,停下,还是回头?如果只有我一人,我肯定回头了,然而后面有俩MM,还挺时尚漂亮,都说要过去,然后再洗,另外一位本来应西装笔挺的正装哥们儿由于裤子太合身无法挽起,执意也要先走过去再说...我如此邋遢的该如何,可想而知...真心不想趟这浑水啊!!...
        这么多和工作无关的琐事,我太罗嗦了。步入正题了!

alloc_null_binding的作用

Linux的NAT实现是基于ip_conntrack的,这句话已经不知道说了多少遍。一切均实现在Netflter的HOOK函数里面,其逻辑一点也不复杂,然而有意个小小的要点,那就是:即使没有匹配到任何的NAT规则的和NAT无关的数据流,也要针对其执行一个null_binding,所谓的null_binding就是用其原有的源IP地址和目标IP地址构造一个range,然后基于这个range做转换,这看似是一个无用的东西,其实还真的有用。
        用处在哪里呢?注意null_binding只是不改变IP地址,其端口可能要发生改变。为何要改变和NAT无关的数据流的端口呢?因为和NAT有关的数据流可能为了五元组的唯一性已经将和NAT无关的数据流的某个端口给占用了,这就影响了和NAT无关的数据流五元组的唯一性。由于ip_conntrack是不区分是否和NAT有关的,而NAT操作要改变五元组,为了整个conntrack的五元组都是唯一的,哪怕只有一个数据流执行了NAT,也可能占用了某个其它数据流的五元组要素,进而引发连锁反应,所以全部要执行唯一性检测和更新,alloc_null_binding就是为了做这个操作。

彻底消除流头匹配NAT的概念

要是没有深入研究过Linux的NAT,只是仅仅会配置它的话,也许你还真的不知道NAT规则只对一个流的第一个起作用,确切的说,是只针对一个流的ip_conntrack结构体刚刚建立还没有confirm的时候起作用,因为有时ip_conntrack结构体会过期。只要这样的包离开了协议栈,流就被confirm了,接下来的属于同一个流的其它数据包就直接使用上述那个包的保存在ip_conntrack结构体中的NAT结果了。
        正是由于这个特点,使得你无法中途添加NAT规则使之立马生效或者修改已有的NAT结果。这种有状态的特性带来了很多的问题。之前写过《Linux系统如何平滑生效NAT》系列文章,做过一些修正补丁。然而那些补丁的问题在于:它们还是基于流头匹配NAT规则的小修小补。我们知道,这种小修小补最终的结果就是不可维护,那么何不来一个颠覆,即,不再采用流头匹配NAT的原则,改为想什么时候匹配就什么匹配的原则。这其实是一种更高层次的颠覆,即流头匹配原则是新的匹配原则的一种特例。
       废除了流头匹配原则后,我决定把何时执行NAT的决定权留给应用程序,因此我决定注册一个sysctl变量,当其非0时执行NAT,不管是不是已经confirm了。

什么时候需要匹配NAT规则

既然说流头匹配原则不好,会带来问题(比如confirm的连接由于没有NAT而僵持在那里的问题),那么肯定要指出何时执行NAT匹配是必要的,这叫有破有立。在以下的情况下,执行NAT是必要的:
1.数据流连接时,由于还没有做NAT而导致久久连不上的情形。此时数据流的CT状态依然是NEW;
2.数据流已经成功连接,但是需要改变一下源地址(改变目标地址意味着重新连接一个新的服务)。此时的数据流的TC状态是ESTABLISHED;
3.数据流已经经过NAT连接,但NAT规则改变了。此时的数据流的TC状态是ESTABLISHED;

哪些情况不能执行NAT

并不是所有的以上情况都适合执行NAT匹配进而执行NAT,我们不光要考虑双向五元组标示的ip_conntrack本身,还要考虑协议本身的语义。我们看一下TCP协议,由于TCP严格根据五元组维持一个既有的连接,修改任何因子都意味着连接不复存在。因此:
1.对于TCP之类的有连接4层协议而言,只有NEW状态的数据流才能执行NAT,非NEW状态意味着已经收到目标的反馈,执行NAT没有意义;
2.一个流的其中一个数据包已经做好了NAT,并且NAT规则没有改变的情况,此时反向五元组已经被改了,没有必要每次都去匹配一遍NAT规则表;

能做和不能做

对于能做的事情,一般而言你不做也可以,就是你可以做也可以不做,但是对于不能做的事情,基本就是严禁了,如果你做了,就会带来严重的后果或者即使没有严重的后果也完全是无用功,世界就是这么的不对称,有时点到为止,总是功不抵过!因此对于以上两个小节,‘什么时候需要匹配NAT规则’中的一些点,我把控制权交给了应用程序,因此导出了一个sysctl接口,而对于‘哪些情况不能执行NAT’中的情形,则由内核来控制。

代码实现

以上的所有落实下来的话就是代码了,我没有将标准的patch贴到文章,因为那是打patch的时候给程序看的,如果让人看,一大堆的+++---的肯定很扰乱视线,因此我换了一种方式,即 //包围的为我添加的代码段, /########包围的为我修改的代码段。本小节的结构为:
{{文件名\n代码段\n总体说明},...}:

include/net/netfilter/nf_nat.h

//避开ip_conntrack_status枚举成员即可,然而13可谓一个重量级的数字
#define NF_FORCE_NAT_BIT 13

说明:增加了一个新的CT状态,用来指示是否要做NAT匹配。
include/net/netfilter/nf_conntrack_l4proto.h

struct nf_conntrack_l4proto
{
...int (*can_force_nat)(struct nf_conn *ct, struct sk_buff *skb);
...
}

说明:nf_conntrack_l4proto结构体增加了一个can_force_nat回调函数,将判断是否能重新执行NAT的决定权交给4层协议自己而不是在ip_conntrack以及nat逻辑中为之代劳。
net/netfilter/nf_conntrack_proto_tcp.c

//
static int nf_ct_can_force_nat(struct nf_conn *ct, struct sk_buff *skb)
{//没什么好说的...return 1;
}
//
...
struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp4 __read_mostly =
{
...
//.can_force_nat          = nf_ct_can_force_nat,
//
...
};

说明:添加了nf_ct_can_force_nat回调函数,指示在ESTABLISH状态不能重新执行NAT。
net/ipv4/netfilter/nf_nat_standalone.c

//
#ifdef CONFIG_SYSCTL
//增加用户态的sysctl接口,位于/proc/sys/net/ipv4/netfilter/nf_force_nat
static struct ctl_table_header *nat_sysctl_header;
static unsigned int nf_force_nat __read_mostly = 0;
static struct ctl_table nf_nat_sysctl_table[] = {{.procname       = "nf_force_nat",.data           = &nf_force_nat,.maxlen         = sizeof(unsigned int),.mode           = 0644,.proc_handler   = proc_dointvec_jiffies,},{.ctl_name       = 0}
};
#endif
//
...
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 *))
{
...
//
#ifdef CONFIG_SYSCTLif (nf_force_nat !=0) {set_bit(NF_FORCE_NAT_BIT, &ct->status);} else {clear_bit(NF_FORCE_NAT_BIT, &ct->status);}
#elseclear_bit(13, &ct->status);
#endif
//switch (ctinfo) {case IP_CT_RELATED:
...case IP_CT_NEW:
/########
//增加一个标签
renat:/* Seen it before?  This can happen for loopback, retrans,or local packets.. *///增加一个允许NAT的可能性if (!nf_nat_initialized(ct, maniptype) || test_bit(NF_FORCE_NAT_BIT, &ct->status))
/########{unsigned int ret;
...default:/* ESTABLISHED */NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
//if (test_bit(NF_FORCE_NAT_BIT, &ct->status)) {struct nf_conntrack_l3proto *l3proto;struct nf_conntrack_l4proto *l4proto;unsigned int dataoff;u_int8_t protonum;int ret;l3proto = __nf_ct_l3proto_find(NFPROTO_IPV4);ret = l3proto->get_l4proto(skb, skb_network_offset(skb),&dataoff, &protonum);l4proto = __nf_ct_l4proto_find(NFPROTO_IPV4, protonum);/***      实际上本来就应该由四层协议本身来决定是否可以强制NAT,*      但是那样就要修改conn层的回调*/if (l4proto->can_force_nat == NULL ||!l4proto->can_force_nat(ct, skb)){goto renat;}}
//}
...
}
...
static int __init nf_nat_standalone_init(void)
{
...
//
#ifdef CONFIG_SYSCTLnat_sysctl_header = register_sysctl_paths(nf_net_ipv4_netfilter_sysctl_path, nf_nat_sysctl_table);if (nat_sysctl_header == NULL) {printk("nf_nat_init: can't register nat_sysctl");goto cleanup_rule_init;}
#endif
//return ret;cleanup_rule_init:
...
}
static void __exit nf_nat_standalone_fini(void)
{
//
#ifdef CONFIG_SYSCTLunregister_sysctl_table(nat_sysctl_header);nat_sysctl_header = NULL;
#endif
//
...
}

说明:为NAT的Netflter的HOOK函数添加何时执行NAT的判断逻辑。
net/ipv4/netfilter/nf_nat_rule.c

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)) || test_bit(NF_FORCE_NAT_BIT, &ct->status)) {
/########
////如果在ipt_do_table中没有匹配到NAT规则,并且此时允许重新NAT,则说明要把反向五元组还原成原始的反向五元组//本来想在这里做一个优化的,即如果还原了之后,在新的NAT配置上来之前,不再执行还原操作,然而这样会有问题,//注意本文第一节,由于不能保证其它的数据流是否做了NAT从而占据了不该占据的五元组,为了保证唯一性,这里的//alloc_null_binding必须持续调用,唯一可以优化的地方在于可以不用每次都调用nf_ct_invert_tuplepr以及//nf_conntrack_alter_reply,而这只需要一个flag位即可。/* NUL mapping */if (nf_ct_is_confirmed(ct)) {struct nf_conntrack_tuple reply;nf_ct_invert_tuplepr(&reply, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);nf_conntrack_alter_reply(ct, &reply);}
//ret = alloc_null_binding(ct, hooknum);}}return ret;
}

说明:nf_nat_rule_find中如果没有找到规则,则判断是否是将已有规则删除了,进而恢复原始状态。

net/ipv4/netfilter/nf_nat_core.c

unsigned int
nf_nat_setup_info(struct nf_conn *ct,const struct nf_nat_range *range,enum nf_nat_manip_type maniptype)
{
...
/########//bug仅仅onBUG_ON(!test_bit(NF_FORCE_NAT_BIT, &ct->status) && nf_nat_initialized(ct, maniptype));
/########
...nf_ct_invert_tuplepr(&reply, &new_tuple);
//if (nf_ct_is_confirmed(ct)) {spin_lock_bh(&nf_conntrack_lock);hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode);hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_REPLY].hnnode);}
//nf_conntrack_alter_reply(ct, &reply);
//if (nf_ct_is_confirmed(ct)) {nf_conntrack_hash_insert(ct);spin_unlock_bh(&nf_conntrack_lock);}
//
...
//clear_bit(NF_FORCE_NAT_BIT, &ct->status);
//return NF_ACCEPT;
}

说明:在nf_nat_setup_info中判断如果是强制重新执行confirm状态的流的NAT,则重新将其修改过的反向五元组入哈希表。

测试方法

1.以上的代码修改过以后,make,insmod...;
2.ping或者telnet一个不存在的地址;
3.加载iptables规则实现DNAT,将不存在的地址转换为一个存在的地址;
4.echo 1 >/proc/sys/net/ipv4/netfilter/nf_force_nat
5.通了吗?
6.删除那条iptables NAT规则,icmp不通了,telnet仍然通。
同样的方法测试SNAT。

问题

即时这个patch已经朝着perfect前进,它依然无法解决在Linux上简单配置双向静态NAT的问题,它解决的只是随时NAT的问题。那么怎么去支持双向静态NAT呢?目前有一种办法(除了之前写过的那个办法之外)。
        即完全启用nat extension,在添加静态NAT规则的时候,用nat后的已经修改的反向二元组(源/目标IP地址)和正向二元组构造两个个虚拟的nat_conntrack,并将两个二元组插入一个专门的NAT哈希表,这样不管数据从哪个方向发起,在静态NAT的HOOK逻辑(即nf_nat_fn)中,直接去根据自己的源地址去查NAT哈希表,如果找到则取出其反向二元组使用其中的非any地址覆盖nf_conntrack反向五元组的对应位置即可。
        以上设计的本质在于,既然基于matches无法实现双向静态NAT,那么为何不扫除match呢?我们需要的仅仅是下面的推导:
SNAT:    源:A==>源:C
正向:    源A->目标X
反向:    源X->目标C
               ||
               \/
DNAT:    目标C==>目标A
正向:    源X->目标C

反向:    源B->目标X

数据结构如下:

ENUM dir {
    orig,
    reply,
}
tuple {
    address[dir] addrs
}
nat_conntrack {
    tuple[dir] tuples;

}

两个方向的tuple均加入哈希表,永远用正方向的IP二元组去查找,然后取出反向二元组使用。如果以上两个tuple都能在配置NAT规则的时候加入系统,则数据包在nf_nat_fn中就可不用Ipt_do_table调用去匹配NAT规则了,只需要:
1.如果是PREROUTING,则用自己目标IP地址去查询nat_hash,找到tuple后获取对应的nat_conntrack,进而得到反向tuple,然后用反向tuple的源IP地址覆盖掉ip_conntrack的反向五元组的源IP,然后alert reply tuple即可;
2.如果是POSTROUTING,则用自己的源IP地址去查询nat_hash,找到tuple后获取对应的nat_conntrack,进而得到反向tuple,然后用反向tuple的目标IP地址覆盖掉ip_conntrack的反向五元组的目标IP,然后alert reply tuple即可。

Linux系统NAT实现机制的升级改进相关推荐

  1. linux系统内存dump机制介绍(一)--kdump

    本文来自 网易云社区 . kdump的原理介绍 按照linux系统的设计哲学,内核只提供dump内存的机制,用户想要dump什么样的内存,dump多少内存是属于策略问题,由用户来决定. 在真实的使用场 ...

  2. linux系统的安全机制有哪些内容,系统安全机制

    AG351.SELINUX SElinux 是一个强制访问控制系统,它为每个进程与文件都打上一个安全上下文标签,而 selinux 通过这个标 签对系统访问控制进行管理. 2.针对车载产品对于启动安全 ...

  3. 领克linux系统怎么下载软件,新升级的领克车机系统好用吗?我们来盘一下

    提到车机系统,可以说是人们日常用车中常常被忽视的,但又是每天都在接触的配置.一套好的车机系统,不仅仅可以为人们提供丰富的娱乐体验,而且可以为驾驶提供便捷.主打潮流.科技.运动的领克汽车一直以来吸引了无 ...

  4. linux系统python的版本怎么升级,python---linux下升级python的版本

    我的linux系统:centos5.5 我要升级的版本是:2.7.3(目前我更新操作的最新版本[是在2这个系列中的,你也可以安装3系列的]) 升级python的步骤 参考资料如下: 1.下载 wget ...

  5. linux pwm 调屏_嵌入式Linux系统基于PWM机制的液晶屏背光Backlight功能配置

    在研发嵌入式产品时,往往会用到LCD(液晶屏)来显示图形界面,而液晶屏的显示亮度则需要背光系统去调节. 调光方法: 1.数字调光,又称波宽控制调光(Pulse Width Modulation,简称P ...

  6. linux系统下解除yozooffice2016提示升级正式版操作

    重新获得试用操作步骤: 1.注释掉/etc/Yozosoft/Yozo_Office/installinfo.cfg配置文件里 #/home/test/7.0.2439.101ZH.S1  更改目录名 ...

  7. Linux系统SCSI磁盘扫描机制解析及命令实例

    介绍 Linux系统扫描SCSI磁盘有几种方式?Linux新增LUN之后,能否不重启主机就认出设备?如果安装了PowerPath,动态添加/删除LUN的命令是什么?本文总结了Linux主机对磁盘设备进 ...

  8. 给linux内核传递数组,数组与指针 - Linux C编程实战之路_Linux编程_Linux公社-Linux系统门户网站...

    谈到C语言编程,数组和指针是很多人的心头大石,总觉得它们是重点难点,重点是没错的,但绝不是什么难点,要说C语言的难点,客观地讲应该是带参宏,而数组和指针,概念浅显易懂,操作简洁方便,根本不是很多初学者 ...

  9. linux创建云主机内存不足,云主机DC2 Linux系统CPU与内存占用率高导致无法登录

    本文档介绍 Linux 云服务器因 CPU 与内存占用率高导致无法登录等问题的排查方法和解决方案. 登录与查看系统负载登录云服务器.通过第三方软件远程登录 Linux 云服务器( Linux 云服务器 ...

最新文章

  1. SQL Server 下的 获取当月最后一天
  2. IT职场规划和学习方法
  3. 《笑谈银行IT》—畅谈“银行IT系统”
  4. boost::format模块演示添加到 printf 语法的功能
  5. 国防科大JAVA工程师笔试题_国防科大人工智能考博题答案
  6. vmos安卓虚拟手机系统x86_VMOSPro下载-VMOSPro下载v 1.1.26 安卓版-西西软件下载
  7. 【干货】python正则表达式应用笔记
  8. C++ / vs 如何生成自己的静态库(lib)文件
  9. python可不可以同时执行1000个线程_python怎么能同时执行代码(多线程)?
  10. 在多媒体计算机中静态媒体是指,《多媒体技术及应用》按章复习题
  11. 浑水摸「YY」、「侠盗」苹果和辛巴的「麦乳精」|极客一周
  12. 百旺如何看是否清卡_百旺税控盘会自动清卡吗
  13. 对话李国权:新加坡为什么能成为全球Web3.0创业的节点?
  14. fiddler手机抓包教程及电脑断网的配置方法
  15. 二叉树有关的计算机二级选择题,2016计算机二级考试选择题训练及答案
  16. 洛谷P2530 [SHOI2001]化工厂装箱员
  17. cs231n笔记阅读
  18. mybatis-plus使用${ew.customSqlSegment}自定义查询,用数据库不存在的字段作为条件
  19. 第三章 VHDL语言的基本结构
  20. tcp 解决short write问题

热门文章

  1. c# lu分解的代码_LU分解法(C语言)
  2. 一文读懂 MySQL Explain 执行计划
  3. Java继承与多态(抽象类和接口练习)定义抽象类Bank,它包含oneyearRate(一年定期利率)oneyearNationalbebt(一年国债利率)和CurrentDepositRate(按年
  4. 蓝奏云网盘无法访问解决方法
  5. Leetcode刷题100天(阿里云周赛)—查找数组中的所有重复项(哈希)—day42
  6. 【春晚鬼畜】赵本山:我就是念诗之王!
  7. 创造力国际农民丰收节贸易会-万祥军:荒漠土地以色列淘金
  8. sql注入的原理分析
  9. 爬虫期末考试笔记(选择题)
  10. Vue中使用的el-upload开启multiple属性,但onSuccess部分数据status:uploading状态,影响图片回显