# 計算機網路 - nftables [TOC] ## 簡介 ![](https://i.imgur.com/gzDwEc0.png) (圖片來源:[Netfilter hooks](https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks)) ## 參考影片 (Pablo Neira Ayuso) 要理解 netfilter 希望達成什麼功能,從它的命令列前端理解起或許比較方便。netfilters 的一個命令列前端是 nftables (`nft`)。而關於 nftables,他的維護者之一的 *Pablo Neira Ayuso* 有不少演講。以下前三個演講都是他親自介紹,這三個演講中涵蓋了 [`nft` 官方文件](https://www.netfilter.org/projects/nftables/manpage.html) 中大部分的內容。而且這些解說都是獨立的講解,沒有使用 nftables 前身的 iptables 或各種 \*tables 的經驗也可以看懂,是個人認為目前找到最棒的解說。 除了影片之外,netables 的[維基百科](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page)上也有[大抄](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes)。 > 如果想要知道 nftables 第一眼看過去有點詭異的術語們是怎麼來的,可以看看 [*KEYNOTE: netf‌ilter archeology: 18 years from Linux 2.3 to 4.x*](https://youtu.be/D1eMqKyn09Y) 這個 netfilters 的歷史介紹。 ### Workshop Getting a Practical Grasp of nftables - Pablo Neira Ayuso 這個影片簡單介紹 `nft` 這個命令列工具的使用方法。裡面講到的各種術語,比如 chain、 rule 等等更詳細的說明可以參考下一個影片。如果你希望先知道這些名詞的定義再開始使用,那也可以先看下一個影片再回來看這個影片中的範例。 {%youtube FXTRRwXi3b4 %} 如果覺得這個影片中的介紹太快的話,可以參考 [*NFTABLES \[PART - 1\] : "Concept and Syntax"*](https://youtu.be/lHLPZlZLWgs) 與 [*NFTABLES [Part - 2] : LIVE DEMO!*](https://youtu.be/MFJA7T2nFMc) 這兩個同一系列的影片,裡面有比較詳細地介紹 `nft` 中的命令列參數的意義。 ### Netdev 1.1 - nftables from ingress 介紹 nftables 的架構與歷史沿革,以及裡面的一些用語。像是介紹 `nft` 的基本用語,像是 table-chain-rule 的階層關係、expressions,以及 set、map、element 這些 nftables 中的資料型態以及例子。最後有介紹一下 Linux 核心中處理 netfilter 的虛擬機器的程式。 上一個影片偏向介紹實際的命令如何使用,如果喜歡直接看可以動的例子可以先看上一個影片。 {%youtube Y3C1BvP2KN8 %} ### NLUUG VJ16 - 21 - Pablo Neira Ayuso -- Goodbye iptables, Hello nftables 內容跟上一個影片大致相同,不過有比較多歷史回顧與架構介紹。在解釋概念時投影片有標底線。對 `nft` 超長一串命令列參數感到不適的可能可以看這個影片。 {%youtube 0wQfSfDVN94 %} ### 其它參考影片 除此之外,ELCE 裡面也有關於他的演講。這兩個演講幾乎是一樣的,也是同一個人講的。其中一個是 [*Tutorial: Firewalls with NFtables - John Hawley, VMware*](https://youtu.be/EGKhIljDPCw) 與 [*Firewalls with NFtables - John Hawley, VMware*](https://youtu.be/ouqDHX6HwOo)。雖然標題中有 *Tutorial*,但內容都是在講歷史沿革。 除此之外,如果想要一些 nftables 跟 iptables 的對照,可以參考 [*nftables from a user perspective*](https://youtu.be/Uf5ULkEWPL0)。 ## 基本結構 --- `table`, `chain`, `rule` 在 nftables 中,有三個主要的角色:`rule`、`chain` 與 `table`。其中,`rule` 是指一個描述「該對具有什麼特徵的封包做什麼事」的基本單位。每一個 *rule* 都屬於一個 *chain*,而每一個 *chain* 都屬於一個 *table*。nftables 的語法會恰好按照 `table`-`chain`-`rule` 的階層排好。像是這樣: ```clike table ip filter { chain input { type filter hook input priority 0; policy accept; # this is the 1º matching rule ip saddr 1.1.1.1 ip daddr 2.2.2.2 tcp sport 111 tcp dport 222 jump other-chain # this is the 3º matching rule ip saddr 1.1.1.1 ip daddr 2.2.2.2 tcp sport 111 tcp dport 222 accept } chain other-chain { # this is the 2º matching rule counter packets 8 bytes 2020 } } ``` 其中: 1. 一個「該對具有什麼特徵的封包做什麼事」的描述,稱為一個 *rule*,是上述的階層中最底部的。以上面為例,`ip saddr 1.1.1.1 ip daddr 2.2.2.2 tcp sport 111 tcp dport 222 jump other-chain` 就是一個 *rule*。 2. *rule* 形成的集合稱為 *chain*,會是具有 `chain <名稱> {...}` 形式的區塊。舉例來說,上面有一個名稱為 `input` 及一個名稱為 `other-chain` 的 chain。可以幫一個 chain 指定它要處理哪個來源的封包,或者更精確地說,要 *hook* 住 netfilter 基礎設施哪個部分,也就是是最前面圖片的部分。這個「來源」會因為這個 `chain` 所處理的協定而有所不同。 3. *chain* 和一些物件形成的集合稱為 table。一個 table 會是一個名稱具有 `table <family> <name> {...}` 形式的區塊。其中,後面的 `<name>` 是這個 table 的名稱,以這邊為例,名稱是 `filter`; 而前面的 `<family>` 是這個 table 用在哪一個通訊協定上。以這邊為例,`ip` 指得是 IPv4。而可以使用的協定可以在 `nft` 文件中的 [*ADDRESS FAMILIES*](https://www.netfilter.org/projects/nftables/manpage.html#lbAJ) 條目中找到。 一個 `table` 中除了可以包含多個 `chain` 之外,還可以包含其他的物件。比如說下面這個例子包含了一個名稱為 `myset` 的 `set`。其中的 `type ipv4_addr` 表示這個 `test` 是一個用於描述 IPv4 位址的 `set`,而這個 `set` 中則包含 `1.1.1.1` 一個元素: ```clike table ip filter { set myset { type ipv4_addr elements = { 1.1.1.1 } } chain input { type filter hook input priority 0; policy accept; add @myset { ip saddr } } } ``` ### 檢視目前 nftables 的規定 在新增任何東西之前,可以用下面的命令看看目前設定了哪些東西: ```shell $ nft list ruleset ``` ## 新增 `table` 這時可以在命令列輸入以下的命令,新增一個名稱為 `foo` ,用於處理 IPv4 family 的 table: ```shell $ nft add table ip foo ``` 新增之後如果再檢視一次: ```shell $ nft list ruleset ``` 就會發現相較於新增之前,多出了以下輸出: ```clike table ip foo { } ``` ## 新增 `chain` 一個 `chain` 是 `rule` 形成的集合,而一個 `rule` 則是用來指定要「對滿足什麼特徵的封包做什麼事」。`nft` 的語法必須維持 `table`-`chain`-`rule` 的階層,所以在開始新增 `rule` 之前,要先有可以拿來裝這個 `rule` 的 `chain`才行。比如說接著可以使用: ```shell $ sudo nft add chain ip foo bar ``` 指定在 IPv4 family 中名稱為 `foo` 的 table 裡面,新增一個名為 `bar` 的 chain。這時候使用 `nft list ruleset` 就會出現: ```clike table ip foo { chain bar { } } ``` 一個 table 中可以有很多 chain,比如說: ```clike $ sudo nft add chain ip foo barbar $ sudo nft add chain ip foo barbarbar $ sudo nft list ruleset ``` 就會出現: ```clike table ip foo { chain bar { } chain barbar { } chain barbarbar { } } ``` 這個名稱為 `bar` 的 chain 的種類是 `filter`,用來 *hook* 住 IPv4 ### Regular Chain --- 沒有封包進入的 Chain > *...it is not attached to a [Netfilter hooks](https://wiki.nftables.org/wiki-nftables/index.php/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...* > > [*Configuring chains, nftables Wiki*](https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains) 像上面這樣沒有指定要 *hook* 住哪個部分的 chain,稱為 *regular chain*,這樣的 chain 就只是一堆 rule 形成的集合,可以作為像是「在某個 chain 中滿足某個條件時,就 jump 到這個 chain 中」,讓整體比較易讀。比如說在下面這個例子中,如果碰到來源是 `1.1.1.1:111`,目的地是 `2.2.2.2:222` 的 TCP 封包,那麼處理的流程會是: ```clike table ip filter { chain input { type filter hook input priority 0; policy accept; # this is the 1º matching rule ip saddr 1.1.1.1 ip daddr 2.2.2.2 tcp sport 111 tcp dport 222 jump other-chain # this is the 3º matching rule ip saddr 1.1.1.1 ip daddr 2.2.2.2 tcp sport 111 tcp dport 222 accept } chain other-chain { # this is the 2º matching rule counter packets 8 bytes 2020 } } ``` 另外一方面,如果希望這個 chain 被拿來處理流經某個部分的封包,或者說「這個 chain 要 *hook* 住哪個封包的處理階段」,就要在建立這個 chain 時多指定一些東西。在這之前,先刪除前面建立的這些 chain: ```clike $ sudo nft delete chain ip foo bar $ sudo nft delete chain ip foo barbar $ sudo nft delete chain ip foo barbarbar ``` 這時候應該要回到以下: ```clike table ip foo { } ``` ### Base Chain --- 有封包進入的 Chain > *Base chains are those that are registered into the [Netfilter hooks](https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks), i.e. these chains see packets flowing through your Linux TCP/IP stack.* > > [*Configuring chains, nftables Wiki*](https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains) 如果希望指定這個 chain 被用來過濾哪個階段處理的封包,就要在新增時多指定。舉例來說,用以下命令: ```shell $ sudo nft add chain ip foo "bar {type filter hook input priority 0;}" ``` 上面這個命令用來在 IPv4 中的 `foo` table 中新增一個名為 `bar` 的 chain。這個 chain 的種類是 `filter`,用來 hook 住 IPv4 處理中的 `input` 階段,優先度是 `0`。這時候 ``` $ sudo nft list ruleset ``` 就會出現: ```clike table ip foo { chain bar { type filter hook input priority filter; policy accept; } } ``` ### Base Chain 的 `type` 有 `filter`、`nat`、`routing` 三種。不過,這個分類更像是「NAT、routing 與其他用途」,那個 `filter` 是「其他用途」。所以這邊就跳過介紹,而在 `nft` 文件中的 *Table 5. Supported chain types* 中可以找到詳細的說明: ``` ┌───────┬───────────────┬─────────────────────┬──────────────────────────┐ │Type │ Families │ Hooks │ Description │ ├───────┼───────────────┼─────────────────────┼──────────────────────────┤ │ │ │ │ │ │filter │ all │ all │ Standard chain type to │ │ │ │ │ use in doubt. │ ├───────┼───────────────┼─────────────────────┼──────────────────────────┤ │ │ │ │ │ │nat │ ip, ip6, inet │ prerouting, input, │ Chains of this type │ │ │ │ output, postrouting │ perform Native Address │ │ │ │ │ Translation based on │ │ │ │ │ conntrack entries. Only │ │ │ │ │ the first packet of a │ │ │ │ │ connection actually │ │ │ │ │ traverses this chain - │ │ │ │ │ its rules usually define │ │ │ │ │ details of the created │ │ │ │ │ conntrack entry (NAT │ │ │ │ │ statements for │ │ │ │ │ instance). │ ├───────┼───────────────┼─────────────────────┼──────────────────────────┤ │ │ │ │ │ │route │ ip, ip6 │ output │ If a packet has │ │ │ │ │ traversed a chain of │ │ │ │ │ this type and is about │ │ │ │ │ to be accepted, a new │ │ │ │ │ route lookup is │ │ │ │ │ performed if relevant │ │ │ │ │ parts of the IP header │ │ │ │ │ have changed. This │ │ │ │ │ allows to e.g. implement │ │ │ │ │ policy routing selectors │ │ │ │ │ in nftables. │ └───────┴───────────────┴─────────────────────┴──────────────────────────┘ ``` ### Base Chain 的 `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.* > > [*Configuring chains, nftables Wiki*](https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains) ![](https://i.imgur.com/kTe2yoE.jpg) (圖片來源:[*Nftables - Packet flow and Netfilter hooks in detail*](https://thermalcircle.de/doku.php?id=blog:linux:nftables_packet_flow_netfilter_hooks_detail)) `hook` 用來指定要在封包處理的哪個階段過濾封包。因為不同通訊協定的處理流程比所不同,所以具體可以在哪些點 hook 住也有所不同。具體的內容可以在 `nft` 文件中,介紹不同 address family 的內容中找到。舉例來說,*IPV4/IPV6/INET ADDRESS FAMILIES* 中的 *Table 1. IPv4/IPv6/Inet address family hooks*,就介紹了 IPv4 與 IPv6 封包處理的哪些階段可以 hook 住: ``` ┌────────────┬──────────────────────────────────────────────────────────────┐ │Hook │ Description │ ├────────────┼──────────────────────────────────────────────────────────────┤ │ │ │ │prerouting │ All packets entering the system are processed by the │ │ │ prerouting hook. It is invoked before the routing process │ │ │ and is used for early filtering or changing packet │ │ │ attributes that affect routing. │ ├────────────┼──────────────────────────────────────────────────────────────┤ │ │ │ │input │ Packets delivered to the local system are processed by the │ │ │ input hook. │ ├────────────┼──────────────────────────────────────────────────────────────┤ │ │ │ │forward │ Packets forwarded to a different host are processed by the │ │ │ forward hook. │ ├────────────┼──────────────────────────────────────────────────────────────┤ │ │ │ │output │ Packets sent by local processes are processed by the output │ │ │ hook. │ ├────────────┼──────────────────────────────────────────────────────────────┤ │ │ │ │postrouting │ All packets leaving the system are processed by the │ │ │ postrouting hook. │ ├────────────┼──────────────────────────────────────────────────────────────┤ │ │ │ │ingress │ All packets entering the system are processed by this hook. │ │ │ It is invoked before layer 3 protocol handlers, hence before │ │ │ the prerouting hook, and it can be used for filtering and │ │ │ policing. Ingress is only available for Inet family (since │ │ │ Linux kernel 5.10). │ └────────────┴──────────────────────────────────────────────────────────────┘ ``` ### Base Chain 的 `priority` --- hook 住同一個地方的優先順序 如果同樣的 `hook` 有指定多個 chain,那麼這些 chain 會依照他們的 `priority` 由小到大依序執行。而某些特定的數值有自己的代稱,可以在 `nft` 文件中的 *Table 6. Standard priority names, family and hook compatibility matrix* 中找到: ``` ┌─────────┬───────┬──────────┬─────────────┐ │Name │ Value │ Families │ Hooks │ ├─────────┼───────┼──────────┼─────────────┤ │ │ │ │ │ │raw │ -300 │ ip, ip6, │ all │ │ │ │ inet │ │ ├─────────┼───────┼──────────┼─────────────┤ │ │ │ │ │ │mangle │ -150 │ ip, ip6, │ all │ │ │ │ inet │ │ ├─────────┼───────┼──────────┼─────────────┤ │ │ │ │ │ │dstnat │ -100 │ ip, ip6, │ prerouting │ │ │ │ inet │ │ ├─────────┼───────┼──────────┼─────────────┤ │ │ │ │ │ │filter │ 0 │ ip, ip6, │ all │ │ │ │ inet, │ │ │ │ │ arp, │ │ │ │ │ netdev │ │ ├─────────┼───────┼──────────┼─────────────┤ │ │ │ │ │ │security │ 50 │ ip, ip6, │ all │ │ │ │ inet │ │ ├─────────┼───────┼──────────┼─────────────┤ │ │ │ │ │ │srcnat │ 100 │ ip, ip6, │ postrouting │ │ │ │ inet │ │ └─────────┴───────┴──────────┴─────────────┘ ``` ## 新增 `rule` 這時候可以在 chain 底下新增 rule。舉例來說,在 IPv4 family 中名稱為 `foo` 的 table 中名稱為 `bar` 的 chain 新增以下規則: ```shell $ nft add rule ip foo bar ip saddr 127.0.0.1 counter ``` 這個 rule 為 `ip saddr 127.0.0.1 counter` 的意思是「如果有 IP 封包的來源位址是 127.0.0.1,那麼就計數」。如果反覆地使用 `nft list ruleset`,就會發現這個計數的值出現在後面。 ```clike $ nft list ruleset table ip foo { chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 4 bytes 252 } } $ sudo nft list ruleset table ip foo { chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 8 bytes 504 } } ``` ### Rule 的兩個部分 --- Expression 與 Statement > *Each rule consists of zero or more expressions followed by one or more statements.* 在新增 rule 的時候,除了指定要把這個 rule 新增在哪個 table 的哪個 chain 底下,最重要的就是這個 rule 要「針對哪些東西做什麼事」: ```shell $ nft rule add <哪個協定的哪個 table 的哪個 chain> <對哪些東西> <做哪些事> ``` 而在 `nft` 的文件當中,指定「對哪些東西」的部分稱為 *expression*,而「做哪些事」則稱為 *statement*。事實上,在 nftables 的[維基百科](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page) 的目錄中,這兩者的標題與次標題分別就是 [*Expressions: Matching packets*](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page#Expressions:_Matching_packets) 與 [*Statements: Acting on packet matches*](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page#Statements:_Acting_on_packet_matches)。舉例來說,前面的 `ip saddr 127.0.0.1` 就是一個 expression,用來指定所有來源 IP 為 `127.0.0.1` 的封包; 而 `counter` 則是一個 statement,表示每碰到一個像這樣的封包就統計一次。 > 這個使用流程跟 regular expression 很像:找到滿足某個 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* > > [*Simple rule management, nftables Wiki*](https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management) 可以參考 `nft` 文件的 [*EXPRESSIONS*](https://www.netfilter.org/projects/nftables/manpage.html#lbBC) 部分,或是看 nftables Wiki 中的 *Quick reference-nftables in 10 minutes* 中的 [*Matches*](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Matches) 章節。 ### Statement --- 要對適用的對象做什麼事? > *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.* > > [*Simple rule management, nftables Wiki*](https://wiki.nftables.org/wiki-nftables/index.php/Simple_rule_management) 可以參考 `nft` 文件中的 [*STATEMENTS*](https://www.netfilter.org/projects/nftables/manpage.html#lbCV) 部分,或是看 nftables Wiki 中的 *Quick reference-nftables in 10 minutes* 中的 [*Statements*](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Statements) 章節。 ## 各種資料結構 可以參考 nftables Wiki 的 [*Advanced data structures for performance packet classification*](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page#Advanced_data_structures_for_performance_packet_classification) 部分。比較常見的會裡面的 *Generic set infrastructure* 中列出來的那些。以下就影片中出現過的介紹。 ### `set` 有時候會希望一次對某些 IP 或某些 port 套用某個規則,這時候就需要有方法指定「一組 IPv4 位址形成的集合」「一組 port 形成的集合」。 `set` 就是用來達成這個功能,可以參考 nftables 官方維基的 [*Sets*](https://wiki.nftables.org/wiki-nftables/index.php/Sets) 條目。舉例來說,在剛剛的 chain 中加入一個名稱為 `test`,用來表示一堆 IPv4 位址的集合: ```shell $ sudo nft add set ip foo "test {type ipv4_addr;}" ``` 這時會出現: ```clike table ip filter { set test { type ipv4_addr } chain input { type filter hook input priority filter; policy accept; counter packets 443 bytes 31668 } } ``` 這個名稱為 `test` 的 set 目前什麼東西都沒有。但是可以使用 `add element` 來加入內容。舉例來說,如果要把 `127.0.0.1` 加入這個名稱為 `test` 的 set 中: ```clike $ sudo nft add element ip foo test "{127.0.0.1}" ``` 這時就會發現剛剛的 `test` 中多出了 `127.0.0.1`: ```clike table ip foo { set test { type ipv4_addr elements = { 127.0.0.1 } } chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 10 bytes 630 } } ``` 最後,可以使用 `@` 來引用這個 `set` (或是其他物件)。比如可以在 `foo` 這個 table 中的 `bar` 這個 chain 加入: ```shell $ sudo nft add rule ip foo bar ip saddr @test counter ``` 來指定「如果 IP 的來源位址有出現在 `test` 當中,那就計數」。這時候就變成: ```clike table ip foo { set test { type ipv4_addr elements = { 127.0.0.1 } } chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 14 bytes 882 ip saddr @test counter packets 2 bytes 126 } } ``` ### `map` 可以建立 key-value 的對應關係。比如說可以用以下命令在上述的 table 中,加入一個名稱為 `test2` 以 IPv4 位址為 key,conntrack mark 為 value 的 `map`: ```shell $ sudo nft add map ip foo "test2 {type ipv4_addr: mark;}" ``` 這時候就會建立一個空的 `map`: ```clike table ip foo { set test { type ipv4_addr elements = { 127.0.0.1 } } map test2 { type ipv4_addr : mark } chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 26 bytes 1638 ip saddr @test counter packets 14 bytes 882 } } ``` 要加入元素,可以使用 `add element`,並且指定要在哪個 table 的哪個 `map` 中加入新的 key-value pair。比如: ```shell $ sudo nft add element ip foo "test2 {127.0.0.1: 0xa, 127.0.0.2: 0xb}" ``` 就會發現 `test2` 裡面出現了新的元素: ```clike table ip foo { set test { type ipv4_addr elements = { 127.0.0.1 } } map test2 { type ipv4_addr : mark elements = { 127.0.0.1 : 0x0000000a, 127.0.0.2 : 0x0000000b } } chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 30 bytes 1890 ip saddr @test counter packets 18 bytes 1134 } } ``` 最後,如果想要使用這個 `map`,比如說「如果有封包的來源 IP 出現在 `test2` 的 key 當中,就把這個封包的 conntrack mark 設為對應的 value」: ```clike $ sudo nft add rule ip foo bar ct mark set ip saddr map @test2 ``` 這時候整個規則會變成: ```clike table ip foo { set test { type ipv4_addr elements = { 127.0.0.1 } } map test2 { type ipv4_addr : mark elements = { 127.0.0.1 : 0x0000000a, 127.0.0.2 : 0x0000000b } } chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 20 bytes 1260 ip saddr @test counter packets 12 bytes 756 ct mark set ip saddr map @test2 } } ``` 如果使用 `conntrack` 檢視: ```shell $ sudo conntrack -L ``` 就會發限從 `127.0.0.1` 發出的封包中,`mark` 被設為 `10`,恰好就是剛剛的 `0x0a`: ``` tcp 6 431999 ESTABLISHED src=172.20.10.8 dst=172.20.10.13 sport=22 dport=49367 src=172.20.10.13 dst=172.20.10.8 sport=49367 dport=22 [ASSURED] mark=0 use=1 udp 17 28 src=127.0.0.1 dst=127.0.0.53 sport=35412 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=35412 [ASSURED] mark=10 use=1 udp 17 29 src=127.0.0.1 dst=127.0.0.53 sport=41129 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=41129 [ASSURED] mark=10 use=1 udp 17 12 src=127.0.0.1 dst=127.0.0.53 sport=60459 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=60459 [ASSURED] mark=10 use=1 conntrack v1.4.6 (conntrack-tools): 4 flow entries have been shown. ``` 如果把 `test2` 中的 `127.0.0.1` 對應的數值換掉,比如說換成 `0x77`: ``` $ sudo nft delete element ip foo test2 "{127.0.0.1}" $ sudo nft add element ip foo test2 "{127.0.0.1:0x77}" ``` 所以現在規則變成: ```clike table ip foo { set test { type ipv4_addr elements = { 127.0.0.1 } } map test2 { type ipv4_addr : mark elements = { 127.0.0.1 : 0x00000077, 127.0.0.2 : 0x0000000b } } chain bar { type filter hook input priority filter; policy accept; ip saddr 127.0.0.1 counter packets 34 bytes 2142 ip saddr @test counter packets 26 bytes 1638 ct mark set ip saddr map @test2 } } ``` 這時候再使用 `conntrack -L`,就會發現之後的 `mark` 從 `10` 變成 `119`,恰好就是 `0x77`: ``` tcp 6 431999 ESTABLISHED src=172.20.10.8 dst=172.20.10.13 sport=22 dport=49367 src=172.20.10.13 dst=172.20.10.8 sport=49367 dport=22 [ASSURED] mark=0 use=1 udp 17 26 src=127.0.0.1 dst=127.0.0.53 sport=57975 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=57975 [ASSURED] mark=119 use=1 udp 17 29 src=127.0.0.1 dst=127.0.0.53 sport=43474 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=43474 [ASSURED] mark=119 use=1 conntrack v1.4.6 (conntrack-tools): 3 flow entries have been shown. ``` 注意上面這個「先刪除再重新加入」的動作不是 atomic 的,所以在這兩個動作中間傳輸的封包 `mark` 就不會被設定(會是 0),因此就會有類似以下的結果: ``` tcp 6 431999 ESTABLISHED src=172.20.10.8 dst=172.20.10.13 sport=22 dport=49367 src=172.20.10.13 dst=172.20.10.8 sport=49367 dport=22 [ASSURED] mark=0 use=1 udp 17 29 src=127.0.0.1 dst=127.0.0.53 sport=55640 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55640 [ASSURED] mark=119 use=1 udp 17 12 src=127.0.0.1 dst=127.0.0.53 sport=55621 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=55621 [ASSURED] mark=0 use=1 udp 17 14 src=127.0.0.1 dst=127.0.0.53 sport=51085 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=51085 [ASSURED] mark=119 use=1 udp 17 23 src=127.0.0.1 dst=127.0.0.53 sport=36802 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=36802 [ASSURED] mark=119 use=1 conntrack v1.4.6 (conntrack-tools): 5 flow entries have been shown. ``` ## 其他 [*Adding new expressions to nftables*](https://zasdfgbnm.github.io/2017/09/07/Extending-nftables/) 是一篇介紹如何在 netfilter 的虛擬機中加入一個 expression (`abcde`) 的實驗。