2020年5月13日 星期三

netfilter framework study

netfilter框架

netfilter是linux內核中的一個數據包處理框架,用於替代原有的ipfwadm和ipchains等數據包處理程序。netfilter的功能包括數據包過濾,修改,SNAT/DNAT等。netfilter在內核協議棧的不同位置實現了5個hook點,其它內核模塊(比如ip_tables)可以向這些hook點註冊處理函數,這樣當數據包經過這些hook點時,其上註冊的處理函數就被依次調用,用戶層工具像iptables一般都需要相應內核模塊ip_tables配合以完成與netfilter的交互。netfilter hooks、ip{6}_tables、connection tracking、和NAT子系統一起構成了netfilter框架的主要部分。可以參考Netfilter Architecture了解更多內容

下面這張圖來自維基百科netfilter,它展示了netfilter框架在協議棧的位置,你們可能發現這張圖在我的博客中出現多次了,那是因為這張圖太經典了。它可以清楚地看到netfilter框架是如何處理通過不同協議棧路徑上的數據包

網絡過濾器

我們知道iptables的定位是IPv4 packet filter,它只處理IP數據包,而ebtables只工作在鏈路層Link Layer處理的是以太網幀(比如修改源目mac地址)。圖中用有顏色的長方形方框表示iptables或ebtables的表和鏈,綠色小方框表示network level,即iptables的表和鏈。藍色小方框表示bridge level,即ebtables的表和鏈,由於處理以太網幀相對簡單,因此鏈路層的藍色小方框相對較少。

我們還注意到一些代表iptables表和鏈的綠色小方框位於鏈路層,這是因為bridge_nf代碼的作用(從2.6 kernel開始),bridge_nf的引入是為了解決在鏈路層Bridge中處理IP數據包的問題(需要通過內核參數開啟),那為什麼要在鏈路層Bridge中處理IP數據包,而不等數據包通過網絡層時候再處理呢,這是因為不是所有的數據包都一定會通過網絡層,比如外部機器與主機上虛擬機的通信流量,bridge_nf也是openstack中實現安全組功能的基礎。

bridge_nf代碼有時候會引起困惑,就像我們在圖中看到的那樣,代表iptables表和鏈的綠色小方框跑到了鏈路層,netfilter文檔對此也有說明ebtables/iptables interaction on a Linux-based bridge

應當指出,br-nf代碼有時會違反TCP / IP網絡模型。稍後將看到,例如,有可能在鏈路層內部進行IP DNAT

ok,圖中長方形小方框已經解釋清楚了,還有一種橢圓形的方框conntrack,即connection tracking,這是netfilter提供的連接跟踪機制,此機制允許內核”審查”通過此處的所有網絡數據包,並能識別出此數據包屬於哪個網絡連接(比如數據包a屬於IP1:8888->IP2:80這個tcp連接,數據包b屬於ip3:9999->IP4:53這個udp連接)。因此,連接跟踪機制使內核能夠跟踪並記錄通過此處的所有網絡連接及其狀態。圖中可以清楚看到連接跟踪代碼所處的網絡棧位置,如果不想讓某些數據包被跟踪( NOTRACK),那就要找位於橢圓形方框conntrack之前的表和鏈來設置規則。conntrack機制是iptables實現狀態匹配( -m state)以及NAT的基礎,它由單獨的內核模塊nf_conntrack實現。下面還會詳細介紹

接著看圖中左下方bridge check方框,數據包從主機上的某個網絡接口進入( ingress),在bridge check處會檢查此網絡接口是否屬於某個Bridge的port,如果是就會進入Bridge代碼處理邏輯(下方藍色區域bridge level),否則就會送入網絡層Network Layer處理

圖中下方中間位置的bridging decision類似普通二層交換機的查表轉發功能,根據數據包目的MAC地址判斷此數據包是轉發還是交給上層處理,具體可以參考我另一篇文章linux-bridge

圖中中心位置的routing decision就是路由選擇,根據系統路由表( ip route查看),決定數據包是forward,還是交給本地處理

