Netfilter框架
netfilter是Linux底層包處理框架,在協議棧中提供了若干hook點,可以用於對數據包進行過濾、修改、地址轉換(SNAT/DNAT)等處理。
Netfilter的5個hook點
netfilter在內核協議棧的不同位置實現了5個hook點:
---> PRE_ROUTING ---> [Routing Decision] ---> FORWARD ---> [Routing Decision] ---> POST_ROUTING --->
| ^
| |
v |
LOCAL_IN LOCAL_OUT
| ^
| |
v |
LOCAL PROCESS
NF_IP_PRE_ROUTING
:數據包一進入協議棧即觸發,在進行任何路由判斷之前NF_IP_LOCAL_IN
:經過路由判斷,如果數據包目的是本機,將觸發該hookNF_IP_FORWARD
:經過路由判斷,如果數據包目的是其他主機,將觸發該hook轉發NF_IP_LOCAL_OUT
:本機准備發送的數據包,在進入協議棧后觸發該hookNF_IP_POST_ROUTING
:准備發出去的包或轉發的包,經過路由判斷后,離開網卡前的最后一個hook點
// include/uapi/linux/netfilter_ipv4.h
/* IP Hooks */
/* After promisc drops, checksum checks. */
#define NF_IP_PRE_ROUTING 0
/* If the packet is destined for this box. */
#define NF_IP_LOCAL_IN 1
/* If the packet is destined for another interface. */
#define NF_IP_FORWARD 2
/* Packets coming from a local process. */
#define NF_IP_LOCAL_OUT 3
/* Packets about to hit the wire. */
#define NF_IP_POST_ROUTING 4
#define NF_IP_NUMHOOKS 5
ip_tables等內核模塊可以通過向這5個hook點注冊處理函數(handler),當數據包經過hook點時,調用回調函數handler對數據包進行處理。
netfilter協議棧數據流分析
Wikipedia上關於netfilter在協議棧中的架構圖
連接跟蹤conntrack
conntrack
是netfilter
實現的連接跟蹤機制,是NAT
和iptables
狀態匹配(-m state
)的基礎,conntrack依賴的內核模塊為nf_conntrack
。conntrack
在內核中的位置有兩處:PREROUTING
和OUTPUT
之前,進入主機的所有數據包會通過PREROUTING處的conntrack,主機本地進程產生的數據包對外發出時會通過OUTPUT處的conntrack。從netfilter協議棧架構圖可以看出,conntrack所處的位置非常靠前,僅位於raw表之后,如果raw將數據包標記為NOTRACK
,則conntrack不會跟蹤該數據包連接。conntrack
通過連接跟蹤表來維護所有的連接信息,當有數據包通過conntrack
時,通過判斷該連接為一條新建的連接,還是已有連接的響應信息,對於新建連接在跟蹤表中新建一條連接條目,對於已有連接信息則更新跟蹤表中對於連接的狀態。
conntrack連接跟蹤表條目
數據包經過conntrack時,conntrack會提取相關信息來唯一標識一條連接,對於TCP/UDP協議,一條連接信息通過源IP、源端口、目的IP、目的端口確定,對於ICMP協議,由type、code、id字段確定。
在用戶態可以使用命令conntrack -L
來查看系統上的連接跟蹤表:
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是依據另外幾個字段
連接跟蹤表大小
連接跟蹤表能夠存放的conntrack條目的最大值,即系統運行的最大連接跟蹤數記作CONNTRACK_MAX
在內核中,連接跟蹤表示一個二維數組結構的哈希表,哈希表的大小記作HASHSIZE
,哈希表的每一項稱為bucket
,因此哈希表中有HASHSIZE
個bucket
,每個bucket包含一個鏈表,每個鏈表能夠存放若干個conntrack條目(bucket size
)。
因此,系統允許的最大連接跟蹤數為:
CONNTRACK_MAX
= HASHSIZE
* bucket size
#查看系統當前最大連接跟蹤數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
對於新收到的數據包,內核使用如下步驟判斷該數據包是否屬於已有連接:
- 內核提取此數據包信息(源IP、源端口、目的IP、目的端口、協議號)進行hash計算得到hash值,在哈希表中以此hash值做索引進行查找,查找結果即為該數據包所屬的bucket。這一步計算時間很短
- 遍歷對應的bucket,查找是否能匹配到conntrack條目。
bucket size
越大,遍歷時間越長
管理連接跟蹤表
在用戶態,使用工具conntrack實現對連接跟蹤表的增刪改查操作
#查看連接跟蹤表所有條目
conntrack -L
#清除連接跟蹤表
conntrack -F
#刪除連接跟蹤表中所有源地址是1.2.3.4的條目
conntrack -D -s 1.2.3.4
iptables
iptables是Linux系統上的主機防火牆,依賴於netfilter框架實現,在內核態通過ip_tables內核模塊與netfilter交互。
iptables由table和chain組成,以前是四表五鏈,新增后已經不止四表了。可以說table是chain的集合,chain是iptables規則的集合。
iptables table
iptables規則通過table來組織,根據需要做的操作分為Filter Table、NAT Table、Mangle Table、Raw Table、Security Table等。
Filter Table
:過濾功能,判斷一個數據包是否應該放行NAT Table
:地址轉換Mangle Table
:修改包的IP頭,如TTL、服務類型Raw Table
:決定數據包是否被連接跟蹤機制處理,對於不需要跟蹤的數據包可以打上NOTRACK
標簽Security Table
:標記SELinux
iptables chain
在每個table內,規則進一步組織成chain,5個chain與netfilter的5個hook點一一對應:
PREROUTING
: 由NF_IP_PRE_ROUTING
觸發INPUT
: 由NF_IP_LOCAL_IN
觸發FORWARD
: 由NF_IP_FORWARD
觸發OUTPUT
: 由NF_IP_LOCAL_OUT
觸發POSTROUTING
: 由NF_IP_POST_ROUTING
觸發
chain的優先級:
- 收到的目的為本機的包:
PREROUTING
->INPUT
- 收到的目標為其他主機的包:
PREROUTING
->FORWARD
->POSTROUTING
- 本機產生准備發出的包:
OUTPUT
->POSTROUTING
table和chain的關系
以上說明了iptables有哪些table和哪些chain,接下來討論兩個問題:
- 每個table里面都有哪些chain
下面表格展示了table和chain的關系,橫向是table,縱向是chain,標記Y的表示table里有這個chain,例如讓raw表中有PREROUTING和OUTPUT兩個chain。 - 注冊到同一個hook的不同chain執行的優先級問題,例如3個table中都有PREROUTING這條chain,應該按照怎樣的順序調用他們?
對應到列從上往下,就是hook點觸發時chain的調用順序。當一個包觸發netfilter hook點時,處理過程將沿着列從上向下執行
table/chain | PREROUTING | INPUT | FORWARD | OUTPUT | POSTROUTING |
---|---|---|---|---|---|
[routing decision] | Y | ||||
raw | Y | Y | |||
連接跟蹤 | Y | Y | |||
mangle | Y | Y | Y | Y | Y |
nat(DNAT) | Y | Y | |||
[routing decision] | Y | Y | |||
filter | Y | Y | Y | ||
security | Y | Y | Y | ||
nat(SNAT) | Y | Y |
iptables狀態匹配
conntrack可以跟蹤數據包的狀態,iptables使用-m state
進行狀態匹配正是使用了conntrack連接跟蹤表中標記的狀態。
數據包內核態狀態比較多,映射到用戶空間有5種狀態:
NEW
:新到達的包為合法包並且在連接跟蹤表中關聯不到,則為這個包創建一條新連接條目ESTABLISHED
:接收到的包為已有連接的響應包,則將NEW狀態改為ESTABLISHED狀態。對於TCP連接來說,就是跟SYN包對應的SYN/ACK包,對於UDP、ICMP來說就是與源相反的包RELATED
:接收到的包不屬於已有連接,但是和已有連接存在一定的關系,稱為輔助連接,例如 FTP 數據傳輸連接,或者是其他協議試圖建立連接時的 ICMP 應答包INVALID
:包無法識別等原因,標記為非法UNTRACKED
:raw表中標記為NOTRACK
參考
https://opengers.github.io/openstack/openstack-base-netfilter-framework-overview/#conntrack條目
http://arthurchiao.art/blog/conntrack-design-and-implementation-zh/#5-個-hook-點
https://arthurchiao.art/blog/deep-dive-into-iptables-and-netfilter-arch-zh/#chain-遍歷優先級
eBPF開發:
https://duo.com/labs/tech-notes/writing-an-xdp-network-filter-with-ebpf
https://github.com/cloudflare/cloudflare-blog/blob/master/2018-07-dropping-packets/xdp-drop-ebpf.c
https://gist.github.com/fntlnz/f6638d59e0e39f0993219684d9bf57d3
https://davidlovezoe.club/wordpress/archives/937