Stateful firewall in OpenFlow based SDN
Stateful firewall
防火牆可以分為有狀態(Stateful)和無狀態(Stateless)。這裡說的防火牆指的不僅是Firewall,也包括OpenStack中常使用的SecurityGroup,它們只是不同層面的防火牆。
無狀態防火牆就是基於靜態數值來過濾或阻攔網路數據包。例如基於地址,埠,協議等等。無狀態指的就是,防火牆本身不關心當前的網路連接狀態。
有狀態防火牆能區分出網路連接的狀態。例如TCP連接,有狀態防火牆可以知道當前是連接的哪個階段。也就是說,有狀態防火牆可以在靜態數值之外,再通過連接狀態來過濾或阻攔網路數據包。
這兩個哪個更好?嚴格來說沒有更好,具體要看使用場景。無狀態的防火牆一般更快,在高負載網路流量下性能更好,但是只適用於簡單的場景。而有狀態的防火牆一般會安全的多,因為它可以定義更加嚴格的規則。Connection tracker
Stateful防火牆的特點就是能知道網路連接當前的狀態,並根據網路連接的狀態來過濾網路流量。網路連接的狀態,通常是由connection tracker來實現。可以簡稱為CT模塊。在linux內核中,CT是由conntrack實現,最開始實現是為了Netfilter 框架能了解特定網路連接的狀態。其內部實際上是對各個網路連接實現了狀態機,這裡的狀態機區別於TCP連接本身的狀態機,畢竟網路連接本身不一定是有狀態的,也就是說,CT模塊自己有一套狀態機。
CT模塊可以結合iptables實現有狀態防火牆,不過今天說一下如何結合OpenvSwitch和OpenFlow來實現有狀態防火牆。
在OpenvSwitch實現的OpenFlow中,CT狀態由ct_state提供。ct_state提供如下狀態:
- 0x01: new:這是一個連接的開始狀態(NEW),這個狀態只可能出現在還未commit的connection中。通常這意味著這個包是網路連接中的第一個數據包。
- 0x02: est:這是一個已經存在的連接(ESTABLISHED),這個狀態只可能出現在已經commit的connection中。並且當前的數據包是合法的,例如TCP連接中,client發送了SYN,server回復的SYN/ACK。
- 0x04: rel:這是一個與已經存在的連接(est)有關聯的連接(RELATED),例如ICMP "destinationunreachable" messages 或者FTP 數據連接。這個狀態只可能出現在已經commit的connection中。
- 0x08: rpl:匹配回復方向的數據,意味著當前主機並沒有初始化連接。這個狀態只可能出現在已經commit的connection中。
- 0x10: inv:當前數據包狀態是無效的,意味著CT模塊不能正確識別當前數據包,例如:L3/L4 protocol handler未載入或者不可用。在linux下,這通常意味著nf_conntrack 模塊未載入。L3/L4 protocol handler發現packet包不合法。Packet 長度不符合協議要求。
- 0x20: trk:表明當前數據包經過了CT模塊。如果這個標誌沒有被設置,那麼其他所有的標誌位都不可能被設置。
- 0x40: snat:數據包經過了SNAT。
- 0x80: dnat:數據包經過了DNAT。
ct_state是從OpenvSwitch 2.5版本才有,而snat和dnat更是從2.6版本才開始有。正是這個原因,早期的OpenStack Neutron的OpenvSwitch實現方案要在虛機port和br-int中間加一個linuxbridge來應用iptables以實現Security Group。這實在是個複雜低效的無奈之舉,不過後期的SDN實現方案都沒有採用這種網路連接方案了。
網路數據包在OpenFlow中有兩種狀態,untracked和tracked。當網路數據包經過了CT模塊,那它就是tracked的,對應的(0x20: trk)會置位。
網路連接在OpenFlow中也有兩種狀態,uncommitted和committed。CT模塊判斷connection是否是ESTABLISHED(0x02: est),commit是一個必要條件。
看一個小例子,這個例子在後面還會提到:
table=0,priority=100,ip,ct_state=-trk,action=ct(table=1)table=1,in_port=1,ip,ct_state=+trk+new,action=ct(commit),2table=1,in_port=1,ip,ct_state=+trk+est,action=2table=1,in_port=2,ip,ct_state=+trk+new,action=droptable=1,in_port=2,ip,ct_state=+trk+est,action=1
首先,對所有untracked的packets,執行ct(table=1)。這裡實際上做了好幾件事情:packet被複制,並發送到了CT模塊;CT模塊處理完之後將packet重新注入到OpenFlow table 1;Table 1收到的packet ct_state已經被設置上相應的值。這裡指定的table序號最好大於當前table,以免陷入循環處理。而原packet可以作為untrackedpacket繼續在OpenFlow中處理,只是例子中沒有指定其他的操作。
在table 1中,對ESTABLISHED的連接,直接發送到對端埠。
在table 1中,對NEW的連接,也就是還未提交到CT模塊的連接,對於來自埠1的連接,執行ct(commit),再提交到埠2。這樣,connection成為了committed,而connection狀態變成ESTABLISHED也成為了可能。而對來自埠2的NEW連接,直接drop。
最終的實現效果是,從埠1發起的向埠2的連接可以建立相互通訊,而從埠2發起的連接會被拒絕。這就是一個有狀態防火牆的應用,試想一下,無狀態防火牆該如何實現類似的功能?
需要注意的是,如果packet本身一個fragment packet,那麼在進入CT前,packet會重新聚合,輸出時再重新拆分。因為CT模塊需要讀取數據包的完整數據來判斷當前連接的狀態。聽起來有點傲嬌,現實就是這樣。很明顯,MTU較小時,fragment packet的可能性更大,而性能將受到很大的影響。有關MTU對網路性能的影響,在 解讀Mirantis最新的Neutron性能測試也有提到過。
Conntrack Entries
前面說過,CT功能是通過conntrack實現的,哪先看看conntrack有什麼輸出,具體來說看看conntrack表項(entry),它們可以在通過sudo conntrack –L 命令得到。更多相關的命令在:Conntracktool頁面。
tcp 6 117 SYN_SENT src=192.168.1.6 dst=192.168.1.9 sport=32775 dort=22 [UNREPLIED] src=192.168.1.9 dst=192.168.1.6 sport=22 dport=32775 [ASSURED] use=2
這條entry包含了packet所有的L3信息。首先是TCP packet。6代表了TCP的協議號。117是entry的TTL,117秒之後,如果沒有別的數據包來更新這個entry,那它將會被刪除。也就是說CT模塊認為當前的connection已經關閉。如果有別的數據包更新這個entry,那它將被reset回默認值。SYN_SENT 是TCP協議的狀態,接下來的一些沒什麼好說的。UNREPLIED,表明這個包還沒有被回復。之後跟的是期待的回復包的信息,以及收到了回復包之後entry對應的狀態。ASSURED的entry,在CT模塊達到最大可跟蹤的連接數之後,不會被刪除,而其他的entry會被刪除。最大可跟蹤的連接數是可以配置,通常與內存大小有關。
Conntrack entry,是在發現了一個NEW的connection,並提交(commit)到CT之後,才會出現。
接下來看看CT是怎麼處理幾個協議的狀態的。
TCP
前面說過CT模塊就是實現了connection state的狀態機。而TCP本身是一個有狀態的協議,有很多狀態,如RFC793指出一樣。但是CT模塊並不會將所有的TCP狀態輸出。TCP的連接建立需要3-way握手,之後才可以認為TCP連接的狀態時ESTABLISHED。這方面的內容網上很多,知乎上也很多,就不再多說了。當TCP client 發出SYN包,CT模塊會認為這是個NEW的connection,一旦TCP server返回了SYN/ACK,CT模塊就會這個connection是ESTABLISHED。注意,這裡的connection是CT模塊概念中的connection,而不是TCPconnection。為什麼會是這樣?先看看不這樣會怎樣?
假設CT模塊一直要到TCP connection是ESTABLISHED 才認為自己管理的connection是ESTABLISHED。那對於TCP server返回的SYN/ACK,CT模塊會仍然認為這是一個NEW的connection,回到前面的小例子,埠1是client,埠2是server,也就是說還需要允許埠2的NEW packet傳向埠1才能建立TCP connection。那該怎麼阻擋由埠2發起的連接呢?
似乎沒有什麼好辦法,所以CT模塊不管TCP的狀態,收到SYN/ACK就認為connection是ESTABLISHED。這樣,再套用上面的小例子,還是能達到應有的控制效果。
對於CT模塊來說,一個connection怎樣才算是斷開?這依賴於前面提到過的TTL,TTL超時了,connection會被刪除。之後相關的packet會認為屬於新的connection。而這裡的TTL是與TCP connection的狀態相關的,每個TCP connection狀態,都有一個默認的TTL值。(知乎為什麼不能插入表格???!!!)UDP
UDP本身是無狀態連接,它沒有連接建立和斷開的過程,它也沒有序列號。兩個UDP包是無法確定它們之間的先後順序。但是對於CT模塊不會因為UDP本身的無狀態而認為UDP對應的connection也是無狀態(否則CT模塊怎麼存活,只為有狀態的IP連接服務?CT模塊是為所有的網路連接服務的)
CT模塊對UDP connection的處理與TCP connection類似,client發出的第一個包,認為connection是NEW狀態,server返回第一個包,認為connection是ESTABLISHED。其實到這裡,大概就能明白CT模塊是如何工作的了吧。只要是第一個packet,就認為connection是NEW,收到了第一個合法的返回,就認為connection是ESTABLISHED。
來看一下UDP connection對應的conntrack entry。
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
與TCP對應的entry不一樣,UDP的entry沒有自身的狀態信息(例如上面的SYN_SENT),因為UDP本身就沒有這些東西。因為UDP狀態的簡單,CT模塊對於UDP的TTL(connection斷開的判斷機制)默認值是:第一個UDP包創建的NEW狀態的UDP connection,TTL是30;當connection變成ESTABLISHED,其TTL默認值是180。
UDP連接本身沒有TCP連接複雜,但是對於CT模塊來說,兩者本質還是一樣的。
ICMP
ICMP全稱是Internet Control Message Protocol,它主要用來傳輸控制信息,因此不應該有任何ICMP connection。ICMP有4種數據包可以產生回復,但是目前最常用的只有ICMP echo request。對於有回復的ICMP,CT模塊也對connection生成NEW 和 ESTABLISHED狀態。
來看看ICMP在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
這裡沒有了sport和dport,但是多了type,code和id。這實際上是匹配ICMP echo請求。前面說過,對於CT模塊來說,commit connection只是ESTABLISHED狀態的必要條件,另一個必要條件就是從對端返回的數據包是正確的。而這裡的type,code,id就是用來判斷返回的數據包是否是正確的。只有匹配的ICMP echo reply,CT模塊才認為connection是ESTABLISHED狀態。這樣相應的防火牆能進行過濾。但是由於ICMP echo reply之後,肯定不會再有別的相關數據包,所以收到了ICMP echo reply之後,CT模塊會立即刪除對應的connection,也就是conntrack entry。ICMP echo request默認有30秒的TTL。
除了ICMP echo,ICMP還常用來通知client一些額外的信息,例如ICMP time exceed,或者連接不可達。這個時候,ICMP數據不是由client發出,而是從remote端發出,這裡的remote端可能是client的對端,也可能就是中途的某個網路設備。
從client的角度來看,這樣的ICMP數據是一個RELATED的連接狀態。因為它跟現有的某個連接是有關聯的,並且它能告知當前client該如何處理現有的連接。
以TCP connection為例,client向server發出建立連接的請求。但是server沒有開放響應的埠,因此server返回ICMP networkunreachable。這就是原有TCP connection的一個RELATED連接,而client在收到了這個ICMP信息之後,CT模塊不會再傻傻的等到超時,而是直接放棄TCP連接,並且在CT模塊中刪除響應的connection。
DEFAULT connections
看了前面幾種協議,CT模塊的工作方式大概也清楚了。
- CT模塊輸出的狀態跟協議關係不大,第一個包認為connection是NEW,收到第一個合法的數據包認為connection是ESTABLISHED。RELATED狀態的connection必然跟現有的某一個connection有關聯。
- CT模塊與協議相關的地方在於TTL的默認值,每個協議的每個狀態都對應一個TTL的默認值。
那麼對於不能識別的協議,其實只要定義好了默認的TTL,CT模塊也能工作。CT模塊對於不能識別的協議,默認的TTL都是600,也就是10分鐘。相應的參數在這裡:Ipsysctltutorial
最後
現實中的協議連接,並不是你來我往這麼簡單,例如CT模塊對FTP協議的處理就相對複雜。不過總的來說,CT模塊能夠識別大部分協議,並且輸出正確的狀態,結合CT模塊,在OpenFlow中也能較為簡單的實現有狀態的防火牆。使得OpenFlow based SDN又更加有競爭力了。
推薦閱讀:
※VLAN Trunk in OpenStack Neutron and SDN
※優雅安裝OpenStack
※關於openstack的部署架構的一點兒疑問?