狀態機制是iptables中特殊的一部分,其實它不該該叫狀態機制,由於它只是一種鏈接跟蹤機制。但 是,不少人都承認狀態機制這個名字。文中我也或多或或少地用這個名字來表示和鏈接跟蹤相同的意思。這 不該該引發什麼混亂的。鏈接跟蹤可讓Netfilter知道某個特定鏈接的狀態。運行鏈接跟蹤的防火牆稱做 帶有狀態機制的防火牆,如下簡稱爲狀態防火牆。狀態防火牆比非狀態防火牆要安全,由於它容許咱們編寫 更嚴密的規則。python
在iptables裏,包是和被跟蹤鏈接的四種不一樣狀態有關的。它們是NEW,ESTABLISHED,RELATED和INVALID。 後面咱們會深刻地討論每個狀態。使用--state匹配操做,咱們能很容易地控制 「誰或什麼能發起新的會話」。linux
全部在內核中由Netfilter的特定框架作的鏈接跟蹤稱做conntrack(譯者注:就是connection tracking 的首字母縮寫)。conntrack能夠做爲模塊安裝,也能夠做爲內核的一部分。大部分狀況下,咱們想要,也 須要更詳細的鏈接跟蹤,這是相比於缺省的conntrack而言。也由於此,conntrack中有許多用來處理TCP, UDP或ICMP協議的部件。這些模塊從數據包中提取詳細的、惟一的信息,所以能保持對每個數據流的跟 蹤。這些信息也告知conntrack流當前的狀態。例如,UDP流通常由他們的目的地址、源地址、目的端口和源 端口惟一肯定。shell
在之前的內核裏,咱們能夠打開或關閉重組功能。然而,自從iptables和Netfilter,尤爲是鏈接跟蹤被 引入內核,這個選項就被取消了。由於沒有包的重組,鏈接跟蹤就不能正常工做。如今重組已經整合入 conntrack,而且在conntrack啓動時自動啓動。不要關閉重組功能,除非你要關閉鏈接跟蹤。安全
除了本地產生的包由OUTPUT鏈處理外,全部鏈接跟蹤都是在PREROUTING鏈裏進行處理的,意思就是, iptables會在PREROUTING鏈裏重新計算全部的狀態。若是咱們發送一個流的初始化包,狀態就會在OUTPUT鏈 裏被設置爲NEW,當咱們收到迴應的包時,狀態就會在PREROUTING鏈裏被設置爲ESTABLISHED。若是第一個包不是本地產生的,那就會在PREROUTING鏈裏被設置爲NEW狀 態。綜上,全部狀態的改變和計算都是在nat表中的PREROUTING鏈和OUTPUT鏈裏完成的。服務器
咱們先來看看怎樣閱讀/proc/net/ip_conntrack裏的conntrack記錄。這些記 錄表示的是當前被跟蹤的鏈接。若是安裝了ip_conntrack模塊,cat /proc/net/ip_conntrack 的顯示相似:網絡
tcp 6 117 SYN_SENT src=192.168.1.6 dst=192.168.1.9 sport=32775 \ dport=22 [UNREPLIED] src=192.168.1.9 dst=192.168.1.6 sport=22 \ dport=32775 use=2
conntrack模塊維護的全部信息都包含在這個例子中了,經過它們就能夠知道某個特定的鏈接處於什麼狀 態。首先顯示的是協議,這裏是tcp,接着是十進制的6(譯者注:tcp的協議類型代碼是6)。以後的117是 這條conntrack記錄的生存時間,它會有規律地被消耗,直到收到這個鏈接的更多的包。那時,這個值就會 被設爲當時那個狀態的缺省值。接下來的是這個鏈接在當前時間點的狀態。上面的例子說明這個包處在狀態 SYN_SENT,這個值是iptables顯示的,以便咱們好理解,而內部用的值稍有不一樣。SYN_SENT說明咱們正在觀 察的這個鏈接只在一個方向發送了一TCP SYN包。再下面是源地址、目的地址、源端口和目的端口。其 中有個特殊的詞UNREPLIED,說明這個鏈接尚未收到任何迴應。最後,是但願接收的應答包的信息,他們 的地址和端口和前面是相反的。框架
鏈接跟蹤記錄的信息依據IP所包含的協議不一樣而不一樣,全部相應的值都是在頭文件linux/include/netfilter-ipv4/ip_conntrack*.h中定義的。IP、TCP、UDP、ICMP協 議的缺省值是在linux/include/netfilter-ipv4/ip_conntrack.h裏定義的。具 體的值能夠查看相應的協議,但咱們這裏用不到它們,由於它們大都只在conntrack內部使用。隨着狀態的 改變,生存時間也會改變。tcp
當一個鏈接在兩個方向上都有傳輸時,conntrack記錄就刪除[UNREPLIED]標誌,而後重置。在末尾有 [ASSURED]的記錄說明兩個方向已沒有流量。這樣的記錄是肯定的,在鏈接跟蹤表滿時,是不會被刪除的, 沒有[ASSURED]的記錄就要被刪除。鏈接跟蹤表能容納多少記錄是被一個變量控制的,它可由內核中的ip- sysctl函數設置。默認值取決於你的內存大小,128MB能夠包含8192條目錄,256MB是16376條。你也能夠在 /proc/sys/net/ipv4/ip_conntrack_max裏查看、設置。函數
一個TCP鏈接是通過三次握手協商鏈接信息才創建起來的。整個會話由一個SYN包開始,而後是一個 SYN/ACK包,最後是一個ACK包,此時,會話才創建成功,可以發送數據。最大的問題在於鏈接跟蹤怎樣控制 這個過程。其實很是簡單。spa
默認狀況下,鏈接跟蹤基本上對全部的鏈接類型作一樣的操做。看看下面的圖片,咱們就能明白在鏈接 的不一樣階段,流是處於什麼狀態的。就如你看到的,鏈接跟蹤的代碼不是從用戶的觀點來看待TCP鏈接創建 的流程的。鏈接跟蹤一看到SYN包,就認爲這個鏈接是NEW狀態,一看到返回的SYN/ACK包,就認爲鏈接是 ESTABLISHED狀態。若是你仔細想一想第二步,應該能理解爲何。有了這個特殊處理,NEW和ESTABLISHED包 就能夠發送出本地網絡,且只有ESTABLISHED的鏈接纔能有迴應信息。若是把整個創建鏈接的過程當中傳輸的 數據包都看做NEW,那麼三次握手所用的包都是NEW狀態的,這樣咱們就不能阻塞從外部到本地網絡的鏈接 了。由於即便鏈接是從外向內的,但它使用的包也是NEW狀態的,並且爲了其餘鏈接能正常傳輸,咱們不得 不容許NEW狀態的包返回並進入防火牆。更復雜的是,針對TCP鏈接內核使用了不少內部狀態,它們的定義在 RFC 793 - Transmission Control Protocol的21-23頁。但好在咱們在用 戶空間用不到。後面咱們會詳細地介紹這些內容。
正如你看到的,以用戶的觀點來看,這是很簡單的。可是,從內核的角度看這一塊還有點困難的。咱們 來看一個例子。認真考慮一下在/proc/net/ip_conntrack裏,鏈接的狀態是如何 改變的。
tcp 6 117 SYN_SENT src=192.168.1.5 dst=192.168.1.35 sport=1031 \ dport=23 [UNREPLIED] src=192.168.1.35 dst=192.168.1.5 sport=23 \ dport=1031 use=1
從上面的記錄能夠看出,SYN_SENT狀態被設置了,這說明鏈接已經發出一個SYN包,但應答還沒發送過 來,這可從[UNREPLIED]標誌看出。
tcp 6 57 SYN_RECV src=192.168.1.5 dst=192.168.1.35 sport=1031 \ dport=23 src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 \ use=1
如今咱們已經收到了相應的SYN/ACK包,狀態也變爲SYN_RECV,這說明最初發出的SYN包已正確傳輸,並 且SYN/ACK包也到達了防火牆。 這就意味着在鏈接的兩方都有數據傳輸,所以能夠認爲兩個方向都有相應的 迴應。固然,這是假設的。
tcp 6 431999 ESTABLISHED src=192.168.1.5 dst=192.168.1.35 \ sport=1031 dport=23 src=192.168.1.35 dst=192.168.1.5 \ sport=23 dport=1031 use=1
如今咱們發出了三步握手的最後一個包,即ACK包,鏈接也就進入ESTABLISHED狀態了。再傳輸幾個數據 包,鏈接就是[ASSURED]的了。
下面介紹TCP鏈接在關閉過程當中的狀態。
如上圖,在發出最後一個ACK包以前,鏈接(指兩個方向)是不會關閉的。注意,這只是針對通常的情 況。鏈接也能夠經過發送關閉,這用在拒絕一個鏈接的時候。在RST包發送以後,要通過預先設定的一段時 間,鏈接才能斷掉。
鏈接關閉後,進入TIME_WAIT狀態,缺省時間是2分鐘。之因此留這個時間,是爲了讓數據包能徹底經過 各類規則的檢查,也是爲了數據包能經過擁擠的路由器,從而到達目的地。
UDP鏈接是無狀態的,由於它沒有任何的鏈接創建和關閉過程,並且大部分是無序列號的。以某個順序收 到的兩個數據包是沒法肯定它們的發出順序的。但內核仍然能夠對UDP鏈接設置狀態。咱們來看看是如何跟 蹤UDP鏈接的,以及conntrack的相關記錄。
從上圖能夠看出,以用戶的角度考慮,UDP鏈接的創建幾乎與TCP的同樣。雖然conntrack信息看起來有點 兒不一樣,但本質上是同樣的。下面咱們先來看看第一個UDP包發出後的conntrack記錄。
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包。第一個是協議名稱,第二個是協議號,第三個是此狀態的生存時間, 默認是30秒。接下來是包的源、目地址和端口,還有期待之中迴應包的源、目地址和端口。[UNREPLIED]標 記說明還未收到迴應。
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 use=1
一旦收到第一個包的迴應,[UNREPLIED]標記就會被刪除,鏈接就被認爲是ESTABLISHED的,但在記錄裏 並不顯示ESTABLISHED標記。相應地,狀態的超時時間也變爲180秒了。在本例中,只剩170秒了,10秒後, 就會減小爲160秒。有個東西是不可少的,雖然它可能會有些變化,就是前面提過的[ASSURED]。要想變爲 [ASSURED]狀態,鏈接上必需要再有些流量。
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
能夠看出來,[ASSURED]狀態的記錄和前面的沒有多大差異,除了標記由[UNREPLIED]變成[ASSURED]。如 果這個鏈接持續不了180秒,那就要被中斷。180秒是短了點兒,但對大部分應用足夠了。只要遇到這個鏈接 的包穿過防火牆,超時值就會被重置爲默認值,全部的狀態都是這樣的。
ICMP也是一種無狀態協議,它只是用來控制而不是創建鏈接。ICMP包有不少類型,但只有四種類型有應 答包,它們是回顯請求和應答(Echo request and reply),時間戳請求和應答(Timestamp request and reply),信息請求和應答(Information request and reply),還有地址掩碼請求和應答(Address mask request and reply),這些包有兩種狀態,NEW和ESTABLISHED 。時間戳請求和信息請求已經廢除不用了,回顯請求仍是經常使用的,好比ping命令就用的到,地址掩碼請 求不太經常使用,可是可能有時頗有用而且值得使用。看看下面的圖,就能夠大體瞭解ICMP鏈接的NEW和ESTABLISHED狀態了。
如圖所示,主機向目標發送一個回顯請求,防火牆就認爲這個包處於NEW狀態。 目標迴應一個回顯應答,防火牆就認爲包處於ESTABLISHED了。當回顯請求被髮送 時,ip_conntrack裏就有這樣的記錄了:
icmp 1 25 src=192.168.1.6 dst=192.168.1.10 type=8 code=0 \ id=33029 [UNREPLIED] src=192.168.1.10 dst=192.168.1.6 \ type=0 code=0 id=33029 use=1
能夠看到,ICMP的記錄和TCP、UDP的有點區別,協議名稱、超時時間和源、目地址都同樣,不一樣之處在 於沒有了端口,而新增了三個新的字段:type,code和id。字段type說明ICMP的類型。code說明ICMP的代 碼,這些代碼在附錄ICMP類型裏有說明。id是ICMP包的ID。每一個ICMP包被髮送時都被分配一個ID,接受方把一樣的ID 分配給應答包,這樣發送方能認出是哪一個請求的應答。
[UNREPLIED]的含義和前面同樣,說明數的傳輸只發生在一個方向上,也就是說未收到應答。再日後,是 應答包的源、目地址,還有相應的三個新字段,要注意的是type和code是隨着應答包的不一樣而變化的,id和 請求包的同樣。
和前面同樣,應答包被認爲是ESTABLISHED的。然而,在應答包以後,這個ICMP 鏈接就再也不有數據傳輸了。因此,一旦應答包穿過防火牆,ICMP的鏈接跟蹤記錄就被銷燬了。
以上各類狀況,請求被認爲NEW,應答是ESTABLISHED。 換句話說,就是當防火牆看到一個請求包時,就認爲鏈接處於NEW狀態,當有應答 時,就是ESTABLISHED狀態。
ICMP的缺省超時是30秒,能夠在/proc/sys/net/ipv4/netfilter/ip_ct_icmp_timeout中修改。這個值是比較合適 的,適合於大多數狀況。
ICMP的另外一個很是重要的做用是,告訴UDP、TCP鏈接或正在努力創建的鏈接發生了什麼,這時ICMP應答 被認爲是RELATED的。主機不可達和網絡不可達就是這樣的例子。當試圖鏈接某臺機 子不成功時(可能那臺機子被關上了),數據包所到達的最後一臺路由器就會返回以上的ICMP信息,它們就 是RELATED的,以下圖:
咱們發送了一個SYN包到某一地址,防火牆認爲它的狀態是NEW。可是,目標網絡 有問題不可達,路由器就會返回網絡不可達的信息,這是RELATED的。鏈接跟蹤會認 出這個錯誤信息是哪一個鏈接的,鏈接會中斷,同時相應的記錄刪除會被刪除。
當UDP鏈接遇到問題時,一樣會有相應的ICMP信息返回,固然它們的狀態也是RELATED ,以下圖:
咱們發送一個UDP包,固然它是NEW的。可是,目標網絡被一些防火牆或路由器所 禁止。咱們的防火牆就會收到網絡被禁止的信息。防火牆知道它是和哪一個已打開的UDP鏈接相關的,而且把 這個信息(狀態是RELATED)發給它,同時,把相應的記錄刪除。客戶機收到網絡被 禁止的信息,鏈接將被中斷。
有時,conntrack機制並不知道如何處理某個特殊的協議,尤爲是在它不瞭解這個協議或不知道協議如何 工做時,好比,NETBLT,MUX還有EGP。這種狀況下,conntrack使用缺省的操做。這種操做很象對UDP鏈接的 操做,就是第一個包被認做NEW,其後的應答包等等數據都是 ESTABLISHED。
使用缺省操做的包的超時值都是同樣的,600秒,也就是10分鐘。固然,這個值能夠經過/proc/sys/net/ipv4/netfilter/ip_ct_generic_timeout更改,以便適應你的通訊 量,尤爲是在耗時較多、流量巨大的狀況下,好比使用衛星等。
有些協議比其餘協議更復雜,這裏複雜的意思是指鏈接跟蹤機制很難正確地跟蹤它們,好比,ICQ、IRC 和FTP,它們都在數據包的數據域裏攜帶某些信息,這些信息用於創建其餘的鏈接。所以,須要一些特殊的 helper來完成工做。
下面以FTP做爲例子。FTP協議先創建一個單獨的鏈接——FTP控制會話。咱們經過這個鏈接發佈命令,其 他的端口就會打開以便傳輸和這個命令相關的數據。這些鏈接的創建方法有兩種:主動模式和被動模式。先 看看主動模式,FTP客戶端發送端口和IP地址信 息給服務器端,而後,客戶端打開這個端口,服務器端從它本身的20端口(FTP-Data端口號)創建與這個端 口的鏈接,接着就可使用這個鏈接發送數據了。
問題在於防火牆不知道這些額外的鏈接(相對於控制會話而言),由於這些鏈接在創建時的磋商信息都 在協議數據包的數據域內,而不是在可分析的協議頭裏。所以,防火牆就不知道是否是該放這些從服務器到 客戶機的鏈接過關。
解決的辦法是爲鏈接跟蹤模塊增長一個特殊的helper,以便能檢測到那些信息。這樣,那些從FTP服務器 到客戶機的鏈接就能夠被跟蹤了,狀態是RELATED,過程以下圖所示:
被動FTP工做方式下,data鏈接的創建過程和主動FTP的相反。客戶機告訴服務器須要某些數據,服務器 就把地址和端口發回給客戶機,客戶機據此創建鏈接接受數據。若是FTP服務器在防火牆後面,或你對用戶 限制的比較嚴格,只容許他們訪問HTTP和FTP,而封閉了其餘全部端口,爲了讓在Internet是的客戶機能訪 問到FTP,也須要增長上面提到的helper。下面是被動模式下data鏈接的創建過程: