天天看点

关于iptables的u32匹配

即使不使用statistic match,也能实现基于包的路由负载均衡,答案就是u32。统观IP头,没有什么其他字段好用,唯一的一个就是ID字段,我们知道,这个ID字段是为了区分IP报文的,可以是所有四层协议全局的递增字段,也可以是四层协议私有的递增字段,因此我们可以用该字段的奇偶来作为负载均衡的依据:

echo "100 A" >> /etc/iproute2/rt_tables

echo "200 B" >> /etc/iproute2/rt_tables

ip route add default via $gw_A table A

ip route add default via $gw_B table B

ip rule add fwmark 10 table A

ip rule add fwmark 20 table B

iptables -t mangle -A OUTPUT/FORWARD -m u32 --u32 "2&0x1=0" -j MARK --set-mark 10

iptables -t mangle -A OUTPUT/FORWARD -m u32 --u32 "2&0x1=1" -j MARK --set-mark 20

如此就完成了负载均衡的配置。没有用到conntrack状态,也没有用到单独的statistic match。

插曲:起初我以为在filter表中使用MARK target,由于OUTPUT这个HOOK是位于route动作之后的,一般而言,对于OUTPUT包,标准route过后,如果发现mark,destination等影响路由动作的字段被hook function改变之后,会reroute,也就是调用ip_route_me_harder函数的,然而filter表的职责就是filter,即使mark改变了,它也并不会去reroute,即使set-mark能成功,其意义也会默默失效(也许POSTROUTING中也能用到这个MARK,但不经常)。因此只能在mangle表中使用。然而我有点生气了,既然不能用,那为何不直接在filter表中set-mark时就报错呢?最讨厌一些机制默默地起作用或者默默地失效!如果有问题,你可以抱怨,但是不能沉默!

上述的“2&amp;0x1=0”这句怎么理解呢?其实还有更加复杂的,比如“<code>0&gt;&gt;22&amp;0x3C@ 12&gt;&gt;26&amp;0x3C@ 0=0x5353482D</code>”等。实际上,如果理解了u32 match的语法,上面这些也没有什么难的。

        简单的讲,u32 match就是一个算式,该算式是一个由&amp;&amp;拼起来的多个子match的集合,每一个子match可以理解成一个标准的iptables match比如-p udp,--dport 1194之类的。每一个子match有4个运算符可以用,分别是:

&amp;:按位与操作。该操作可以过滤出一个IP数据报中我们需要的最多四个字节。

&lt;&lt;:左移操作。该操作的含义和C语言一致。

&gt;&gt;:右移操作。同上

@:向前推进操作。该操作允许你将匹配向前skip掉你不感兴趣的字节数

location = value &amp;&amp; location = value ...

其中,location可以有几种方式得到:

立即数方式:取从IP报头开始的立即数指示的偏移初的绝对字节值;

数值移位方式:先取数值偏移位置的4字节绝对数值,将绝对数据通过移位转换为相对数值;

数值&amp;掩码方式:取立即数指示的偏移位置的4字节数值,屏蔽掉不感兴趣的位;

数值@偏移:跳过数值指示的字节,然后取当前锚点后偏移处的值;

注意上述的location计算是可以嵌套的,也就是说立即数可以通过上述的运算法计算得到,比如如下的算式:

0&gt;&gt;22&amp;0x3C@4=0x29

其中0&gt;&gt;22&amp;0x3C计算出一个数值为X,@表示跳过X字节,4作为相对偏移加上X得到绝对偏移,取值,与0x29比较,进一步分析0&gt;&gt;22,它的含义是取IP报头的第0偏移处的4字节值一共32位,右移22位得到10位的数值,接下来和0x3C即二进制的111100按位与,得到上述的IP头长度X。

        需要注意的是,u32的匹配操作是以4字节为单位的,这就引出了下面一个小节的主题!

在u32的操作文档上,Start@Mask的方式中,Start的计算为匹配的最后一个字节的偏移减去3,这个3到底是怎么回事呢?实际上这完全是为了书写上的简单,为了将最后在特定位置取得的数值移动到4字节的低位,举例如下,如果Start从0开始,那么如果匹配IP报头的proto字段为ICMP的话,可以这么写:

9&amp;0xff000000 = 0x01000000;

这个写法非常长,因为匹配是从前到后的,因此掩码就必须把后面的位清除,保留最前面的高位,最终的location = value算式中,value的值也不得不写成低位清除的方式,如果一开始就从第4字节开始计算偏移,就可以解决这个问题。为了总是能将mask过滤后匹配字段留在低位,需要一个不是0的基准偏移,以后其它的偏移都由需要匹配的最后面字节的偏移和这个不是0的基准偏移相减得到,由于u32是基于4字节操作的,因此这个基准偏移就第4字节的偏移,即3!如下图所示:

关于iptables的u32匹配

       理解了上面的论述后,仍以匹配proto为ICMP为例,其实以下的书写是一致的:

9&amp;0xff000000 = 0x01000000 

6&amp;0xff = 0x01

所以不要再为这个3而纠结了!

如果说写上述的6&amp;0xff = 0x01之类的东西实在不可读,那么可以做一个封装,编写一个解释器,将可读的诸如bpf的语法翻译成u32的语法,类似:

-m u32 --u32 `u32-compiler 'tcp port 80 and dst 1.2.3.4'`

这个u32-compiler应该非常好写,简单的可以根据u32操作文档的“Tests”小节中给出的用例做一个一一映射表,参数从外部接收即可,文档最后的用例给出了大量的例子:

<dl></dl>

<dt><code>"2&amp;0xFFFF=0x2:0x0100"</code></dt>

<dd>Test for IPID's between 2 and 256</dd>

<dt><code>"0&amp;0xFFFF=0x100:0xFFFF"</code></dt>

<dd>Check for packets with 256 or more bytes.</dd>

<dt><code>"5&amp;0xFF=0:3"</code></dt>

<dd>Match packets with a TTL of 3 or less.</dd>

<dt><code>"16=0xE0000001"</code></dt>

<dd>Destination IP address is 224.0.0.1</dd>

<dt><code>"12&amp;0xFFFFFF00=0xC0A80F00"</code></dt>

<dd>Source IP is in the 192.168.15.X class C network.</dd>

<dt><code>0&amp;0x00FF0000&gt;&gt;16=0x08</code></dt>

<dd>Is the TOS field 8 (Maximize Throughput)?</dd>

<dt><code>"3&amp;0x20&gt;&gt;5=1"</code></dt>

<dd>Is the More Fragments flag set?</dd>

<dt><code>"6&amp;0xFF=0x6"</code></dt>

<dd>Is the packet a TCP packet?</dd>

<dt>......</dt>

<dt><code>"6&amp;0xFF=1"</code></dt>

<dd>Is this an ICMP packet? (From Don Cohen's documentation)</dd>

<dt><code>"6&amp;0xFF=17"</code></dt>

<dd>Is this a UDP packet?</dd>

<dt><code>"4&amp;0x3FFF=0"</code></dt>

<dd>Is the fragment offset 0 and MF cleared? (If so, this is anunfragmented packet).</dd>

<dt><code>"4&amp;0x3FFF=1:0x3FFF"</code></dt>

<dd>Is the fragment offset greater than 0 or MF set? (If so,this is a fragment).</dd>

这个可以用C实现,也可以用脚本实现,毕竟u32不像bpf是一个标准的东西,有自己的规范,u32编译实际上需要做的仅仅就是字符串转换而已。

效率角度

u32和bpf这两个match都可以将诸多的matches浓缩到一个,本质上就是针对IP报文开刀,直接在IP报文游走匹配,非常高效。二者的区别在于,u32的效率完全取决于编码者对整体匹配的认知,而bpf则不需要这样,可以将优化交给JIT编译器来做。

实现角度

在内部实现上,u32 match会涉及到bits copy/compare等操作,而bpf的代码则更紧凑,compare操作内置于bpf的bytecode中,不像u32那样必须先提取完所有信息,然后再比较。 

配置角度

在rule可读性上,由于bpf的语法和JIT是分离的,因此你可以写成--bytecode `nfbpf_compiler RAW &lt;可读性强的filter&gt;`,然而由于u32没有所谓的编译期,你必须一开始在rule的编写时就写好它,采用类似start&amp;mask = value之类的令人费解的语法。

 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1268854

继续阅读