面对让人无语的ip_conntrack,我有一种说不出的感觉!自从接触它到现在,已经两年多了,其间我受到过它的恩惠,也被它蹂躏过,被它玩过,但是又不忍心舍弃它,因为我找不到更好的替代。工作中,学习中,用到了ip_conntrack的几乎所有特性,然而这些都不能拿来主义得使用,过程中多少有些美中不足,多少会留下一些遗憾,总结下来,我遇到的典型而非全部的问题如下所列:
1.不能马上生效NAT问题;
2.需要confirm才能用的问题;
3.conntrack cache面对save/restore mark问题
4.双向NAT问题
5.filter表DROP掉的流头包所属的流无法被conntrack
...


这些问题,最终让我”发明“出很多小技巧,以下是我的handle方案:

针对问题1.写出了平滑生效NAT的模块;

这个是我在在《Linux系统如何平滑生效NAT》http://blog.csdn.net/dog250/article/details/9394853中提出的,后续又进行了一些修正。

针对问题2.马上着手解决,本文目的

有时候,你可能希望使用conntrack工具监控新到的数据包,于是你写出了:

conntrack -E -e NEW

但是即使有数据包进来,可能也没有任何事件结果输出,因为这些数据包被filter给DROP掉了,于是这个问题就是问题5。

针对问题3.”提出“依赖condition match和conntrack timeout的慢速匹配模式

我们不能保证总能从save/restore mark的优化中获益,一般而言,我们设置下面的经典规则:

iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle $以下都是逐条的慢速匹配规则,匹配到就打mark


启用了ip_conntrack,任何数据包都要绑定到一个唯一的conntrack,由于ip_conntrack的保存是基于超时时间(UDP,ICMP而言)或者协议(TCP而言)的,因此就会导致在上述规则增加前的一个流的头包过去以后,新增加的依赖ip_conntrack的上述iptables规则不能生效,必须等到该conntrack过期或者协议关闭之后,下一次重新建立conntrack流时才能生效,这会引起很多莫名其妙的问题。通过一个condition match,当新iptables规则被添加时,将其置为0,关闭IPMARK带来的优化,强行让数据包匹配所有的策略。关闭时间设计为(max_timeout+5)秒,其中max_timeout秒为conntrack项过期的最长时间,5秒是一个误差修正值。为了使conntrack项的最长过期时间为max_timeout秒,需要对sysctl参数进行conntrack的timeout调整,且需要禁用掉conntrack的协议限制。最终,引入了两种策略匹配模式,在新规则添加时,会进入慢速匹配模式,所谓慢速匹配模式,就是不依赖ip_conntrack流状态的规则完全遍历匹配模式,反之快速匹配模式则是依赖ip_conntrack状态的匹配模式,直接从conntrack结构体中获取上次的匹配结果。

针对问题4.模仿Cisco风格写出了双向静态NAT模块

但是没有和conntrack关联,这是一种遗憾,以至于每个包都不得不去查找静态NAT哈希,其实有更好的方案,那就是和conntrack关联,新增一个static NAT的extension加入conntrack,无非就是对两个方向发起的流均查一次static NAT表,而不是查iptables设置的NAT rule表。

针对问题5.马上着手解决,本文目的

ip_conntrack有一个confirm逻辑,即当数据流的头包离开协议栈的时候,会被confirm,只有被confirm的conntrack才会加入到conntrack哈希,目前来讲,离开协议栈的地点有两个,第一个是被forward出去,即从一个网卡发出去,第二个是进入用户态,即被本地socket接收。而被DROP的包不会到达这两个点,所以就不会到达confirm点,进而永远都不会建立conntrack条目,也就是说,被filter DROP的数据流头包代表的整个流都无法使用conntrack的任何特性。此时你可能想到了用save/restore mark的方式,然而这也不行,因为没有被confirm,所以就根本就没有conntrack被加入哈希,试问,你能将mark save到哪里去呢?

        然而这样做是不合理的,要知道,被DROP也是一种离开协议栈的方式啊!实际上也需要confirm的。如此一来,我就可以将”被DROP“这个事实,记录在conntrack的extension里面,然后在下一个包进来的时候,在PREROUTING中取出这个结果,直接DROP而不用再去匹配filter ruleset,提高了效率。到底应该怎么做呢?实际上并不需要做一个extension,仅仅在数据流头包被DROP的时候将其conntrack给confirm一下即可。余下的事情就可以交给IPMARK了,比如可以在mangle表和filter表设置以下的ruleset:

mangle:
iptables -t mangle -I PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle -A PREROUTING -m state --state ESTABLISHED -m condition --condition fastmatch -j ACCEPT
....数百条mangle规则,匹配则打mark
iptables  -t mangle -A PREROUTING  -m mark ! --mark 0 -j CONNMARK --save-mark
filter:
iptables -A FORWARD -m mark ! --mark 0 -j DROP
iptables-A FORWARD -m state --state ESTABLISHED -m condition --condition fastmatch -j ACCEPT
...成百上千条filter规则,匹配则打mark;
iptables -A FORWARD -m mark ! --mark 0 -j CONNMARK --save-mark
iptables -A FORWARD -m mark ! --mark 0 -j DROP


如果说为了判断该数据包是否要DROP掉就去遍历匹配成百上千条规则,那就会大大影响效率,何不用conntrack的policy cache呢?遗憾的是,数据包被DROP导致一个流无法confirm,因此无法使用conntrack的特性,如果被DROP的数据包也能绑定到一个conntrack,那么上述的ruleset就能省大事儿了,需要做的仅仅是在DROP的时候confirm一下而已,代码修改非常简单,我一般喜欢抓住最小交集做最小的改动,需要修改的地方只有两个:

a>第一处修改:$K/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c中增加一个notifier_block,本来我想新增一个HOOK点的,可是为了不把Netfilter搞乱,我还是用了体制外的一种方法,那就是notifier_block,因为我太喜欢Netfilter的设计了,多一点就多了,少一点就残了,所以我不对它的5个HOOK点进行任何拓展和想象,具体的修改方式如下:

//初始化list
BLOCKING_NOTIFIER_HEAD(conn_notify_list);
EXPORT_SYMBOL(conn_notify_list);
//定义notifier_block
static struct notifier_block nf_filter_drop_notifier = {
.notifier_call = nf_confirm_handler,
};
//在nf_conntrack_l3proto_ipv4_init中注册这个notifier_block
blocking_notifier_chain_register(&conn_notify_list,
&nf_filter_drop_notifier);

b>第二处修改:$K/net/ipv4/netfilter/ip_tables.c的ipt_do_table的最后几行修改一下:

#ifdef DEBUG_ALLOW_ALL
return NF_ACCEPT;
#else
if (hotdrop)
return NF_DROP;
else return verdict;
#endif

改为:

#ifdef DEBUG_ALLOW_ALL
return NF_ACCEPT;
#else
if (hotdrop || verdict == NF_DROP) {
blocking_notifier_call_chain(&conn_notify_list, hook, skb);
return NF_DROP;
}
else return verdict;
#endif

最后给出nf_confirm_handler的实现:

static int
nf_confirm_handler(struct notifier_block *this, unsigned long hook, void *argv)
{
struct sk_buff *skb = (struct sk_buff *)argv;
switch (hook) {
case NF_INET_FORWARD:
case NF_INET_LOCAL_IN:
//本应该将in,out,skb等封装在一个struct里面传过来的,但是希望早点看到结果
//就省略了,反正就用到一个skb而已,其它的暂时不管了。
ipv4_confirm(hook, skb, NULL, NULL, NULL);
break;
default:
break;
}
return NOTIFY_DONE;
}

另外别忘了声明一下这个conn_notify_list,我是在#include <linux/netfilter_ipv4.h>中声明的:

extern struct blocking_notifier_head conn_notify_list;

编译十分顺利,用起来效果也十分明显,在5000条iptables规则下,性能几乎没有任何损耗,而且也没有报任何错误,因为它简单,所以它好用。
结语:conntrack主导下的整体方案(和硬件接口)

