iptables高性能前端优化-无压力配置1w+条规则

来源:互联网 发布:淘宝代购服务点申请表 编辑:程序博客网 时间:2024/05/17 13:09

产生本文的故事

显然是个周末,这是一个下雨的周末,这是一个台风登陆的周末…

  上周,有一个很猛的台风“天鸽”,当天红警晚发了两个小时,当发布红警时,几乎所有人都到了公司,当然,除了那些怕西装和皮鞋被雨淋湿的经理。我本来是想特别感谢一下这个台风的,然而它已经造成了重大的人员伤亡,无论如何,我都不能再对它多说一句褒义的话,我是一个喜欢风雨的人,仅诠释我个人。

  在台风“天鸽”将至的那晚,我把一个在暴热和加班中被遗忘搁置的问题重新拿了出来,开始了一番探究…

  就在三天后,台风“帕卡”将至,我写了本文。所以我把本文定位为两个接踵而至的台风刺激下写的一篇技术散文,而不是技术指导,技术指导性的文档需要相当的理性,而我现在只有狂风暴雨,和酒。

动机

有人咨询我关于几千条iptables规则造成收包性能急剧下降的问题了,iptables再次背了锅。

  自从2009年底开始,就不断有人天天强调基于Linux内核Netfilter的iptables以及Netfilter本身会造成性能下降,这种声音最后一次听是在昨天下午!

  这么多年来,发出这种声音的很多人甚至根本就理不清iptables和Netfilter的关系,更是对其内部原理一无所知,大部分都是人云亦云,也不知道是谁第一个说的,反正后面就跟着把故事流传了下来。也许第一个这么说的是国外一位大牛,针对特定场景提到了这么一个结论,很多人断章取义就认为这是在否定iptables本身…

  我被很多公司面试过,也面试过很多别人,在我被问及关于Netfilter相关的问题时,我知道他们的意思是想知道如何优化它,天啊,你自己可能都不知道Netfilter的问题在哪,何谈优化,不破哪有立…当我面试别人的时候,我也会问关于Netfilter相关的,但我的问题往往是:你觉得Netfilter有问题吗?

  我不止一次地强调过,iptables没有错,Netfilter也没有错!

  是的,你可能用“几千条iptables规则确实造成了性能下降”,“一打开conntrack性能就会下降”这种来反驳我,但我马上就能怼回去:这是Netfilter的问题吗?然而这是iptables的问题吗?

  规则配的太多导致性能下降是因为你iptables玩的不精!

  conntrack造成性能下降那是conntrack自己的问题,为什么让Netfilter背锅?!

  我之前写过几篇关于nf-HiPAC的文章,它即使配置20000+条规则也不会造成收包性能下降,然而它也是基于Netfilter的。更具讽刺意义的是,你们可能对APPEX(华夏创新)这个公司有所耳闻,是的,它是做加速的,请注意,我在强调“加速”二字,人家的加速竟然用了Netfilter!虽然彼性能不是此性能,一个是TCP的性能,一个是主机CPU性能,但为了榨取哪怕0.1%的CPU性能,如果Netfilter有大问题,APPEX会用它吗?

  即便你认识到了问题出在iptables而不是Netfilter,你还是有别的选择的,比如你可以试试nf-HiPAC,不过我敢保证你不一定能玩得起来,这玩意儿资料很少,中文的估计也就我那几篇了…另外的选择就是用ipset,这个很常规,也很朴素,它只是一个iptables的模块,在你的match是IP地址的情况下,使用ipset完全可以解决几千条规则导致的性能下降问题。

  我知道你不想重写规则,更不想去编译安装什么nf-HiPAC,ipset之类的新东西,你只想保留你的8347条iptables规则,然后用很有点拿来主义经理的口吻问我:别的我不能动,但是性能下降必须控制在10%以内。

  虽然这并不关我毛事,但还是感觉受到了威胁,还好,感谢台风“天鸽”,“帕卡”,让我有了一个新的思路,解除了威胁。

声明:

1.本文的解决方案是纯iptables方案,不涉及nf-HiPAC和ipset,需要了解这些的请参考下面两篇文章:
《可编译易用的模块化nf-HiPAC移植成功》
《于Linux-2.6.32内核上编译ipset-6.23的坎坷经历》

