Iptables狀態跟蹤機制介紹和優化探討

蘇研大雲人同時發文

前言

iptables是最常用的一種Linux主機防火牆,藉助於netfilter優秀的性能和擴展,雖歷經多年,但仍不落伍。OpenStack中安全組功能,floating IP的實現,以及fwaas的主流方案大多是依賴iptables而實現的。

與包過濾防火牆不同的是,iptables是一種狀態防火牆,可以記錄和跟蹤數據流的狀態。正是基於此,纔會有以上優秀方案的落地。

從Linux2.6.15的內核版本後,iptables開始支持狀態跟蹤(conntrack),該功能依賴於netfilter的內核模塊nf_conntrack。此後,iptables可以根據包的狀態進行二次的過濾攔截和狀態跟蹤。它也是state/ctstatenat的主要依賴模塊。

狀態跟蹤

conntrack將數據流的狀態信息以Hash表的形式儲存在內存中,包括五元組信息以及超時時間等。這裏說的狀態跟蹤並非是指狀態協議(如TCP)中連接狀態的跟蹤,而是conntrack特有的與網絡傳輸協議無關的狀態的跟蹤。

下面介紹一下conntrack中的狀態分類以及在TCP和UDP這兩種4層協議中,是如何進行狀態跟蹤的。

五種狀態

conntrack共可以爲連接標記五種狀態,分別如下:

NEW:

新建連接請求的數據包,且該數據包沒有和任何已有連接相關聯。
判斷的依據是conntrack當前“只看到一個方向數據包(UNREPLIED)”,沒有回包。

ESTABLISHED:

該連接是某NEW狀態連接的回包,也就是完成了連接的雙向關聯。

RELATED:

匹配那些屬於helper模塊定義的特殊協議的網絡連接,該連接屬於已經存在的一個ESTABLISHED連接的衍生連接。

簡而言之,A連接已經是ESTABLISHED,而B連接如果與A連接相關,那麼B連接就是RELATED。這部分不理解沒有關係,也很難一句話說清,後面章節會用大量筆墨來闡明它。

INVALID:

匹配那些無法識別或沒有任何狀態的數據包。這可能是由於系統內存不足或收到不屬於任何已知連接的ICMP錯誤消息,也就是垃圾包,一般情況下我們都會DROP此類狀態的包。

UNTRACKED :

這是一種特殊狀態,或者說並不是狀態。它是管理員在raw表中,爲連接設置NOTRACK規則後的狀態。這樣做,便於提高包過濾效率以及降低負載。

conntrack是一種狀態跟蹤和記錄的機制,本身並不能過濾數據包,只是提供包過濾的依據。 有狀態是一種過濾依據,無狀態實際也是一種過濾依據。

因此,需要小心的是,設置了NOTRACK,不代表是放行,只是它的狀態是UNTRACKED。所以如果想要對這種包放行或者處理,同樣需要配置相應的規則。具體使用方法可以查看下面“設置NOTRACK”部分的內容。

TCP的狀態跟蹤

從TCP開始討論的原因也是因爲TCP本身是狀態協議。TCP通過三次握手建立連接,分別是SYN,SYN/ACK和ACK,當完成之後連接成爲ESTABLISHED狀態。也就是說TCP完成ESTABLISHED的時候,C和S兩端已經進行了三次交互。

對於iptables而言,每一次握手,都需要對連接過濾,如前面所說NEW和ESTABLISHED狀態,分別指的是,當nf_conntrack第一次發現該連接的時候,會將其狀態設置爲NEW,當反方向也出現包的時候,即認爲是ESTABLISHED。如下圖,觀察在TCP的三次握手過程(SYN,SYN/ACK,ACK)中,conntrack狀態(NEW,ESTABLISHED)出現的時機。可以看到,第1次握手與第2次握手間是NEW,第2次握手之後就是ESTABLISHED。某種角度來說,可以認爲兩次握手對conntrack而言就已經完成了ESTABLISHED

圖1 TCP的網絡連接狀態中conntrack的狀態分佈

討論到這裏,已經把本章節的兩個主角(TCP和conntrack中的ESTABLISHED狀態)請了出來,下面舉個具體的例子,看下TCP狀態的變化。

打開/proc/net/nf_conntrack,可以看到類似如下的內容。

先說下每行entry的意思,第1列是網絡層協議名稱;第2列是協議代號;第3列代表傳輸層協議名稱;第4列是傳輸層協議代號,其中tcp是6,udp是17;第5列的117指的是TTL;第6列是TCP的狀態;第7列是表示源目地址以及對應端口;第8列是表示是否收到迴應包;第9列表示期望收到的回包的源目地址及端口。

