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