2.本文不是一个关于新技术的HowTo,它只是一个新想法,有很多TODO。如果问这东西的代码在哪里,那只能等温州皮鞋厂老板了。

已有的优化方案

开本文的背景(比如不能修改规则集之类的),如果你确实觉得iptables性能很不行,然后希望优化的话,你做的第一件事可能就是去google或者去百度,你会发现一系列现成的方案,比如:

  • 使用state match

比如你可能会加入下面的一条规则在所有规则最前面:

iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT

你期望着用流而不是数据包来进行匹配,但你可能不知道的是,流依赖了conntrack机制,而该机制也是饱受诟病的因素之一。

  • 使用自定义chain

在很多论坛上,有很多人早就提出了使用-N选项创建不同的chain,然后肉眼归类所有的规则,尽量把具有同一性的规则分配到单独的chain中,这样整个规则集就分层了。
  这正是本文要描述的模型的朴素版本。

  • 使用nf-HiPAC或者ipset

这个就不多说了,效果相当好!

这里给出一个关于iptables性能测试的链接,可以关注一下:
《Netfilter Performance Testing》
值得一提的是,在这篇文档中,提及了一些优化手段,其中包括一个叫做Compact Filter (CF)的项目,我觉得是比较有意思的,然而其链接已经失效,后来找到了一篇论文:
《An Interval Decision Diagram Based Firewall》
这个设计正是采用区间匹配来快速定位Action的,跟我将要通篇描述的方案几乎是一致的,感兴趣的可以看一下。

其实,iptables如何优化,方案几乎也就两类:

1.重写内核过滤机制

比如nf-HiPAC,ipset之类就属于这类。

在用户态优化规则集

比如Compact Filter (CF),精心设计自定义chain跳转,以及本文描述的n+1模型就属于这类。

  如果上面这些你都觉得太简单或者说已经看过了了解过了,那么本文剩余的部分将要介绍的这个可能是你第一次听说。

优化方案补遗

在我终于完成了这篇冗长的文章后,我顺势google了一下关于iptables优化的一些枝枝蔓蔓,希望能找到一些好玩的东西,如果能找到,那简直是比吃一顿大餐还要更爽的,加上外面狂风暴雨,舒坦地难以言表。

  我还是找到了,我找到了下面这个:
《Optimizing the Interval Decision Diagram Im-plementation in the CF Firewall》
我不得不说,快速扫完了这个之后,我是激动的(在完成本文前,我一直想找到有关Compact Filter (CF)的资料,但没有找到)。这份资料几乎完整地阐述了一个跟我的n+1模型一模一样的方案的优化版本,同时在这份资料的开头表明了Compact Filter (CF)是一个非常大众化的方案,不然的话,为什么大家都往一起想呢?

  虽说这份资料让我希望成为首先提出类似想法的那批人成为了泡影(同样的方案出现于2005年之前!那时我还才刚刚参加完高考…),然而,令我依然感到欣慰的是,至少我用一个直观的立体几何模型而不是抽象的代数模型来完整地阐释了这个思想,这简直和前端之间写关于BadVPN,无理数本质以及泰勒级数时的方法有着异曲同工之妙啊!

Anyway,让我们开始吧!

性能前端封装

接触iptables已经很多年了,第一次接触是2006年的冬天,然而2006年到2010年底这期间,我也只是听过这个东西并且会简单使用而已,从2010年底开始才逐渐深入。在这些年里,我接触到了太多的Linux防火墙,其中不乏成型的产品。从OpenWRT中简易的Firewall模块到Endian Firewall,随着UTM越来越多的自带了防火墙的功能,Linux防火墙这个词所表示的意义就更加广泛了。

  然而万变不离其宗的是,所有这些都是对iptables的前端封装,有的基于Python,有的用Go,更多的是用Web,所有这些iptables前端都是功能性前端,没有专门的性能前端,这也许是因为在大多数场景下,按照常规方式添加的iptables规则跑得还不错(几乎没有人用iptables去配置几万条规则),至少没有出现严重性能问题,那么大家不关注也是理所当然,万一出了性能问题,那就只能见招拆招。

  然而,功能性前端则完全不同,它是刚需!因为很少有用户愿意去熟悉或者学习iptables命令的用法,人们希望有一个直观的UI,比如WebUI,GUI,用最快最直观的方式去配置防火墙而不是研究防火墙的原理

  本文的思路主要是想做一个很少见的针对iptables的性能前端封装,它串接在功能性前端封装后面,将功能前端的iptables规则输出作为性能前端的输入,最终输出一套优化后的iptables规则集并注入内核。大致的图示如下:

这里写图片描述

可行性

经,每一条iptables规则都是独立的,规则的顺序决定了匹配的顺序,这样的话,内核只能是遍历匹配,因为它看到的就是一条条独立的规则组成的一个规则链,如果你添加了100条规则,那么内核只能先匹配第1条规则,然后第2条,然后第3条…如果足够不幸的话,你要匹配到第100条规则才会发现根本没有一个匹配到的,然后无奈地去执行Default Action

  我记得在我2010年刚刚找到一份新工作入职之后,就从领导那里得到一个信息,如果配置10000条iptables规则,PPS性能会急剧跌落。但当时还不是很懂iptables的原理,所以也只是记住了这个结论,待到后来终于理解了iptables/Netfilter之后,我验证了这个结论,确实如此!

  造成这个结论的原因很简单,就是内核协议栈会针对每一个经过的数据包去逐一遍历这10000条规则…

  如果把所有的iptables规则组成的集合看成是一个整体,这样就可以多维度分析了,比如来个转置什么的。由此,我们就可以更细维度地去分析这些规则的特征和分布了。

  如果是逐条添加的独立的iptables规则,对于孤立的一条条规则,整个过程看起来是下面这个样子:

这里写图片描述

然而,如果我们把所有的规则看成一个整体,就可以对所有的规则进行如下的分析:

这里写图片描述

当然,这里面必然还存在很多的细节,但我们先忽略这些细节,先让我们看看这意味着什么。我们又需要做什么。

  我们可以先不急着把规则一条条添加入内核,取而代之的是,先对所有这些规则进行一次预处理,然后再以一种完全不同的方式注入内核

  谢天谢地,在台风“天鸽”将至的夜里,我想到了办法!(在最终写这篇文章的时候,是台风“帕卡”将至的早上,这会儿已经风雨大作了)

  虽然在视觉上一眼看上去iptables以内置的chain作为集合的方式,但与此同时,iptables提供了用户自定义chain以及相应的goto指令,这就意味着iptables规则不必一定按照线性的平坦组织方式来组织,而完全可以组织成一个树型结构,比如下面的样子:

iptables -A INPUT --match1 a -g self_define_chain1_1iptables -A INPUT --match1 b -g self_define_chain1_2self_define_chain1_1:iptables -A self_define_chain1_1 --match1 c -g self_define_chain1_1_1iptables -A self_define_chain1_1 --match1 d -g self_define_chain1_1_2self_define_chain1_2:iptables -A self_define_chain1_2 --match1 e -g self_define_chain1_2_1iptables -A self_define_chain1_2 --match1 f -g self_define_chain1_2_2...self_define_chain_x_end:iptables -A self_define_chain_x_end --match1 x -j XXX

它对应下面的二叉搜索树:

这里写图片描述

看出点端倪了吗?

  我的意思是说,将按照顺序添加的一条条iptables规则(它们是线性结构)进行一系列的预处理变换,它们就可以被组织成类似上面分层的树形结构,并且效果是等价的。然而,如何做到效果等价的呢?这要通过我的一个模型(该模型与nf-HiPAC以及DxRPro++的模型非常类似)来阐释,请接着读。

  我们知道,iptables处在同一个chain中的规则在内核里是顺序执行的,不同chain中的规则组成了规则树,内核是按照深度优先的原则遍历规则树并执行规则的,因此依然可以看作是顺序执行的,所以,为了在统一的视图中体现这个顺序,必须把顺序这个维度单独抽取出来,这就形成了一个完备的自包含多维超立方体模型

  为了理解这个模型,我得从头说起。再次感谢台风,不然我可能真的要颓废下去了…

一维match匹配模型

从最简单的说起。

  以仅有1个match的规则集为例,比如-s指示的源IP地址,我假设所有的规则都只有-s XXX这一个匹配,这样的规则配置了10000条:

iptables -A INPUT -s 1.1.1.1 -j DROPiptables -A INPUT -s 1.1.1.2 -j DROPiptables -A INPUT -s 1.2.3.6 -j DROPiptables -A INPUT -s 1.2.3.7 -j DROPiptables -A INPUT -s 1.2.3.8 -j DROPiptables -A INPUT -s 1.2.3.9 -j DROP....iptables -A INPUT -s 10.20.30.40 -j DROP

这其中的-s XXX(Source IP address)所示的match单独标识为一个维度,而规则的顺序则标识为另一个维度(这是重点),模型的一个示例图如下:

这里写图片描述

这个模型是非常简单的,并且非常直观,经过该模型预处理过的iptables规则集注入内核后,当数据包到来的时候,将会在规则集内执行二分查找(随便哪种查找算法都行)算法,快速定位到自己该匹配那条规则的Action

这里写图片描述

通过上图的模型,就可以直观地保证采用树型组织后的规则集合和原始的线性规则集合是等价的。我感觉这是非常帅的,事实上,我知道这个思想借鉴于nf-HiPAC,但不管怎样,我可以把iptables本身改造成高效的工具了,不用nf-HiPAC,也不用nftables!它就是iptables本身。爆炸!

  以下开启高能模式。

多个match多维度模型–n+1模型

1维扩展到多维在操作上往往是简单的,难点是你要有想象力。

  如果有n个matches,那么就需要n+1个维度来在模型中刻画现实,其中n个维度刻画的超长方体表示n个matches,每一个维度就是一个match,每条规则的每一个match映射到这n个维度的某个单独的区间,而第n+1个维度则标识了规则的顺序。

  我实在是想象不出一个4维的立体图像如何刻画,能想象的最高只有3维,其中有2个维度是刻画match的,第3个维度刻画规则的顺序,这里n+1中的n为2。我想添加一条规则的过程,用下图表示足够了:

这里写图片描述

这样,添加一条ipables规则就成了一个画图的过程。这很直观,虽然我只是用了2个matches加上一个顺序维度去描述了3维空间里的规则,但多个match表述的多维空间在数学上操作上与这个是一致的,也是很简单的。整个n+1模型用语言描述非常简单,一条iptables规则在这个模型中就是一个n+1维的超立方体,所以这个模型我把它叫做n+1模型,其中n个维度描述n个matches,1个维度描述规则顺序。

  我记得有人写过一篇微信公众号文章(链接不记得了,但应该能搜到),帮人从0维一直理解到11维,写的非常好,至少我是看懂了,我的同事朋友大多数也看懂了,所以我就觉得这些抽象的东西是可以理解的,但这不是本文的主题,不过有时间我一定会专门写一篇有关这个话题的短文的。

实际的例子

以一个图示表述一下上面阐述的我的iptables模型,既然是图示,能画出图的最多3维,因此我用2个维度表示2个match,分别为源IP地址目标IP地址,整个填充后的模型如下:

这里写图片描述

我将其翻转一个角度,同时看看重合区域如何处理:

这里写图片描述

这非常简单。

  现在我可以重复性总结一下了。

  在超立方体中,一条iptables规则就是一个小立方体,其中垂直(应该说是‘正交’)于其它match轴的那个维度边表示规则的顺序,其它的边则表示规则的各个match对应的维度的区间。集合所有的matches构成的n维立方体,将其向上提升m个高度(m为已经添加的规则的数量),就得到了一个n+1维度立方体,这个立方体表示了该条iptables规则的全部!

n+1模型定性分析

来先来展示一组规则:

iptables -A INPUT -s IPorNET_1 -j DROP;iptables -A INPUT -s IPorNET_2 -j DROP;iptables -A INPUT -s IPorNET_3 -j DROP;iptables -A INPUT -s IPorNET_4 -j DROP;iptables -A INPUT -s IPorNET_5 -j DROP;...iptables -A INPUT -s IPorNET_n -j DROP;

