天天看點

關于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

繼續閱讀