總的來看,不同packet有不同的packet flow,packet總是從主機的某個接口進入(左下方ingress),然後經過check/decision/一系列表和鏈處理,最後,目的地或是主機上某應用進程(上中位置local process),或是需要從主機另一個接口發出(右下方egress)。這裡的接口即可以是物理網卡em1,也可以是虛擬網卡tun0/vnetx,還可以是Bridge上的一個port。

上面就是關於這張圖的一些解釋,如果還有其它疑問,歡迎留言討論,下面說說netfilter框架的各個部分

連接跟踪
當加載內核模塊nf_conntrack後,conntrack機制就開始工作,如上圖,橢圓形方框conntrack在內核中有兩處位置(PREROUTING和OUTPUT之前)能夠跟踪數據包。對於每個通過conntrack的數據包,內核都為其生成一個conntrack條目用以跟踪此連接,對於後續通過的數據包,內核會判斷若此數據包屬於一個已有的連接,則更新所對應的conntrack條目的狀態(比如更新為ESTABLISHED狀態),否則內核會為它新建一個conntrack條目。所有的conntrack條目都存放在一張表裡,稱為連接跟踪表

那麼內核如何判斷一個數據包是否屬於一個已有的連接呢,我們先來了解下連接跟踪表

連接跟踪表
連接跟踪表存放於系統內存中,可以用cat /proc/net/nf_conntrack查看當前跟踪的所有conntrack條目。如下是代表一個tcp連接的conntrack條目,根據連接協議不同,下面顯示的字段信息也不一樣,比如icmp協議

ipv4 2 tcp 6 431955已建立src = 172.16.207.231 dst = 172.16.207.232 sport = 51071 dport = 5672 src = 172.16.207.232 dst = 172.16.207.231 sport = 5672 dport = 51071 [確定] mark = 0區域= use = 2

每個conntrack條目表示一個連接,連接協議可以是tcp,udp,icmp等,它包含了數據包的原始方向信息(藍色)和期望的響應包信息(紅色),這樣內核能夠在後續到來的數據包中識別出屬於此連接的雙向數據包,並更新此連接的狀態,各字段意思的具體分析後面會說。連接跟踪表中能夠存放的conntrack條目的最大值,即係統允許的最大連接跟踪數記作CONNTRACK_MAX

netfilter_hash_table

在內核中,連接跟踪表是一個二維數組結構的哈希表(hash table),哈希表的大小記作HASHSIZE,哈希表的每一項(hash table entry)稱作bucket,因此哈希表中有HASHSIZE個bucket存在,每個bucket包含一個鍊錶(linked list),每個鍊錶能夠存放若干個conntrack條目( bucket size)。對於一個新收到的數據包,內核使用如下步驟判斷其是否屬於一個已有連接:

內核提取此數據包信息(源目IP,port,協議號)進行hash計算得到一個hash值,在哈希表中以此hash值做索引,索引結果為數據包所屬的bucket(鍊錶)。這一步hash計算時間固定並且很短
遍歷hash得到的bucket,查找是否有匹配的conntrack條目。這一步是比較耗時的操作,bucket size越大,遍歷時間越長
如何設置最大連接跟踪數
根據上面對哈希表的解釋,系統最大允許連接跟踪數CONNTRACK_MAX= 连接跟踪表大小(HASHSIZE) * Bucket大小(bucket size)。從連接跟踪表獲取bucket是hash操作時間很短,而遍歷bucket相對費時,因此為了conntrack性能考慮,bucket size越小越好,默認為8

#查看系统当前最大连接跟踪数CONNTRACK_MAX
sysctl -a | grep net.netfilter.nf_conntrack_max
#net.netfilter.nf_conntrack_max = 3203072

#查看当前连接跟踪表大小HASHSIZE
sysctl -a | grep net.netfilter.nf_conntrack_buckets
#400384
#或者这样
cat /sys/module/nf_conntrack/parameters/hashsize
#400384 
這兩個的比值即為bucket size = 3203072 / 400384