如果说,IPorNETx1<=x<=n这n个IP地址区间都是独立的,互相不重叠,那么顺序执行就是没有意义的,毕竟一个数据包的源IP地址只能匹配这n条规则中的一条,不是这个就是那个,如果都不匹配,那么就是Default Action,此时就可以直接二分检索!或者用你任何熟悉的高效的算法搜索超立方体都可以。

  引申开来,就是说,只要多个(姑且设为m个)matches构成的超立方体彼此之间没有任何重合重叠,那么就可以直接采用多维区间二分查找算法来定位。据我所知,如果说是一套设计良好的防火墙规则集,那么防止重合将是一个十分必要的需求,这也是为了以后的策略扩展,有没有这个能力体现了一个运维人员的职业素养。这个工作的难度看起来并不大,但是却能体现一个人的内力,规划防火墙规则的意义与规划IP地址的意义差不多一样重要。对于规划IP而言,你把1.1.1.1分配给非洲,然后把1.1.1.2分配给日本,这在地理上也是不明智的,因为它影响了路由聚合!

  因此,我们假设设计良好的iptables所有规则的matches超立方体都是正交的,都是相互不重合的,那么我们要做的就是在n+1的维的空间中快速定位一个超立方体的位置。这要怎么做呢?
  我们先来看一个直观的例子。如何取到苹果核?这看起来是一件没有意义的事,但是假设有一天我就是需要苹果核,而苹果肉对于我而言将不再需要,取到苹果核有几种方法呢?在我看来,最典型的方法有两种:

1.沿着一个方向不断削,直到在这个方向削到苹果核的时候再换另一个方向:

这里写图片描述

这种方式有点像切西瓜。

2.沿着一个方向削一下,然后再在另一个方向削一下,始终保持手里的苹果是球形状,只是球的半径在不断变小:

这里写图片描述

这种方式和切西瓜完全不同,有点像削苹果皮。然而那种方案更可行呢?

  我们发现,在n+1模型中,可以先沿着一个维度二分搜索查到叶子节点,定位到该维度某个具体的区间,然后再进行第二个维度,然后第三个维度…但是也可以先沿着一个维度朝着目标逼近一点,然后再在另一个维度逼近一点,…这颇有一点KD-Tree中临近节点算法,这个温州皮鞋厂老板正在研究,我就不再细谈了。总之,有一事需要告知,任何复杂的算法在现实生活中都有原形…

  在n+1模型中,不再需要逐一的遍历匹配,取而代之的是可以基于所有的match来做二分查找(任意查找均可,本文强调的是最容易实现的情形)。

  举一个简单的例子,一个规则集合一共有4个matches维度要匹配,一共1024,即210条规则,那么按照常规的策略,内核需要遍历所有的1024条规则,如果经过本文所述的预处理,1024条规则在每个match维度最多log210,即最多10次就能找到该维度的区间,4个维度总共最多需要40次即可,和1024相比,自己想想效果吧…以下是一个示意图:

这里写图片描述

其实,仔细想想,只要事先把规则集规划好,那么每一条规则的match几乎都可以做到相互不重合,这就使得预处理变得非常简单,比如如果说我配置了以下的一条规则:

iptables -A INPUT -s 192.168.10.2 -j DROP;

接下来我又添加了一条:

iptables -A INPUT -s 192.168.10.10 -j DROP;

在对这么重复性的工作厌倦了以后,我得知192.168.10.0/25这个网段的地址除了明确禁止的之外,其它的未被启用,于是我干脆一劳永逸:

iptables -A INPUT -s 192.168.10.0/25 -j DROP;

请注意,这是一种偷懒的做法,因为.10.0/25和.10.2/32,.10.10/32相互之间有重合,这就需要规则的顺序来决定那条规则首先起效,如果配置管理员再勤奋一些,就可以避免规则match的区间重合,所要做的就是手工把重叠的match区间拆成不重叠的区间,而这个拆分是非常直观的。我们知道一个Net/Mask可以将IP地址空间分为三个部分:

这里写图片描述

按照这个思路,再来个Net/Mask无非就是把地址空间切割的更细罢了:

这里写图片描述

