(圖片來源:Netfilter hooks)
要理解 netfilter 希望達成什麼功能,從它的命令列前端理解起或許比較方便。netfilters 的一個命令列前端是 nftables (nft
)。而關於 nftables,他的維護者之一的 Pablo Neira Ayuso 有不少演講。以下前三個演講都是他親自介紹,這三個演講中涵蓋了 nft
官方文件 中大部分的內容。而且這些解說都是獨立的講解,沒有使用 nftables 前身的 iptables 或各種 *tables 的經驗也可以看懂,是個人認為目前找到最棒的解說。
如果想要知道 nftables 第一眼看過去有點詭異的術語們是怎麼來的,可以看看 KEYNOTE: netfilter archeology: 18 years from Linux 2.3 to 4.x 這個 netfilters 的歷史介紹。
這個影片簡單介紹 nft
這個命令列工具的使用方法。裡面講到的各種術語,比如 chain、 rule 等等更詳細的說明可以參考下一個影片。如果你希望先知道這些名詞的定義再開始使用,那也可以先看下一個影片再回來看這個影片中的範例。
如果覺得這個影片中的介紹太快的話,可以參考 NFTABLES [PART - 1] : "Concept and Syntax" 與 NFTABLES [Part - 2] : LIVE DEMO! 這兩個同一系列的影片,裡面有比較詳細地介紹 nft
中的命令列參數的意義。
介紹 nftables 的架構與歷史沿革,以及裡面的一些用語。像是介紹 nft
的基本用語,像是 table-chain-rule 的階層關係、expressions,以及 set、map、element 這些 nftables 中的資料型態以及例子。最後有介紹一下 Linux 核心中處理 netfilter 的虛擬機器的程式。
上一個影片偏向介紹實際的命令如何使用,如果喜歡直接看可以動的例子可以先看上一個影片。
內容跟上一個影片大致相同,不過有比較多歷史回顧與架構介紹。在解釋概念時投影片有標底線。對 nft
超長一串命令列參數感到不適的可能可以看這個影片。
除此之外,ELCE 裡面也有關於他的演講。這兩個演講幾乎是一樣的,也是同一個人講的。其中一個是 Tutorial: Firewalls with NFtables - John Hawley, VMware 與 Firewalls with NFtables - John Hawley, VMware。雖然標題中有 Tutorial,但內容都是在講歷史沿革。
除此之外,如果想要一些 nftables 跟 iptables 的對照,可以參考 nftables from a user perspective。
table
, chain
, rule
在 nftables 中,有三個主要的角色:rule
、chain
與 table
。其中,rule
是指一個描述「該對具有什麼特徵的封包做什麼事」的基本單位。每一個 rule 都屬於一個 chain,而每一個 chain 都屬於一個 table。nftables 的語法會恰好按照 table
-chain
-rule
的階層排好。像是這樣:
其中:
一個「該對具有什麼特徵的封包做什麼事」的描述,稱為一個 rule,是上述的階層中最底部的。以上面為例,ip saddr 1.1.1.1 ip daddr 2.2.2.2 tcp sport 111 tcp dport 222 jump other-chain
就是一個 rule。
rule 形成的集合稱為 chain,會是具有 chain <名稱> {...}
形式的區塊。舉例來說,上面有一個名稱為 input
及一個名稱為 other-chain
的 chain。可以幫一個 chain 指定它要處理哪個來源的封包,或者更精確地說,要 hook 住 netfilter 基礎設施哪個部分,也就是是最前面圖片的部分。這個「來源」會因為這個 chain
所處理的協定而有所不同。
chain 和一些物件形成的集合稱為 table。一個 table 會是一個名稱具有 table <family> <name> {...}
形式的區塊。其中,後面的 <name>
是這個 table 的名稱,以這邊為例,名稱是 filter
; 而前面的 <family>
是這個 table 用在哪一個通訊協定上。以這邊為例,ip
指得是 IPv4。而可以使用的協定可以在 nft
文件中的 ADDRESS FAMILIES 條目中找到。
一個 table
中除了可以包含多個 chain
之外,還可以包含其他的物件。比如說下面這個例子包含了一個名稱為 myset
的 set
。其中的 type ipv4_addr
表示這個 test
是一個用於描述 IPv4 位址的 set
,而這個 set
中則包含 1.1.1.1
一個元素:
在新增任何東西之前,可以用下面的命令看看目前設定了哪些東西:
table
這時可以在命令列輸入以下的命令,新增一個名稱為 foo
,用於處理 IPv4 family 的 table:
新增之後如果再檢視一次:
就會發現相較於新增之前,多出了以下輸出:
chain
一個 chain
是 rule
形成的集合,而一個 rule
則是用來指定要「對滿足什麼特徵的封包做什麼事」。nft
的語法必須維持 table
-chain
-rule
的階層,所以在開始新增 rule
之前,要先有可以拿來裝這個 rule
的 chain
才行。比如說接著可以使用:
指定在 IPv4 family 中名稱為 foo
的 table 裡面,新增一個名為 bar
的 chain。這時候使用 nft list ruleset
就會出現:
一個 table 中可以有很多 chain,比如說:
就會出現:
這個名稱為 bar
的 chain 的種類是 filter
,用來 hook 住 IPv4
…it is not attached to a Netfilter hooks, by itself a regular chain does not see any traffic. But one or more base chains can include rules that jump or goto this chain…
像上面這樣沒有指定要 hook 住哪個部分的 chain,稱為 regular chain,這樣的 chain 就只是一堆 rule 形成的集合,可以作為像是「在某個 chain 中滿足某個條件時,就 jump 到這個 chain 中」,讓整體比較易讀。比如說在下面這個例子中,如果碰到來源是 1.1.1.1:111
,目的地是 2.2.2.2:222
的 TCP 封包,那麼處理的流程會是:
另外一方面,如果希望這個 chain 被拿來處理流經某個部分的封包,或者說「這個 chain 要 hook 住哪個封包的處理階段」,就要在建立這個 chain 時多指定一些東西。在這之前,先刪除前面建立的這些 chain:
這時候應該要回到以下:
Base chains are those that are registered into the Netfilter hooks, i.e. these chains see packets flowing through your Linux TCP/IP stack.
如果希望指定這個 chain 被用來過濾哪個階段處理的封包,就要在新增時多指定。舉例來說,用以下命令:
上面這個命令用來在 IPv4 中的 foo
table 中新增一個名為 bar
的 chain。這個 chain 的種類是 filter
,用來 hook 住 IPv4 處理中的 input
階段,優先度是 0
。這時候
就會出現:
type
有 filter
、nat
、routing
三種。不過,這個分類更像是「NAT、routing 與其他用途」,那個 filter
是「其他用途」。所以這邊就跳過介紹,而在 nft
文件中的 Table 5. Supported chain types 中可以找到詳細的說明:
hook
–- 在哪個階段過濾?…to filter packets at a particular processing step, you explicitly create a base chain with name of your choosing, and attach it to the appropriate Netfilter hook.
(圖片來源:Nftables - Packet flow and Netfilter hooks in detail)
hook
用來指定要在封包處理的哪個階段過濾封包。因為不同通訊協定的處理流程比所不同,所以具體可以在哪些點 hook 住也有所不同。具體的內容可以在 nft
文件中,介紹不同 address family 的內容中找到。舉例來說,IPV4/IPV6/INET ADDRESS FAMILIES 中的 Table 1. IPv4/IPv6/Inet address family hooks,就介紹了 IPv4 與 IPv6 封包處理的哪些階段可以 hook 住:
priority
–- hook 住同一個地方的優先順序如果同樣的 hook
有指定多個 chain,那麼這些 chain 會依照他們的 priority
由小到大依序執行。而某些特定的數值有自己的代稱,可以在 nft
文件中的 Table 6. Standard priority names, family and hook compatibility matrix 中找到:
rule
這時候可以在 chain 底下新增 rule。舉例來說,在 IPv4 family 中名稱為 foo
的 table 中名稱為 bar
的 chain 新增以下規則:
這個 rule 為 ip saddr 127.0.0.1 counter
的意思是「如果有 IP 封包的來源位址是 127.0.0.1,那麼就計數」。如果反覆地使用 nft list ruleset
,就會發現這個計數的值出現在後面。
Each rule consists of zero or more expressions followed by one or more statements.
在新增 rule 的時候,除了指定要把這個 rule 新增在哪個 table 的哪個 chain 底下,最重要的就是這個 rule 要「針對哪些東西做什麼事」:
而在 nft
的文件當中,指定「對哪些東西」的部分稱為 expression,而「做哪些事」則稱為 statement。事實上,在 nftables 的維基百科 的目錄中,這兩者的標題與次標題分別就是 Expressions: Matching packets 與 Statements: Acting on packet matches。舉例來說,前面的 ip saddr 127.0.0.1
就是一個 expression,用來指定所有來源 IP 為 127.0.0.1
的封包; 而 counter
則是一個 statement,表示每碰到一個像這樣的封包就統計一次。
這個使用流程跟 regular expression 很像:找到滿足某個 expression 的東西,然後決定要對他做什麼。
Each expression tests whether a packet matches a specific payload field or packet/flow metadata. Multiple expressions are linearly evaluated from left to right: if the first expression matches, then the next expression is evaluated and so on.
If we reach the final expression, then the packet matches all of the expressions in the rule, and the rule's statements are executed
可以參考 nft
文件的 EXPRESSIONS 部分,或是看 nftables Wiki 中的 Quick reference-nftables in 10 minutes 中的 Matches 章節。
Each statement takes an action, such as setting the netfilter mark, counting the packet, logging the packet, or rendering a verdict such as accepting or dropping the packet or jumping to another chain.
可以參考 nft
文件中的 STATEMENTS 部分,或是看 nftables Wiki 中的 Quick reference-nftables in 10 minutes 中的 Statements 章節。
可以參考 nftables Wiki 的 Advanced data structures for performance packet classification 部分。比較常見的會裡面的 Generic set infrastructure 中列出來的那些。以下就影片中出現過的介紹。
set
有時候會希望一次對某些 IP 或某些 port 套用某個規則,這時候就需要有方法指定「一組 IPv4 位址形成的集合」「一組 port 形成的集合」。 set
就是用來達成這個功能,可以參考 nftables 官方維基的 Sets 條目。舉例來說,在剛剛的 chain 中加入一個名稱為 test
,用來表示一堆 IPv4 位址的集合:
這時會出現:
這個名稱為 test
的 set 目前什麼東西都沒有。但是可以使用 add element
來加入內容。舉例來說,如果要把 127.0.0.1
加入這個名稱為 test
的 set 中:
這時就會發現剛剛的 test
中多出了 127.0.0.1
:
最後,可以使用 @
來引用這個 set
(或是其他物件)。比如可以在 foo
這個 table 中的 bar
這個 chain 加入:
來指定「如果 IP 的來源位址有出現在 test
當中,那就計數」。這時候就變成:
map
可以建立 key-value 的對應關係。比如說可以用以下命令在上述的 table 中,加入一個名稱為 test2
以 IPv4 位址為 key,conntrack mark 為 value 的 map
:
這時候就會建立一個空的 map
:
要加入元素,可以使用 add element
,並且指定要在哪個 table 的哪個 map
中加入新的 key-value pair。比如:
就會發現 test2
裡面出現了新的元素:
最後,如果想要使用這個 map
,比如說「如果有封包的來源 IP 出現在 test2
的 key 當中,就把這個封包的 conntrack mark 設為對應的 value」:
這時候整個規則會變成:
如果使用 conntrack
檢視:
就會發限從 127.0.0.1
發出的封包中,mark
被設為 10
,恰好就是剛剛的 0x0a
:
如果把 test2
中的 127.0.0.1
對應的數值換掉,比如說換成 0x77
:
所以現在規則變成:
這時候再使用 conntrack -L
,就會發現之後的 mark
從 10
變成 119
,恰好就是 0x77
:
注意上面這個「先刪除再重新加入」的動作不是 atomic 的,所以在這兩個動作中間傳輸的封包 mark
就不會被設定(會是 0),因此就會有類似以下的結果:
Adding new expressions to nftables 是一篇介紹如何在 netfilter 的虛擬機中加入一個 expression (abcde
) 的實驗。