如下,現在需求是設置系統最大連接跟踪數為320w,由於bucket size不能直接設置,為了使bucket size值為8,我們需要同時設置CONNTRACK_MAX和HASHSIZE,因為他們的比值就是bucket size

#HASHSIZE (内核会自动格式化为最接近允许值)     
echo 400000 > /sys/module/nf_conntrack/parameters/hashsize

#系统最大连接跟踪数
sysctl -w net.netfilter.nf_conntrack_max=3200000   

#注意nf_conntrack内核模块需要加载   
為了使nf_conntrack模塊重新加載或系統重啟後生效

#nf_conntrack模块提供了设置HASHSIZE的参数       
echo "options nf_conntrack hashsize=400000" > /etc/modprobe.d/nf_conntrack.conf               
只需要固化HASHSIZE值,nf_conntrack模塊在重新加載時會自動設置CONNTRACK_MAX = hashsize * 8,當然前提是你bucket size使用系統默認值8。如果自定義bucket size值,就需要同時固化CONNTRACK_MAX,以保持其比值為你想要的bucket size

上面我們沒有改變bucket size的默認值8,但是若內存足夠並且性能很重要,你可以考慮每個bucket一個conntrack條目( bucket size= 1),最大可能降低遍歷耗時,即HASHSIZE = CONNTRACK_MAX

#HASHSIZE
echo 3200000 > /sys/module/nf_conntrack/parameters/hashsize
#CONNTRACK_MAX
sysctl -w net.netfilter.nf_conntrack_max=3200000
如何計算連接跟踪所佔內存
連接跟踪表存儲在系統內存中,因此需要考慮內存佔用問題,可以用下面公式計算設置不同的最大連接跟踪數所佔最大系統內存

total_mem_used(bytes) = CONNTRACK_MAX * sizeof(struct ip_conntrack) + HASHSIZE * sizeof(struct list_head)
例如我們需要設置最大連接跟踪數為320w,在centos6/7系統上,sizeof(struct ip_conntrack)= 376,sizeof(struct list_head)= 16,並且bucket size使用默認值8,並且HASHSIZE = CONNTRACK_MAX / 8,因此

total_mem_used(bytes) = 3200000 * 376 + (3200000 / 8) * 16
# = 1153MB ~= 1GB
因此可以得到,在centos6/7系統上,設置320w的最大連接跟踪數,所消耗的內存大約為1GB,對現代服務器來說,佔用內存並不多,但conntrack實在讓人又愛又恨

關於上面兩個sizeof(struct *)值在你係統上的大小,如果會C就好說,如果不會,可以使用如下python代碼計算

import ctypes

#不同系统可能此库名不一样,需要修改           
LIBNETFILTER_CONNTRACK = 'libnetfilter_conntrack.so.3.6.0'

nfct = ctypes.CDLL(LIBNETFILTER_CONNTRACK)
print 'sizeof(struct nf_conntrack):', nfct.nfct_maxsize()
print 'sizeof(struct list_head):', ctypes.sizeof(ctypes.c_void_p) * 2
conntrack條目
conntrack從經過它的數據包中提取詳細的,唯一的信息,因此能保持對每一個連接的跟踪。關於conntrack如何確定一個連接,對於tcp/udp,連接由他們的源目地址,源目端口唯一確定。對於icmp,由type,code和id字段確定。

ipv4     2 tcp      6 33 SYN_SENT src=172.16.200.119 dst=172.16.202.12 sport=54786 dport=10051 [UNREPLIED] src=172.16.202.12 dst=172.16.200.119 sport=10051 dport=54786 mark=0 zone=0 use=2
如上是一條conntrack條目,它代表當前已跟踪到的某個連接,conntrack維護的所有信息都包含在這個條目中,通過它就可以知道某個連接處於什麼狀態