这样,不管是重叠还是不重叠的原始区间,最终我们都能手动地拆分成不重叠的区间,我们针对每一个这样的不重叠的区间编写一条iptables规则,然后按照这些区间的分布情况利用iptables的goto机制将这些规则组织成一棵查找树,一次性把最终的已然成树的规则集注入内核,这样不就可以避免内核线性逐条匹配了吗?将这里手动的做法编成程序写成代码,封装成一个前端,就是本文的主旨思路了。能手工做的Step by step的事情,就一定能写成代码。

  也许你会考虑得更加周全一些,比如你会觉得“远远不止-s,-d这种match啊,iptables的选项和参数太多了,这样程序写起来会不会太复杂…”,其实这种担心在现实的角度是不存在的。

  像我在2013年左右做的VPN里面的那些复杂的iptables规则在现网几乎很难碰到,我也承认,那些东西有时真的是喝了酒之后的突发奇想,要是在清醒的时候仔细斟酌,我相信肯定会有更好更简洁的方案。

  事实上,即便是真的去写处理10个matches的前端预处理程序,也并不复杂,因为区间拆分这件事可以共用一套代码,只要你能把任意match映射到有限的定义域,那么10个matches的预处理程序仅仅是把同一件简单的事情重复10次而已。

预处理程序的形态

态很简单,基本就是一个管道,只有输入和输出接口,输入是一套iptables规则集,而输出则是一套完全不同的经过优化的使用goto机制的分层iptables规则集合。

  见本文“性能前端封装”一节的示意图。

遇到的难题以及解决

然考虑4个matches的例子,本来1000条规则,4个matches必须把全部的二叉树嫁接在上一个match的叶子节点上,这会造成规则数量的指数级增加,空间占用不仅仅增加了一丢丢…

这里写图片描述

这是显然的,空间换时间嘛,天下没有免费的宴餐。然而幸运的是,我们依然可以采用各种优化手段,比如节点合并,聚合之类尽量消解不必要的空间浪费。更加幸运的是,谢天谢地,其实能让iptables规则超过几千条的,其涉及的match并不多,基本也就是IP地址而已,不然的话,规则的维护成本问题将让空间问题不是个问题,此时还不如直接采购硬件防火墙呢,相信没有任何人自己没事找事吧。

  即便有人像我这般,真的就能写10000条复杂的多个matches的规则集,那也好办!

  写个iptables的Netfilter模块即可。在描述这个方案的细节之前,我得先讲一下iptables的弊端,以便理解为什么非要自己再写一个内核模块。

  在一开始设计这个前端预处理的时候,我在想,能不能每个match维度只写一次匹配规则,比如对于-s $saddr这个match,不管它嫁接于哪个match的叶子节点,均采用同一个自定义chain作为该match的二叉查找树的树根。然而后来我发现这是难以做到的。因为:
iptables是不支持变量的-这好像是一个老生常谈的痛处了。
我希望最终的优化后的规则集按照下面的逻辑执行,以快速定位到Action:

这里写图片描述

这很好,但是做不到,为什么呢?

  我们先把上面的图示转化为iptables规则:

# 首先在Match1的搜索树中查找iptables -A INPUT --match1 a -g self_define_chain1_1iptables -A INPUT --match1 b -g self_define_chain1_2self_define_chain1_1:...self_define_chain1_2:...self_define_chain_x_end:...# 到达叶子节点,跳入Match2的查找树iptables -A self_define_chain_x_end --match1 x -g Match2# 以下是Match2的查找iptables -A Match2 --match2 a_1 -g self_define_chain2_1iptables -A Match2 --match2 b_1 -g self_define_chain2_2# 现在问题是,匹配执行到这里的时候,执行流并不知道自己是从哪里跳过来的!...

是的,这就是问题所在,当执行流在Match2的搜索树里搜索的时候,它已经忘了在Match1搜索树里得到的结果!这就像狗熊掰棒子一样,最终注定只有最后一维的Match搜索结果可以保留下来。

  到此为止,该想到的也许你也已经想到了。是的,使用nf_MARK!然而这就是痛点所在,IPMARK是一个常量,而不是变量!比如你无法像下面这样仅仅通过一条iptables规则就可以通配所有的匹配:

iptables -A XXXXX ! --mark 0 -j --GET_MARK_and_GOTO;

这条规则本意是如果一个数据包的nf_MARK不为0,那么就跳转到该数据包的IPMARK指示的chian中。然而这在iptables中是无法做到的!在配置一条规则的时候,你必须已经知道IPMARK是多少。当然,一种不那么好的解药是使用xtables-addons中的一个叫做IPMARK的extension,它可以用IP地址生成MARK,有点变量的意思,但是在规则添加的时候,IP地址肯定是无法确定的。

  所以说,我的做法是修改IPMARK extension,使之满足:

iptables -A XXXXX ! --mark 0 -j --GET_MARK_and_GOTO;

这样的话,在每goto到一个新的维度进行新的Match搜索的前,我可以把当前Match搜索的结果索引放在一个变量中,该变量在数据包未离开Netfilter期间都有效。

  由此,在全部的Matches查找树都搜索完毕的时候,就可以使用已经保存在内核中的各个Match的区间索引,然后用这些索引来定位Action!有了这个基础设施的话,预处理后的iptables规则集就可以减少很多了,每一个Match维护一棵查找树即可!

如何想出n+1模型

哈,看看这是什么?这不就是nf-HiPAC吗?

  本文所说的方法,只是在用户态对iptables规则集的组织进行了一个重构,就达到了和nf-HiPAC相同的效果,二者的思想非常一致,其实都是一种针对查找定位算法的优化,如果我们能在上计算机基础课学过冒泡排序后或者考试的时候想到用各种重定序代替遍历,那么在遇到iptables的时候,为什么就很少有人能想到呢?!

  也许,当老师逼着你问你如何排序一个序列的时候,你满脑子想的只有一个结果,那就是“最终我一定要拿出一个比遍历要好的算法”,至少你的潜意识里就会觉得遍历一定是最Low的。然而,当你遇到iptables的时候,没人逼你去优化,恰恰相反,iptables在绝大多数的场景下,它工作的非常OK,如果你对此一无所知,那么另一方面,绝大多数的人是不懂iptables在内核中是如何被执行的,至少大家没有动力去了解这个,我敢说,很少有人看完过ipt_do_table函数,是吧?

  那么这重重障碍后,谁还会能有动力去优化iptables的不足,即便是少数人看到了弊端,也只能要么另起炉灶搞个nf-HiPAC最终却销声匿迹,要么向iptables内靠,将ipset做成一个隶属于iptables的模块,很少有人有力量去触动iptables本身,直到nftables的彻底颠覆。即便是nftables,它的前端目前也仅仅是在兼容了iptables的标准用法之外并没有做太多,虽然nftables的内核支撑结构完全发生了改变,其完全有能力在用户态做出一个完美的多维树匹配结构,但却没有任何必须这么做的理由。还是同样的基因,只是整形了。

  我在本文中描述的预处理,虽然最终还是输出了iptables规则集,但是却是完全不同的理念。如果说iptables规则是竖着排列的线性集合,那么n+1模型的iptables规则则是加入了横排列的立体分层的集合。即便如此,我不得不说,本文的n+1预处理模型依然只是一次小小的外科手术,然而这符合我的风格,不喜欢重新来过,我比较在行的是,利用所有现有的东西,将它们组合成一个更加精妙的装置,这里的东西,并不仅仅指物件,还包括执着勇气

关于iptables的设计

得不提出的问题终于还是来了,我必须面对且必须要自问自答。

  说白了,设计这个n+1模型的初衷,不还是因为iptables有问题吗?本文的“动机”一节中,我不是还怒怼那些认为iptables有问题的人,现在自己岂不是自打脸了?
哈哈,我必须做出几点解释,首先依然强调iptables并不Low,相反,它很好,认为它Low的是你没玩好它。其次要解释的是,iptables为什么不是从一开始设计的时候就包含我这里扯的这些乱七八糟的东西,包括n+1预处理模型,既然我这么Low的人都能想到的这个优化,iptables的作者能想不到吗?嗨,这个问题只能去问iptables的作者了,质问他为什么不。

  其实,iptables只是提供了一个接口的底层,该接口蕴含的无限种可能性和玩法需要你必须自己去挖掘,轮子和发动机有人提供,然而车却需要你自己来造。

  最近我同步在做一个半手工版的步进电机(一个很朴素很Low的设计),依然使用的树莓派,完成后我舒了一口气,不再抱怨。

  我曾经抱怨,为什么树莓派不自带几个例子,为什么所有的一切都要我自己来做,甚至连个SD卡都不送!是的,树莓派就是一块单板,你花200多块钱买回来后,如果不想继续花钱买别的东西,你甚至无法点亮一个灯,至少SD卡是没有的吧。然而当你把需要的配件买回来后,就会发现,无非也就是花了点钱而已,且不需要花很多的钱,这很棒。

  iptables与此类似,单单iptables本身,非常直接,它仅仅是一个支撑设施,然而没有人会直接住在井盖上,也没有人会直接烧石油,所以很多东西确实需要你自己来做。几年前,当我自己曾经手工添加100多条iptables规则的时候,我意识到,要让最容易匹配的规则位于规则链的相对前面的位置,这样可以减少开销。我知道,这是一个很朴素的想法,但却已经有点我想优化它的欲望,既然我能手工做到规则排序,为什么不能用程序搞定呢?…

  过了好几年了,各种想法揉在一起,这便有了n+1预处理模型。

  那么代码呢?

