1.什么是狀態檢測
每個網絡連接包括以下信息:源地址、目的地址、源端口和目的端口,叫作套接字對(socket pairs);協議類型、連接狀態(TCP協議)和超時時間等。防火墻把這些信息叫作狀態(stateful),能夠檢測每個連接狀態的防火墻叫作狀態包過濾防火墻。它除了能夠完成簡單包過濾防火墻的包過濾工作外,還在自己的內存中維護一個跟蹤連接狀態的表,比簡單包過濾防火墻具有更大的安全性。
iptables中的狀態檢測功能是由state選項來實現的。對這個選項,在iptables的手冊頁中有以下描述:
state
這個模塊能夠跟蹤分組的連接狀態(即狀態檢測)。
--state state
這里,state是一個用逗號分割的列表,表示要匹配的連接狀態。有效的狀態選項包括:INVAILD,表示分組對應的連接是未知的; ESTABLISHED,表示分組對應的連接已經進行了雙向的分組傳輸,也就是說連接已經建立;NEW,表示這個分組需要發起一個連接,或者說,分組對應的連接在兩個方向上都沒有進行過分組傳輸;RELATED,表示分組要發起一個新的連接,但是這個連接和一個現有的連接有關,例如:FTP的數據傳輸連接和控制連接之間就是RELATED關系。
對于本地產生分組,在PREROUTING或者OUTPUT鏈中都可以對連接的狀態進行跟蹤。在進行狀態檢測之前,需要重組分組的分片。這就是為什么在iptables中不再使用ipchains的ip_always_defrag開關。
UDP和TCP連接的狀態表由/proc/net/ip_conntrack進行維護。稍后我們再介紹它的內容。
狀態表能夠保存的最大連接數保存在/proc/sys/net/ipv4/ip_conntrack_max中。它取決于硬件的物理內存。
2.iptables的狀態檢測是如何工作的?
2.1.iptables概述
在討論iptables狀態檢測之前,我們先大體看一下整個netfilter框架。如果要在兩個網絡接口之間轉發一個分組,這個分組將以以下的順序接收規則鏈的檢查:
PREROUTING鏈
如果必要對這個分組進行目的網絡地址轉換(DNAT)和mangle處理。同時,iptables的狀態檢測機制將重組分組,并且以以下某種方式跟蹤其狀態:
分組是否匹配狀態表中的一個已經實現(ESTABLISHED)的連接。
它是否是和狀態表中某個UDP/TCP連接相關(RELATED)的一個ICMP分組。
這個分組是否要發起一個新(NEW)的連接。
如果分組和任何連接無關,就被認為是無效(INVALID)的。
FORWARD鏈
把分組的狀態和過濾表中的規則進行匹配,如果分組與所有的規則都無法匹配,就使用默認的策略進行處理。
POSTROUTING鏈
如果有必要,就對分組進行源網絡地址轉換(SNAT),
注意:所有的分組都必須和過濾表的規則進行比較。如果你修改了規則,要拒絕所有的網絡流量,那么即使分組的狀態匹配狀態表中的一個ESTABLISHED條目,也將被拒絕。
下面,我們對UDP、TCP和ICMP三個協議分別進行分析。
2.2.UDP連接
UDP(用戶數據包協議)是一種無狀態協議,以為這個協議沒有序列號。不過,這并不意味著我們不能跟蹤UDP連接。雖然沒有序列號,但是我們還可以使用其它的一些信息跟蹤UDP連接的狀態。下面是狀態表中關于UDP連接的條目:
udp 17 19 src=192.168.1.2 dst=192.168.1.50 sport=1032 dport=53 [UNREPLIED] src=192.168.1.50 dst=192.168.1.2 sport=53 dport=1032 use=1
這個狀態表項只有在iptables過濾規則允許建立新的連接時,才能建立。以下的規則可以產生這類狀態表項,這兩條規則只允許向外的UDP連接:
iptables -A INPUT -p udp -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -P udp -m state --state NEW,ESTABLISHED -j ACCEPT
上面的狀態表項包含如下信息:
連接的協議是UDP(IP協議號17)。
這個狀態表項還有19秒中就超時。
發起連接方向上的源、目的地址和源、目的端口。
應答方向上的源、目的地址和源、目的端口。這個連接使用UNREPLIED標記,表示還沒有收到應答。
UDP連接的超時時間在/usr/src/linux/net/ipv4/netfilter/ip_conntrack_proto_udp.c文件中設置,如果改變了這個值,需要重新編譯Linux內核源代碼才能生效。下面是UDP連接超時時間的相關的源代碼:
#define UDP_TIMEOUT (30*HZ)
#define UDP_STREAM_TIMEOUT (180*HZ)
一個UDP請求等待應答的時間是30*HZ(這個值一般是30秒)。在上面的例子中,等待的時間已經消耗了11秒,還剩余19秒,如果在這段時間之內沒有收到應答分組,這個表項就會被刪除。一旦收到了應答,這個值就被重置為30,UNREPLIED標志也被刪除。這個表項編程如下形式:
udp 17 28 src=192.168.1.2 dst=192.168.1.50 sport=1032 dport=53 src=192.168.1.50 dst=192.168.1.2 sport=53 dport=1032 use=1
如果在這一對源、目的地址和源、目的端口上,發生了多個請求和應答,這個表項就作為一個數據流表項,它的超時時間是180秒。這種情況下,這個表項就變成如下形式:
udp 17 177 src=192.168.1.2 dst=192.168.1.50 sport=1032 dport=53 src=192.168.1.50 dst=192.168.1.2 sport=53 dport=1032 [ASSURED] use=1
這時我們看到這個表項使用ASSURED標志。一旦連接表項使用ASSURED標志,那么即使在網絡負沉重的情況下,也不會被丟棄。如果狀態表已經飽和,當新的連接到達時,使用UNREPLIED標志的表項會受被丟棄。
2.3.TCP連接
一個TCP連接是通過三次握手的方式完成的。首先,客戶程序發出一個同步請求(發出一個SYN分組);接著,服務器端回應一個SYN|ACK分組;最后返回一個ACK分組,連接完成。整個過程如下所示:
Client Server
SYN --->
<--- SYN+ACK
ACK --->
<--- ACK
ACK --->
.........
.........
SYN和ACK是由TCP分組頭的標志決定的。在每個TCP分組頭還有32位的序列號和應答號用于跟蹤會話。
為了跟蹤一個TCP連接的狀態,你需要使用下面這樣的規則:
iptables -A INPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp -m state --state NEW,ESTABLISHED -j ACCEPT
2.3.1.連接建立過程中狀態表的變化
下面,我們詳細討論在連接建立的每個階段中,狀態表發生的變化:
一旦一個初始SYN分組進入OUTPUT鏈,并且輸出規則允許這個分組建立一個新的連接,狀態表的相關表項將如下所示:
cp 6 119 SYN_SENT src=140.208.5.62 dst=207.46.230.218 sport=1311 dport=80 [UNREPLIED] src=207.46.230.218 dst=140.208.5.62 sport=80 dport=1311 use=1
其中,TCP連接狀態是SYN_SENT,連接被標記為UNREPLIED。
現在,我們等待SYN+ACK分組的響應。一旦得到響應,這個TCP連接表項就變為:
tcp 6 57 SYN_RECV src=140.208.5.62 dst=207.46.230.218 sport=1311 dport=80 src=207.46.230.218 dst=140.208.5.62 sport=80 dport=1311 use=1
連接的狀態變為SYN_RECV,UNREPLIED標志被清除。
現在我們需要等待完成握手的ACK分組。ACK分組到達后,我們首先對其序列號進行一些檢查,如果正確,就把這個連接的狀態變為ESTABLISHED,并且使用ASSURED標記這個連接。這時,這個連接的狀態如下所示:
cp 6 431995 ESTABLISHED src=140.208.5.62 dst=207.46.230.218 sport=1311 dport=80 src=207.46.230.218 dst=140.208.5.62 sport=80 dport=1311 [ASSURED] use=1
2.3.2.透視狀態表
上面,我們涉及了很多CP連接的狀態。現在,我們分析一下TCP連接的狀態檢測。實際上,狀態表只知道NEW、ESTABLISHED、RELATED和INVALID。
要注意:狀態檢測的狀態不等于TCP狀態。當一個SYN分組的響應SYN+ACK分組到達,Netfilter的狀態檢測模塊就會認為連接已經建立。但是,這時還沒有完成三次握手,因此TCP連接還沒有建立。
另外,包過濾規則不能刪除狀態表中的表項,只有連接超時,對應的狀態表項才會被刪除。ACK分組能夠建立一個NEW狀態表項。向防火墻之后一臺并不存在主機發送ACK分組,并不會返回RST分組,可以證明這個結論。因此,你需要使用以下的規則明確新的TCP連接應該是SYN分組建立的:
iptables -A INPUT -p tcp !--syn -m state --state NEW -j DROP
這樣可以阻止空會話的繼續進行。
2.3.3.超時
所謂狀態表項的超時值是指每個表項存在的最大時間,這些超時值的大小在/usr/src/linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c文件中設置。以下是相關的代碼:
static unsigned long tcp_timeouts[]
= { 30 MINS, /* TCP_CONNTRACK_NONE, */
5 DAYS, /* TCP_CONNTRACK_ESTABLISHED, */
2 MINS, /* TCP_CONNTRACK_SYN_SENT, */
60 SECS, /* TCP_CONNTRACK_SYN_RECV, */
2 MINS, /* TCP_CONNTRACK_FIN_WAIT, */
2 MINS, /* TCP_CONNTRACK_TIME_WAIT, */
10 SECS, /* TCP_CONNTRACK_CLOSE, */
60 SECS, /* TCP_CONNTRACK_CLOSE_WAIT, */
30 SECS, /* TCP_CONNTRACK_LAST_ACK, */
2 MINS, /* TCP_CONNTRACK_LISTEN, */
};
2.3.4.連接的中斷
關閉一個TCP連接可以有兩種方式。第一種類似于建立TCP連接的三次握手。一旦一個TCP會話完成,要終止會話的一方首先發出一個FIN為1的分組。接收方TCP確認這個FIN分組,并同志自己這邊的應用程序不要在接收數據了。這個過程可以如下所示:
Client Server
.........
.........
FIN+ACK --->
<--- ACK
<--- FIN+ACK
ACK --->
在這個過程之中或者之后,狀態表的連接狀態變為TIME_WAIT。在默認情況下,2分鐘之后從狀態表刪除。
除此之外,還有其它關閉中斷的方式。TCP會話的任何一方發出一個RST標志為1的分組,可以快速斷開一個TCP連接。而且,RST分組不需要應答。在這種情況下,狀態表項的狀態變為CLOSE,10秒之后被刪除。和http連接經常通過這種方式中斷,如果一個連接很長時間沒有請求了,服務器端就會發出一個 RST分組中斷連接。
2.4.ICMP
在iptables看來,只有四種ICMP分組,這些分組類型可以被歸為NEW、ESTABLISHED兩類:
ECHO請求(ping,8)和ECHO應答(pong,0)。
時間戳請求(13)和應答(14)。
信息請求(15)和應答(16)。
地址掩碼請求(17)和應答(18)。
這些ICMP分組類型中,請求分組屬于NEW,應答分組屬于ESTABLISHED。而其它類型的ICMP分組不基于請求/應答方式,一律被歸入RELATED。
我們先看一個簡單的例子:
iptables -A OUTPUT -p icmp -m state --state NEW,ESTABLISHED, RELATED -j ACCEPT
iptables -A INPUT -p icmp -m state --state ESTABLISHED,RELATED -j ACCEPT
這鏈條規則進行如下的過濾:
一個ICMP echo請求是一個NEW連接。因此,允許ICMP echo請求通過OUTPUT鏈。
當對應的應答返回,此時連接的狀態是ESTABLISED,因此允許通過INPUT鏈。而INPUT鏈沒有NEW狀態,因此不允許echo請求通過INPUT鏈。也就是說,這兩條規則允許內部主機ping外部主機,而不允許外部主機ping內部主機。
一個重定向ICMP(5)分組不是基于請求/應答方式的,因此屬于RELATED。INPUT和OUTPUT鏈都允許RELATED狀態的連接,因此重定向(5)分組可以通過INPUT和OUTPUT鏈。
3.FTP協議的狀態檢測
上面,我們比較詳細地介紹了iptables的態檢測機制。現在,我們以FTP狀態檢測為例介紹如何使用iptables進行連接狀態檢測。
首先,你需要加載ip_conntrack_ftp模塊。使用如下規則就可以允許建立FTP控制連接(這里沒有考慮IMCP問題):
iptables -A INPUT -p tcp --sport 21 -m state --state ESTABLESED -j ACCEPT
iptables -A OUTPUT -p tcp --dport 21 -m state --state NEW,ESTABLISED -j ACCEPT
除了控制連接之外,FTP協議還需要一個數據通道,不過,數據連接可以通過主動和被動兩種模式建立,我們需要分別討論。
3.1.主動模式
在主動模式下,客戶程序在控制通道上,使用PORT命令告訴FTP服務器自己這邊的數據傳輸端口,然后FTP從20端口向這個端口發起一個連接。連接建立后,服務器端和客戶端就可以使用這個連接傳輸數據了,例如:傳誦的文件、ls等命令的結果等。因此,在主動模式下FTP數據傳輸通道是反向建立的,它從 FTP服務器端向客戶端發起。
在主動模式下,客戶端使用的數據傳輸端口是不固定的,因此我們需要在規則中使用端口范圍。由于客戶端使用的端口都是大于1024的,這并不會降低系統的安全性。
在iptables 中,有一個專門跟蹤FTP狀態的模塊--ip_conntrack_ftp。這個模塊能夠識別出PORT命令,并從中提取端口號。這樣,FTP數據傳輸連接就被歸入RELATED狀態,它和向外的FTP控制連接相關,因此我們不需要在INPUT鏈中使用NEW狀態。下面的規則可以實現我們的意圖:
iptables -A INPUT -p tcp --sport 20 -m state --state ESTABLISED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp --dport 20 -m state --state ESTABLISED -j ACCEPT
3.2.被動模式
和主動模式相反,在被動模式下,指定連接端口的PORT命令是服務器端發出的。FTP服務器通過PORT命令告訴客戶端自己使用的FTP數據傳輸端口,然后等待客戶端建立數據傳輸連接。在被動模式下,建立數據傳輸連接的方向和建立控制連接的方向是相同的。因此,被動模式具有比主動模式更好的安全性。
由于ip_conntrack_ftp模塊能夠從PORT命令提取端口,因此我們在OUTPUT鏈中也不必使用NEW狀態,下面的規則可以實現對被動模式下的FTP狀態檢測:
iptables -A INPUT -p tcp --sport 1024: --dport 1024: -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 1024: --dport 1024: -m state --state ESTABLISHED,RELATED -j ACCEPT
綜合以上的分析,我們可以得到FTP連接的狀態檢測規則,對于主動模式的FTP,需要下面的iptables規則:
iptables -A INPUT -p tcp --sport 21 -m state --state ESTABLESED -j ACCEPT
iptables -A OUTPUT -p tcp --dport 21 -m state --state NEW,ESTABLISED -j ACCEPT
iptables -A INPUT -p tcp --sport 20 -m state --state ESTABLISED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp --dport 20 -m state --state ESTABLISED -j ACCEPT
對于被動模式的FTP連接,需要使用如下iptables規則
iptables -A INPUT -p tcp --sport 21 -m state --state ESTABLESED -j ACCEPT
iptables -A OUTPUT -p tcp --dport 21 -m state --state NEW,ESTABLISED -j ACCEPT
iptables -A INPUT -p tcp --sport 1024: --dport 1024: -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 1024: --dport 1024: -m state --state ESTABLISHED,RELATED -j ACCEPT
本文中提到的狀態檢測,在iptables中實際叫作連接跟蹤(Connection tracking),出于自己的習慣,我在本文中一律改為狀態檢測