ipv4   2   tcp      6 117 SYN_SENT src=192.168.1.5 dst=192.168.1.7 sport=1031 dport=23 [UNREPLIED] src=192.168.1.7 dst=192.168.1.5 sport=23 dport=1031 use=1

第1次握手時(SYN),TCP的狀態是SYN_SENT,且有 [UNREPLIED] 標記,意味着還沒有收到回包。此時,連接是NEW的狀態,這點很好理解。
接着往下看

ipv4   2   tcp      6 57 SYN_RECV src=192.168.1.5 dst=192.168.1.7 sport=1031 dport=23 src=192.168.1.7 dst=192.168.1.5 sport=23 dport=1031 use=1

第2次握手時(SYN/ACK),TCP連接的狀態爲SYN_RECV,也就是說,kernel收到了回覆(replay), 在初次握手時候的[UNREPLIED]的tag被去除。需要注意的是,此時conntrack已經是ESTABLISHED,再往下看

ipv4   2  tcp      6 431999 ESTABLISHED src=192.168.1.5 dst=192.168.1.7 sport=1031 dport=23 src=192.168.1.7 dst=192.168.1.5 sport=23 dport=1031 [ASSURED] use=1

第3次握手時(ACK),TCP連接的狀態變爲ESTABLISHED,也就是說,Client發出了ACK的確認標記包,此時三次握手完畢,TCP連接建立。

TCP總結: 對Client而言,TCP狀態與conntrack狀態對比是SYN_SENT(NEW),SYN_RECV(ESTABLISHED),ESTABLISHED(ESTABLISHED)

配置iptables規則

對於Client是192.168.1.5:1031,Server是192.168.1.7:23。需求是Client可以主動連接Server,而反向主動的連接不可以。Client上防火牆規則如下:


#iptables  -A  INPUT  -s 192.168.1.7  -p tcp --dport 1031  --sport 23  -m conntrack  --ctstate ESTABLISHED  -j ACCEPT


#iptables  -A  OUTPUT  -d 192.168.1.7  -p tcp --sport 1031  --dport 23  -m conntrack  --ctstate NEW,ESTABLISHED  -j ACCEPT

在這裏順便提及一下-m conntrack --ctstate與-m state --state的關係。
官方給的說法是這樣:

The conntrack match is an extended version of the state match, which makes it possible to match packets in a much more granular way. It let’s you look at information directly available in the connection tracking system, without any “frontend” systems, such as in the state match.

大意是ctstate是state的擴展版本(內核版本>=2.5開始支持),包括狀態參數也是基本相同,不用過多糾結,不過既然出了新的寫法,個人還是推薦新的寫法和規則。

UDP的狀態跟蹤

UDP是無狀態的傳輸協議,不需要三次握手也沒有SYN和ACK等各種標籤和狀態。雖然沒有三次握手的概念,但是我們還是來看三次連接的狀態記錄。

如下圖,觀察在UDP連接過程中,conntrack狀態(NEW,ESTABLISHED)出現的時機。可以看到,第1次連接與第2次連接間是NEW的狀態,第2次連接之後就是ESTABLISHED。對UDP而言,也可以認爲兩次握手對conntrack而言就已經完成了ESTABLISHED

圖2 TCP的網絡連接

ipv4   2   udp      17 20 src=192.168.1.2 dst=192.168.1.5 sport=137 dport=1025 [UNREPLIED] src=192.168.1.5 dst=192.168.1.2 sport=1025 dport=137 use=1

首先可以看到的是UDP沒有SYN_SENT這種TCP特有的狀態標籤,但是有 [UNREPLIED] 的標識,說明這個包是初次發出的包,還沒有收到迴應。此時conntrack中對應的狀態是NEW,接着往下看。

ipv4   2   udp      17 170 src=192.168.1.2 dst=192.168.1.5 sport=137  dport=1025 src=192.168.1.5 dst=192.168.1.2 sport=1025 dport=137 [ASSURED] use=1

同樣還是沒有標識,但是 [UNREPLIED] 變成了 [ASSURED] ,表明已經收到了回包,且連接建立完成。另外TTL變成了170,這時由於在該連接狀態下,默認的TTL是180,而第一次連接時默認值是30。此時conntrack中定義的狀態是ESTABLISHED。接着往下看

ipv4   2   udp      17 175 src=192.168.1.5 dst=195.22.79.2 sport=1025 dport=53 src=195.22.79.2 dst=192.168.1.5 sport=53 dport=1025 [ASSURED] use=1

Client發出第3次包之後,可以發現TTL變爲了175,說明TTL更新了,也同時說明第二次和第三次中包的狀態或者說標籤是一致的,所以TTL默認值纔會相同。

UDP總結: 對Client而言,UDP過程與conntrack狀態對比是,第一次連接(NEW),第二次連接(ESTABLISHED);

配置iptables規則

對於Client是192.168.1.5:1031,Server是192.168.1.7:23。需求是Client可以主動連接Server,而反向主動的連接不可以。Client上iptables規則如下:

#Client
#iptables  -A  INPUT  -s 192.168.1.7  -p udp --dport 1031  --sport 23  -m conntrack  --ctstate ESTABLISHED  -j ACCEPT


#iptables  -A  OUTPUT  -d 192.168.1.7  -p udp --sport 1031  --dport 23  -m conntrack  --ctstate NEW,ESTABLISHED  -j ACCEPT

回頭再看

通過對以上知識的整理,iptables的狀態跟蹤(conntrack)思想若隱若現。

網絡防火牆更關心的“進”和“出”,他有自己的考慮和規則,至於進出的包依循的是什麼,他並不關心。

當連接初次出現的時候,該連接就是NEW,當出現了對應的反向連接的時候,那麼該連接就是ESTABLISHED。看起來有點像UDP,是不是?

至於爲什麼會設計成這種模式,可能是考慮到防火牆只是涉及進出兩個方向,而兩次握手已經可以代表兩次的方向,也可能考慮到對UDP以及ICMP的兼容等問題,在這就不去深入討論了。


helper擴展

如前所述,iptables不關心進出包所使用的協議,只是適配符合OSI七層模型設計的協議。但是某些協議比較特殊,它們不遵循OSI的設計,這些協議包括FTP,SIP,H.323等。

以FTP爲例,在其被動模式下,使用21號端口作爲控制端口,使用1024~65536的隨機端口被動接受Client連接,作爲數據傳輸端口,因此數據傳輸時其dport無法預知。

爲了解決這種問題,在conntrack的基礎上,擴展出了helper機制,以確定某個連接是否與已經存在的某個ESTABLISHED連接相關(related)。nf_conntrack_core.h在nf_conntrack_expect結構體中定義了expectation(期望)的概念。指的是,將來某段時間,可能會出現的具有某種屬性和特點的連接。

以FTP爲例,其管理端口是21,那麼其數據端口就是與21端口連接相關的連接。藉助於helper,conntrack將這種狀態定義爲RELATED。由於這些模塊是擴展模塊,因此conntrack默認並沒有加載,需要額外加載如支持FTP的nf_conntrack_ftp,支持SIP的nf_conntrack_sip,支持RAS的nf_conntrack_h323等。

特殊的FTP

FTP是一種文件傳輸協議,包括主動(PORT)模式和被動(PASV)模式。兩種模式下,其服務都開放兩種端口,分別是控制端口和數據端口。

在被動模式時,使用21號端口作爲控制端口,使用1024~65536的隨機端口作爲其數據傳輸端口接受被動連接;在主動模式時,使用21號端口作爲控制端口,使用20號端口作爲數據傳輸端口主動連接Client的任意1024-65535的端口。如果按照一般協議來處理,被動模式時,需要放行1024-65535的INPUT流量,主動模式時,需要放行1024-65535的OUTPUT流量。這樣很明顯很危險。

那麼對於FTP的服務器而言,他的規則應該配成什麼樣的?舉例說明之,
如果FTP的server是192.168.0.103,Client是192.168.0.105。下面是分別在兩種模式時,在FTP server上的iptables的規則。

主動模式的iptables規則

#iptables   -A  INPUT   -s  192.168.0.105  -p tcp --dport 20  -m conntrack   --ctstate  ESTABLISHED   -j ACCEPT
#iptables   -A  OUTPUT   -d  192.168.0.105  -p tcp --sport 20  -m conntrack   --ctstate  RELATED,ESTABLISHED   -j ACCEPT

被動模式的iptables規則

#iptables -A INPUT  -s 192.168.0.105  -p  tcp  --dport 1024: -m conntrack --ctstate  RELATED,ESTABLISHED  -j ACCEPT
#iptables -A OUTPUT  -d 192.168.0.105  -p  tcp  --sport 1024: -m conntrack --ctstate   ESTABLISHED  -j ACCEPT

conntrack參數和配置優化