此連接使用ipv4協議,是一條tcp連接(tcp的協議類型代碼是6)
33是這條conntrack條目在當前時間點的生存時間(每個conntrack條目都會有生存時間,從設置值開始倒計時,倒計時完後此條目將被清除),可以使用sysctl -a |grep conntrack | grep timeout查看不同協議不同狀態下生存時間設置值,當然這些設置值都可以調整,注意若後續有收到屬於此連接的數據包,則此生存時間將被重置(重新從設置值開始倒計時),並且狀態改變,生存時間設置值也會響應改為新狀態的值
SYN_SENT是到此刻為止conntrack跟踪到的這個連接的狀態(內核角度),SYN_SENT表示這個連接只在一個方向發送了一初始TCP SYN包,還未看到響應的SYN+ACK包(只有tcp才會有這個字段)。
src=172.16.200.119 dst=172.16.202.12 sport=54786 dport=10051是從數據包中提取的此連接的源目地址、源目端口,是conntrack首次看到此數據包時候的信息。
[UNREPLIED]說明此刻為止這個連接還沒有收到任何響應,當一個連接已收到響應時,[UNREPLIED]標誌就會被移除
接下來的src=172.16.202.12 dst=172.16.200.119 sport=10051 dport=54786地址和端口和前面是相反的,這部分不是數據包中帶有的信息,是conntrack填充的信息,代表conntrack希望收到的響應包信息。意思是若後續conntrack跟踪到某個數據包信息與此部分匹配,則此數據包就是此連接的響應數據包。注意這部分確定了conntrack如何判斷響應包(tcp/udp),icmp是依據另外幾個字段
上面是tcp連接的條目,而udp和icmp沒有連接建立和關閉過程,因此條目字段會有所不同,後面iptables狀態匹配部分我們會看到處於各個狀態的conntrack條目

注意conntrack機制並不能夠修改或過濾數據包,它只是跟踪網絡連接並維護連接跟踪表,以提供給iptables做狀態匹配使用,也就是說,如果你iptables中用不到狀態匹配,那就沒必要啟用conntrack

iptables狀態匹配
本部分參考自iptables文檔iptables-tutorial#The state machine,我找不到比它更全面的文章了

先明確下conntrack在內核協議棧所處位置,上面也提過conntrack跟踪數據包的位置在PREROUTING和OUTPUT這兩個hook點,主機自身進程產生的數據包會通過OUTPUT處的conntrack,從主機任意接口進入(包括Bridge的port)的數據包會通過PREROUTING處的conntrack,從netfilter框架圖上可以看到conntrack位置很靠前,僅在iptables的raw表之後,raw表主要作用就是允許我們對某些特定的數據包打上NOTRACK標記,這樣後面的conntrack就不會記錄此類帶有NOTRACK標籤的數據包。conntrack位置很靠前一方面是保證其後面的iptables表和鏈都能使用狀態匹配,另一方面使得conntrack能夠跟踪到任何進出主機的原始數據包(比如數據包還未NAT/FORWARD)。

iptables狀態匹配模塊
iptables狀態

iptables是帶有狀態匹配的防火牆,它使用-m state模塊從連接跟踪表查找數據包狀態。上面我們分析的那條conntrack條目處於SYN_SENT狀態,這是內核記錄的狀態,數據包在內核中可能會有幾種不同的狀態,但是映射到用戶空間iptables,只有5種狀態可用:NEW,ESTABLISHED,RELATED ,INVALID和UNTRACKED。注意這裡說的狀態不是tcp/ip協議中tcp連接的各種狀態。下面表格說明了這5種狀態分別能夠匹配什麼樣的數據包,注意下面兩點