我看过BSD的Netgraph,接口要比Netfilter的好用,于是我就想把Netfilter的HOOK函数的调用机制更改一下,不再用协议栈调用,而是通过event的方式来触发!在所有的HOOK的所有event中,都可以统一使用ip_conntrack,即一切策略均可以保存在conntrack,实现数据流头包的一次匹配,后续包的conntrack查找,策略提取,动作执行的序列化操作。

        基本思想就是“一切均可缓存在conntrack”,其中可以缓存的类别包括:

转发策略:接受还是丢弃
路由策略:从哪个接口发出
NAT策略:如何实现地址转换
流控策略:和流控相关的配置
封装策略:VPN或者GRE
感兴趣流匹配策略:是否感兴趣流


以上的这些都可以仅仅针对一个流的流头进行匹配,然后将结果cache到conntrack,后续的包就不必再去匹配了,而是仅仅需要查找到对应的conntrack,取出conntrack中缓存的策略,直接使用,本文以上的DROP notifiler就是为实现这个目标完成的第一步。现在我们看看现有的Netfilter有什么问题,其实没有什么问题,一切都很好,唯一的问题是可能很多人都不知道如何从一个HOOK点直接调用另一个HOOK点的HOOK函数,人们认为只有协议栈才可以调用HOOK点,也就是说被PRE/POST/IN/OUT/FORWARD等字面意义迷惑了,而实际上,NF_HOOK是在任何地方都可以调用的,看看bridge的实现,bridge-call-iptables就是这么玩的,到处都是从一个HOOK点函数直接调用NF_HOOK的例子。

        好了,有了这个前提,我所谓的event机制其实早就已经有模板了,无非就是在一个HOOK函数中去触发另一个HOOK的函数,所以改变的就是一个名字,一个HOOK函数就是一个event!下面是一个例子。我们可以仅仅针对一个流头进行路由查找,然后将dst_entry缓存在conntrack里面,后续的包只要对应到该conntrack,就可以取出dst_entry,此时就不必继续进入PREROUTING-ROUTING-FORWARDING...了,而是直接触发POSTROUTING的转发事件,直接转发,这样其实也就类似BSD的Netgraph了,只是名字叫法不一样而已。Netfilter其实早就把框架给搭好了,现在需要做的就是写几个HOOK函数而已,当然,并不一定非要和iptables关联,和procfs,sysfs等关联均可,无非就是一个内核态和用户态的沟通方式而已!

        最后是一个硬件接口问题,我之所以考虑上面那些,就是为了提高效率,要说效率,再好的算法也比不上三流算法注入的硬件,以上的想法和硬件结合是最好的了,所有的查表,转发等全部由硬件实现。由于硬件不灵活,所以更是需要接口的灵活,可以互相调用构成一张图,这才能发挥硬件最大的作用。

        所有的查询全部归为conntrack的查询,流头慢速匹配规则,然后缓存到conntrack,或者慢速匹配模式(见上述的condition match实现的慢速匹配)匹配非流头,然后缓存到conntrack,总是,二者是不见不散,类似Linux kernel的dirver和device之间的probe关系一样。