正如前面所說,nf_conntrack的狀態記錄是寫在內存中的,比較耗費內存資源,因此開啓了nf_conntrack之後,當分配給conntrack的資源表耗盡,會報如下錯誤:

kernel: nf_conntrack: table full, dropping packet.

說明當前服務器的conntrack_maxconntrack_buckets值已經小於當前負載需要。

使用如下命令查看當前連接的hash表數,以及最大連接數。
查看當前系統的conntrack_max和conntrack_buckets值

[root@iptables ~]# dmesg | grep conntrack
[  120.182019] nf_conntrack version 0.5.0 (7796 buckets, 31184 max)

也可以使用如下命令,分別查看當前機器的conntrack_max和conntrack_buckets:

[root@iptables ~]# cat /proc/sys/net/netfilter/nf_conntrack_max 
[root@iptables ~]# cat  /proc/sys/net/netfilter/nf_conntrack_buckets

最大連接數(conntrack_max)是conntrack_bucket的4倍,這是由於每條hash表記錄佔4個字節。這兩個值的默認值是根據當前機器的內存(不包括swap)計算出來的。參考以下公式:

# CONNTRACK_MAX = RAMSIZE (in bytes) / 16384 / (ARCH / 32) 
# (ARCH爲機器CPU的架構,64或32)

使用如下命令,查看當前有多少連接被nf_conntrack跟蹤:

[root@iptables proc]# cat /proc/sys/net/netfilter/nf_conntrack_count 

一般使用3種方式進行性能調優,包括修改配置參數,設置NOTRACK,以及卸載nf_conntrack模塊

修改配置參數

#增大conntrack_max 以及 conntrack_buckets 的值:
# echo 100000 > /proc/sys/net/netfilter/nf_conntrack_max
# echo 25000 > /proc/sys/net/netfilter/nf_conntrack_buckets
#縮短timeout的值,默認是43200秒:
# echo 600 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established

設置NOTRACK

iptables 中的 raw 表是優先級最高的表,因此其可以設置一些預先的規則。通過設置NOTRACK 給不需要被跟蹤的連接打標記, nf_conntrack 就不會跟蹤該連接,在/proc/net/nf_conntrack中就看不到該連接。下面舉個例子:

對sip爲192.168.0.105,想要訪問本機ssh的連接,要求在本機僅不對該連接進行狀態跟蹤,並且放行該連接的進出流量:

#首先是將進出的流量都打上NOTRACK的tag,在raw表中設置NOTRACK標記
#iptables  -t  raw -A  PREROUTING  -s 192.168.0.105  -p tcp  --dport 22  -j NOTRACK
#iptables  -t  raw  -A  OUTPUT  -d  192.168.0.105  -p   tcp  --sport 22  -j NOTRACK

#然後在filter表中,對UNTRACKED的流量放行
#iptables  -A INPUT  -s 192.168.0.105 -p tcp  --dport 22 -m conntrack --ctstate    UNTRACKED  -j  ACCEPT
#iptables   -A OUTPUT  -d  192.168.0.105  -p tcp --sport 22 -m conntrack --ctstate   UNTRACKED  -j  ACCEPT

卸載nf_conntrack模塊

首先需要將iptables中所有表中使用state和ctstate的規則全部刪除並清空nat表。

#使用如下命令清空全部表規則:
#iptables -F  -t table_name
#iptables -X  -t table_name

#查看nf_conntrack模塊的使用情況
#lsmod   |  grep nf_conntrack

#然後使用如下命令進行卸載
#modprobe -r nf_conntrack

如果在卸載的時候提示模塊在使用,就需要把依賴的模塊一一卸載。

不推薦卸載nf_conntrack模塊,只要iptables還有規則用到nat和state/ctstate,就不適合關掉該模塊,否則這些規則會失效。

總結

conntrack的狀態跟蹤與傳輸協議本身無關,僅考慮連接是否新產生,連接是否有響應,適配一般的傳輸協議。helper的擴展,定義了“期望”的概念,讓conntrack可以在之前的基礎上,兼容對不符合ISO設計的特殊傳輸協議的按需擴展。但是,conntrack對狀態跟蹤的背後,是操作系統資源的消耗,因此在實際生產中,對於系統資源有限且無需開啓狀態跟蹤的時候,可以適當考慮對conntrack的優化。

參考

1. Secure use of iptables and connection tracking helpers
2. Iptables Tutorial 1.2.2
3. RFC of TCP
4. iptables深入解析:ct篇
5. iptables man page
6. patch of nf_ct_helper: allow to disable automatic helper assignment

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章