用戶空間這5種狀態是iptables用於完成狀態匹配而定義的,不關聯與特定連接協議
conntrack記錄在前,iptables匹配在後(見netfilter框架圖)
狀態 解釋
NEW匹配連接的第一個包。意思就是,iptables從連接跟踪表中查到此包是某連接的第一個包。判斷此包是某連接的第一個包是依據conntrack當前”只看到一個方向數據包”( [UNREPLIED]),不關聯特定協議,因此NEW並不單指tcp連接的SYN包
已建立 ESTABLISHED匹配連接的響應包及後續的包。意思是,iptables從連接跟踪表中查到此包是屬於一個已經收到響應的連接(即沒有[UNREPLIED]字段)。因此在iptables狀態中,只要發送並接到響應,連接就認為是ESTABLISHED的了。這個特點使iptables可以控制由誰發起的連接才可以通過,比如A與B通信,A發給B數據包屬於NEW狀態,B回复給A的數據包就變為ESTABLISHED狀態。ICMP的錯誤和重定向等信息包也被看作是ESTABLISHED,只要它們是我們所發出的信息的應答。
有關 RELATED匹配那些屬於RELATED連接的包,這句話說了跟沒說一樣。RELATED狀態有點複雜,當一個連接與另一個已經是ESTABLISHED的連接有關時,這個連接就被認為是RELATED。這意味著,一個連接要想成為RELATED,必須首先有一個已經是ESTABLISHED的連接存在。這個ESTABLISHED連接再產生一個主連接之外的新連接,這個新連接就是RELATED狀態了,當然首先conntrack模塊要能”讀懂”它是RELATED。拿ftp來說,FTP數據傳輸連接就是RELATED與先前已建立的FTP控制連接,還有通過IRC的DCC連接。有了RELATED這個狀態,ICMP錯誤消息、FTP傳輸、DCC等才能穿過防火牆正常工作。有些依賴此機制的TCP協議和UDP協議非常複雜,他們的連接被封裝在其它的TCP或UDP包的數據部分(可以了解下overlay/vxlan/gre),這使得conntrack需要藉助其它輔助模塊才能正確”讀懂”這些複雜數據包,比如nf_conntrack_ftp這個輔助模塊
無效 INVALID匹配那些無法識別或沒有任何狀態的數據包。這可能是由於系統內存不足或收到不屬於任何已知連接的ICMP錯誤消息。一般情況下我們應該DROP此類狀態的包
未追踪 UNTRACKED狀態比較簡單,它匹配那些帶有NOTRACK標籤的數據包。需要注意的一點是,如果你在raw表中對某些數據包設置有NOTRACK標籤,那上面的4種狀態將無法匹配這樣的數據包,因此你需要單獨考慮NOTRACK包的放行規則
狀態的使用使防火牆可以非常強大和有效,來看下面這個常見的防火牆規則,它允許本機主動訪問外網,以及放開icmp協議

#iptables-save  -t filter
*filter
:INPUT DROP [1453341:537074675]
:FORWARD DROP [10976649:6291806497]
:OUTPUT ACCEPT [1221855153:247285484556]
-A INPUT -p icmp -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
主機進程與外網機器通信經歷如下步驟,因為除了filter表,其它表沒設置任何規則,所以下面步驟就省略其它表的匹配過程

進程產生要發送的數據包,數據包通過raw錶OUTPUT鍊(可以決定是否要NOTRACK)
數據包通過conntrack,conntrack記錄此連接到連接跟踪表( [UNREPLIED]) – NEW
通過OUTPUT鏈,然後從主機網卡發出– NEW
外網目標主機收到請求,發出響應包
響應包從主機某個接口進入,到達raw錶PREROUTING鍊(可以決定是否要NOTRACK) – NEW
響應包通過conntrack,conntrack發現此數據包為一個連接的響應包,更新對應conntrack條目狀態(去掉[UNREPLIED],至此兩個方向都看到包了) – ESTABLISHED
響應包到達filter錶INPUT鍊,在這裡匹配到--state RELATED,ESTABLISHED,因此放行– ESTABLISHED
像上面這種允許本機主動出流量的需求,如果不用conntrack會很難實現,那你可能會說我也可以使用iptables

數據包在內核中的狀態
從內核角度,不同協議有不同狀態,這裡我們來具體看下三種協議tcp/udp/icmp在連接跟踪表中的不同狀態

tcp連接

下面是172.16.1.100向172.16.1.200建立tcp通信過程中,/proc/net/nf_conntrack中此連接的狀態變化過程

