# 計算機網路 - nftables
[TOC]
## 簡介

(圖片來源:[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: netfilter 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)

(圖片來源:[*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`) 的實驗。