n+1模型的代码何在

近实在是不想写代码,一来是因为最近总是加班,根本就没有时间,二来是因为我想用脚本语言来实现这个前端而不是用C/C++/Java这种编译型的语言,然而我对脚本语言并不是很在行,水平比较垃圾,除非有足够的外部刺激,我完成这个代码还有难度的,考虑到台风“天鸽”已经不在,台风“帕卡”也即将成为过往,而且周末加了两天班,今天周日晚上还有饭局,所有的外部刺激都是反方向的,即越来越不可能完成这个代码…就连这篇文章也是花了周五和周六晚上和周日早上的时间写的…

  所以,代码实现只能由温州皮鞋厂老板来TODO了。以下是温州老板的代码片段:

这里写图片描述

敬请期待温州皮鞋厂老板的杰作吧!!

  温州皮鞋厂老板,何许人也?温州皮鞋厂老板是我的一位朋友。温州老板是我写的几乎任何随笔中均出现过的福尔摩斯的华生般的神秘人物,当然,我不敢自诩福尔摩斯,至多是柯南·道尔(Arthur Conan Doyle)罢了…

  温州老板平时喜欢研究各种编程语言以及各种算法,且对系统和网络的底层原理有着深刻的理解,从网卡虚拟化技术到TCP加速,从分布式存储到一致性哈希,都是老板的领域,老板而且还对TLS/SSL,Https有一定的研究和自己的认识…同时,温州老板是一位吉他手,精通乐理,喜欢复杂的电声设备而鄙视一切朴素的乐器,比如口琴,二胡,木吉他这种,老板也是一位摇滚乐爱好者,有点崇洋(但不媚外)地喜欢国外金属,而鄙视国内“土摇”(也不知道谁取的这个名字…)。这就是温州老板,一位比较有意义的人。推荐温州老板的一个Blog:
http://www.xxx.net
【删除了,老板比较低调。。。】

尾声

本文中,我试图将孤立的iptables规则整合在了一起,形成了一个n+1维度的空间,每一条传统的iptables规则在该空间中都可以展开成一个n+1维超立方体,最终又把这个n+1超维空间映射到了n棵顺序嫁接的二叉查找树

  其实,iptables还是很多好玩的小Trick,能玩转它不光可以让你在使用它的时候得心应手,更重要的是可以帮助你深入理解iptables的细节和本质,从而消除很多的误解。

  早在接触了nf-HiPAC的时候,我看到了多维区间树的精妙之处,后来KD-Tree,DxR,DxRPro++等让我的这种认识逐渐深化,其实这多维树是一个非常简单的思想,它可以构建出很多高效且好玩的东西,路由查找,包分类,防火墙这些只是其中比较直接的,其它的还包括Bloom Filter,傅立叶级数的理解,无理数的理解等等…

  如果希望进一步地理解区间树更加严谨的数学描述,请参考本文“已有的优化方案”一小节中的“补遗”中引用的资源。

  这是2017年8月份的最后一个周末,下周小小就要开学了,正式开始小学之路,希望小小也能像我一样,勤于总结,勤于分享,每天进步!

补遗

虽然我知道已经有了我本文中阐述的方案,并且也有数学逻辑分析,瞬时感觉自己慢了不止一步。在以往的十年,自诩在iptables领域舍我其谁,然而碰到硬茬却是无可奈何,不过我还是有话要说。
  虽然CF项目可以实现区间匹配,但是,它实现得太复杂了,它的热度并不高,几乎没有关注。所以现如今需要一个简版的,脚本实现的,可操作性强的,这是必要的。毕竟…
  毕竟真的有人在不想改任何他的iptables规则的情况下,让我为他优化iptables防火墙性能….

原创粉丝点击