ipv4     2 tcp      6 118 SYN_SENT src=172.16.1.100 dst=172.16.1.200 sport=36884 dport=8220 [UNREPLIED] src=172.16.1.200 dst=172.16.1.100 sport=8220 dport=36884 mark=0 zone=0 use=2
如上,首先172.16.1.100向172.16.1.200發送SYN包,172.16.1.200收到SYN包但尚未回复,由於是新連接,conntrack將此連接添加到連接跟踪表,並標記為SYN_SENT狀態,[UNREPLIED]表示conntrack尚未跟踪到172.16.1.200的響應包。注意上面這條conntrack條目存在於兩台主機的連接跟踪表中(當然,首先要兩台主機都啟用conntrack),對於172.16.1.100,數據包在經過OUTPUT這個hook點時觸發conntrack,而對於172.16.1.200 ,數據包在PREROUTING這個hook點時觸發conntrack

隨後,172.16.1.200回复SYN/ACK包給172.16.1.100,通過conntrack更新連接狀態為SYN_RECV,表示收到SYN/ACk包,去掉[UNREPLIED]字段

ipv4     2 tcp      6 59 SYN_RECV src=172.16.1.100 dst=172.16.1.200 sport=36884 dport=8220 src=172.16.1.200 dst=172.16.1.100 sport=8220 dport=36884 mark=0 zone=0 use=2
接著,172.16.1.100回复ACK給172.16.1.200,至此,三次握手完成,tcp連接已建立,conntrack更新連接狀態為ESTABLISHED

ipv4     2 tcp      6 10799 ESTABLISHED src=172.16.1.100 dst=172.16.1.200 sport=36884 dport=8220 src=172.16.1.200 dst=172.16.1.100 sport=8220 dport=36884 [ASSURED] mark=0 zone=0 use=2
連接跟踪表中的conntrack條目不可能是永久存在,每個conntrack條目都有超時時間,可以如下方式查看tcp連接各個狀態當前設置的超時時間

# sysctl -a |grep 'net.netfilter.nf_conntrack_tcp_timeout_'
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_established = 432000
...
正常的tcp連接是很短暫的,不太可能查看到一個tcp連接所有狀態變化的,那如何構造處於特定狀態的tcp連接呢,一個方法是利用iptables的--tcp-flags參數,其可以匹配tcp數據包的標誌位,比如下面這兩條

#对于本机发往172.16.1.200的tcp数据包,丢弃带有SYN/ACK flag的包     
iptables -A OUTPUT -o eth0 -p tcp --tcp-flags SYN,ACK SYN,ACK -d 172.16.1.200 -j DROP

#同样,这个是丢弃带有ACK flag的包
iptables -A OUTPUT -o eth0 -p tcp --tcp-flags ACK ACK -d 172.16.1.100 -j DROP
同時利用tcp超時重傳機制,待cat /proc/net/nf_conntrack獲取到conntrack條目後,使用iptables -D OUTPUT X刪除之前設置的DROP規則,這樣tcp連接就會正常走下去,這個很容易測試出來

udp連接

UDP連接是無狀態的,它沒有連接的建立和關閉過程,連接跟踪表中的udp連接也沒有像tcp那樣的狀態字段,但這不妨礙用戶空間iptables對udp包的狀態匹配,上面也說過,iptables中使用的各個狀態與協議無關

#只收到udp连接第一个包
ipv4     2 udp      17 28 src=172.16.1.100 dst=172.16.1.200 sport=26741 dport=8991 [UNREPLIED] src=172.16.1.200 dst=172.16.1.100 sport=8991 dport=26741 mark=0 zone=0 use=2

#收到此连接的响应包 
ipv4     2 udp      17 29 src=172.16.1.100 dst=172.16.1.200 sport=26741 dport=8991 src=172.16.1.200 dst=172.16.1.100 sport=8991 dport=26741 mark=0 zone=0 use=2
同樣可以查看udp超時時間

# sysctl -a | grep 'net.netfilter.nf_conntrack_udp_'
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 180
icmp