我和ip_conntrack不得不说的一些事的更多相关文章

  1. 【原创】关于DNS不得不说的一些事

    引言 今天我们来聊聊DNS. 所谓域名系统(Domain Name System缩写DNS,Domain Name被译为域名)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据 ...

  2. 【转】关于DNS不得不说的一些事

    转自:https://www.cnblogs.com/rjzheng/p/11395695.html 引言 今天我们来聊聊DNS.所谓域名系统(Domain Name System缩写DNS,Doma ...

  3. Session与Cookie间不得不说的一些事

    在很久很久以前,刚有浏览器和网页的时候,web开发者发现了一个问题,我必须要在客户端这边保存一些东西才能实现某些功能,比如大家喜闻乐见的购物车.用户登录.自动登陆等.但是客户端只有一个浏览器,怎么在用 ...

  4. String不得不说的那些事

    一.String.StringBuilder和StringBuffer的区别 1. String是字符串常量,StringBuilder和StringBuffer是字符串变量 String对象创建完成 ...

  5. 镜像仓库管理:与Portus不得不说的那些事

    背景: 目前在做一个云计算相关的项目,其中有这样一个需求:每个平台用户都有自己的docker镜像仓库(docker registry),用户可以对自己的镜像仓库的push/pull权限进行管理,也就是 ...

  6. 关于javaScript事件委托的那些事

    今天是第一次写稿,还是有那么一丢丢小鸡冻...回归正题啦... 关于javaScript事件委托不得不说的那些事,为什么要使用事件委托? 我们可以这么说,假设老板要分配一项任务,首先要秘书叫A君来到办 ...

  7. 开篇&TexturePacker打出图集给UGUI使用

    开篇: 前段时间,网上流出了一套手游源码,本想着把服务器端搭一下,给自己认识小伙伴们调试着把这套源码学习一下.于是就买一个阿里云服务器,可是花了几天时间,就是run不起来了啊.还好网上已经有人搭出来了 ...

  8. SSD(Single Shot MultiBox Detector)的安装配置和运行

    下文图文介绍转自watersink的博文SSD(Single Shot MultiBox Detector)不得不说的那些事. 该方法出自2016年的一篇ECCV的oral paper,SSD: Si ...

  9. cocos2d-x 3.6 mac下的试用(粒子,触摸事件,图片)

    戏说 虽然公司再如何如何,咱程序员在干好课外学习的情况下也是要努力做好本职工作的. 工作中的lua也写多了,深入了解Cocos2d-x当然还是要倒腾倒腾C++,对于一个C#用了这么多年,工作用lua的 ...

随机推荐

  1. 马上着手开发Mac应用程序

    你是否想要开发 Mac 应用程序却又不知道从哪里入手?本路线图提供了 Mac 应用程序开发的绝佳起点,即使你已经是一个 iOS 开发专家,本路线图对你依然适用.Apple让开发应用程序和提交应用程序到 ...

  2. 开通GitHub以及使用笔记

    把小游戏的代码和博客迁移到GitHub上,路径是:https://github.com/GAMTEQ,欢迎访问 以下是使用GITHUB的一些命令 504  cd code 506  mkdir Fai ...

  3. 【跟我一起学Python吧】Python的包管理工具

    刚开始学习Python时,在看文档和别人的blog介绍安装包有的用easy_install, setuptools, 有的使用pip,distribute,那麽这几个工具有什么关系呢,看一下下面这个图 ...

  4. [Hive - LanguageManual] Import/Export

    LanguageManual ImportExport     Skip to end of metadata   Added by Carl Steinbach, last edited by Le ...

  5. Getting Started(Google Cloud Storage Client Library)

    在运行下面的步骤之前,请确保: 1.你的项目已经激活了Google Cloud Storage和App Engine,包括已经创建了至少一个Cloud Storage bucket. 2.你已经下载了 ...

  6. SqlServer修改数据库文件及日志文件存放位置

    --查看当前的存放位置 select database_id,name,physical_name AS CurrentLocation,state_desc,size from sys.master ...

  7. xcode6默认不支持armv7s

    升级到xcode6以后发现,配置里关于Architectures到默认选项只有armv7和arm64.而再次之前xcode5到时代还是有armv7.armv7s和arm64三项的.  xcode5.1 ...

  8. work_7

    1. 理解C++变量的作用域和生命周期 a) 用少于10行代码演示你对局部变量的生命周期的理解 局部变量分为动态局部变量和静态局部变量,其共同点为作用域均为定义它的函数体或语句块,其不同点为其生命周期 ...

  9. Oracle 表数据去重

    Oracle数据库中重复数据怎么去除?使用数据表的时候经常会出现重复的数据,那么要怎么删除呢?下面我们就来说一说去除Oracle数据库重复数据的问题.今天我们要说的有两种方法. 一.根据rowid来去 ...

  10. freemaker自定义分页控件实现

    <link href="${res}/css/pages-jhdb.css" rel="stylesheet" type="text/css&q ...