icmp請求,在用戶空間iptables看來,跟踪到echo request時連接處於NEW狀態,當有echo reply時就是ESTABLISHED狀態。

#icmp请求
ipv4     2 icmp     1 28 src=103.229.215.2 dst=113.31.136.7 type=8 code=0 id=35102 [UNREPLIED] src=113.31.136.7 dst=103.229.215.2 type=0 code=0 id=35102 mark=0 zone=0 use=2

#reply
ipv4     2 icmp     1 29 src=103.229.215.2 dst=113.31.136.7 type=8 code=0 id=35102 src=113.31.136.7 dst=103.229.215.2 type=0 code=0 id=35102 mark=0 zone=0 use=2
如何管理連接跟踪表
有一個用戶空間工具conntrack,其提供了對連接跟踪表的增刪改查功能,可以用yum install conntrack-tools來安裝,下面是幾個例子

#查看连接跟踪表所有条目
conntrack -L
#清除连接跟踪表
conntrack -F
#删除连接跟踪表中所有源地址是1.2.3.4的条目
conntrack -D -s 1.2.3.4

如果openstack使用安全組IptablesFirewallDriver就會用到這個工具,但默認不會安裝,未安裝時,計算節點neutron-linuxbridge-agent日誌會出現類似下面錯誤

2018-02-14 16:59:42.755 993 ERROR neutron.agent.linux.utils [req-4f84df17-534b-4032-b907-8d3463824726 - - - - -] Rootwrap error running command: ['conntrack', '-D', '-f', 'ipv4', '-d', '172.19.3.99', '-w', '1']
2018-02-14 16:59:42.872 993 ERROR neutron.plugins.ml2.drivers.agent._common_agent     self._remove_conntrack_entries_from_port_deleted(port)
需要這個工具是因為在對instance進行安全組/port/ip方面的變更後,需要同時刪除連接跟踪表中舊的相關條目,比如某台instance之前放通本機80端口,某外部主機與此instance 80端口有連接並且有流量,那此時iptables從連接跟踪表中識別到此連接狀態就是ESTABLISHED,然後此時突然有個需求需要禁止本機的80端口,如果不刪除與本機80端口有關的處於ESTABLISHED狀態的conntrack條目,而iptables又會放行處於RELATED,ESTABLISHED狀態的連接,這樣就會造成80端口仍能夠連接

網橋與netfilter

從netfilter框架圖中可以看到,最下層藍色區域為bridge level。Bridge的存在,使得主機可以充當一台虛擬的普通二層交換機來運作,這個虛擬交換機可以建多個port,連接到多個虛擬機。由此帶來的問題是,外部機器與其上虛擬機通信流量只會經過主機二層(靠Bridge轉發,此時不經過主機IP層),主機上的網卡類型變得複雜(物理網卡em1,網橋br0,虛擬網卡vnetX),進入主機的數據包可選路徑變多(bridge轉發/交給主機本地進程)。幸好,netfilter框架都可以解決,這部分內容在我的另一篇文章Bridge與netfilter,相信已經說得夠清楚了。

conntrack與LVS

LVS的修改數據包功能也是依賴netfilter框架,在LVS機器上應用iptables時需要注意一個問題就是,LVS-DR(或LVS-Tun)模式下,不能在director上使用iptables的狀態匹配(NEW,ESTABLISHED, INVALID,…)

LVS-DR模式下,client訪問director,director轉發流量到realserver,realserver直接回复client,不經過director,這種情況下,client與direcotr處於tcp半連接狀態

因此如果在director機器上啟用conntrack,此時conntrack只能看到client–>director的數據包,因為響應包不經過direcotr,conntrack無法看到反方向上的數據包,就表示iptables中的ESTABLISHED狀態永遠無法匹配,從而可能發生DROP

以上就是netfilter框架的內容,理論居多,後續會有一篇專門分析openstack安全組實現的文章,會包括本文大部分知識點,算是作為本文的一個實例應用

沒有留言:

張貼留言