nftables備忘録

## この記事で説明すること・しないこと

説明すること
- nftablesの概要
- netfilterの概要
- nftablesでのパケットフィルタリングの記法
- nftablesの設定方法（nftファイル）

説明しないこと
- TCPのようなプロトコルの概要
- nftablesの有効化（いわゆるsystemdに登録するといった話）

まずnftablesの概要に触れ、構文の解説を行い、最後に設定を反映させる方法を述べます。

簡易のため、必ずしも正確な説明がなされていない箇所がいくつかあるかと思います。
また、この記事でnftablesのすべての表現を網羅しているわけではありません。
必要に応じてmanページや参考資料を確認してください。

## nftablesとは

2023年時点で最新のLinuxカーネルにおけるパケットの制御ツールです。
iptablesなどのxtablesを置き換えるために開発され、FirewallDやUncomplicatedFirewall[^1]のバックエンドにあたります。
nftablesなどの下のレイヤーにはnetfilterというLinuxカーネルの機能があり、このラッパーとして機能します。

[^1]: firewalldやufwと呼ばれているツール。公式っぽいサイトを見に行ったら本文中の表記で驚いた。[firewalld/firewalld: Stateful zone based firewall daemon with D-Bus interface](https://github.com/firewalld/firewalld), [UncomplicatedFirewall - Ubuntu Wiki](https://wiki.ubuntu.com/UncomplicatedFirewall)

## ざっくりnetfilter

netfilterはLinuxカーネル用のパケットフィルタリングソフトであり、パケットやルーティングに関する様々な機能を提供します。

フック（Hook）という形でLinux内部でのパケットの流れに対して関数を設定でき、パケットが通過するたびに設定した関数を適用することができます。[^2]

[^2]: [netfilter/iptables project homepage - The netfilter.org project](https://www.netfilter.org/)

nftablesはこのフックに対して設定の枠組みを提供します。

フックについて、例えばローカルで動作するアプリケーションへパケットを流すIPレイヤーでは、
1. `prerouting hook`
1. `input hook`
1. アプリケーション
1. `output hook`
1. `postrouting hook`
の順にパケットが流れていき、それぞれのフックで処理されていきます。

## フィルタリングの例

```sh
nft list ruleset
```

で現在設定されているルールを出力できます。ひとまず見てみると良いでしょう。

```
table ip filter {
	chain INPUT {
		type filter hook input priority filter; policy accept;
		ct state related,established counter packets 720379559 bytes 398093490153 accept
		meta l4proto icmp counter packets 0 bytes 0 accept
		iifname "lo" counter packets 24175 bytes 2731917 accept
		meta l4proto udp udp sport 123 counter packets 0 bytes 0 accept
		meta l4proto tcp ct state new tcp dport 22 counter packets 3525 bytes 201684 accept
		counter packets 9704 bytes 769125 reject with icmp type host-prohibited
	}
}
```

手元のマシンの設定の一部分を抜き出しました。

1行目はテーブル名とどのファミリー、つまりどのプロトコル（IPv4、IPv6、ARPなど）かを指定します。

そして2、3行目に"hook"というキーワードが見えます。
これがnetfilterのフックにあたり、それ以降の1行1行が具体的なフィルタリングルールとなっています。

例えば`counter packets 720379559 bytes 398093490153`は、本来は`counter`の部分だけがステートメントでこのステートメントを通過したパケットを数えてくれます。

## ルール(rule)

ここから構文について述べます。
かっこ書きで英訳が示されている場合、[Man page of NFT](https://www.netfilter.org/projects/nftables/manpage.html)に書かれているものと対応していることが基本となっています。

ルールとは、nft（nftablesのフロントエンドツール）を通じて表示した時に表示される1行1行のことです。
これは式とステートメントの組み合わせで表現されます。
つまり式を`<e>`、ステートメントを`
`で表すなら、おおよそ`rule := (<e> | 
)+`のように書けます。
1行の中に式とステートメントどちらかが1つ以上あればルールになるということです。

そして定義から察する方もいるかと思われますが、nftablesのルールを読む上での壁として*式とステートメントが入り混じっているので非常に読みにくい*ことが挙げられるでしょう。

上の例で言えば、
```
meta l4proto tcp ct state new tcp dport 22 counter accept
```
は[^3]
```
meta l4proto tcp | ct state new | tcp dport 22 | counter | accept
```
と区切ることができ、
```
<e> | <e> | <e> | 
 | 

```
となります。

[^3]: ctはconnection trackingの略

式とステートメントの違いを以下で具体例を挙げつつ述べましょう。

### 式(expression)

式はIPアドレスやポート番号、プロトコルやこれらを組み合わせた論理式などを表します。
そしてこれらに合致するパケットのみ、そのルールの後ろの式やステートメントに渡します。

基本的に式を用いることでパケットを絞り込んでいきます。

具体例を出していきます。

`meta l4proto udp`はメタ式（meta expressions）に分類され、「L4レイヤのプロトコルがUDPである」パケットに合致します。

`iifname`（input interfaceの略）や`oifname`（output interfaceの略）のように`meta`が省略できるものも多いです。
例えば`iifname "lo"`は頭の`meta`が省略されています。

`tcp dport 22`はTCPヘッダー式（TCP header expression）であり、「TCPプロトコルの宛先アドレスが22番である」パケットに合致します。
ヘッダー式は特定のプロトコル（IPv4、ICMP、TCP、etc.）のパケットのペイロードを絞り込めます。

`iifname != "hogehoge0"`は論理式で「入力インタフェース名が"hogehoge0"でない」パケットに合致します。おおよそ直感的に読めるかと思います。

筆者が軽く触った程度ではメタ式とヘッダー式と論理式の3種類で式の大半を占めるでしょう。

### ステートメント(statements)

ステートメントはパケットに対する具体的な処理を表します。
ざっくりと言えば「パケットをacceptしたりdropしたりする部分」です。

式で絞り込んだものに対してパケットを受け入れるのかそうでないのかを決めます。
また、パケットの中身を編集したり、ログを取るのもステートメントの役割です。

特によくみる評決ステートメント（verdict statement）を以下に示します。
これはmanページの定義をほぼそのまま引っ張ってきますが、一部のみ解説します。

```
{ accept | drop | queue | continue | return } | { jump | goto } chain名
```

`accepct`、`drop`はその場でパケットを受け入れるかどうかを決めます。
`return`は`jump`で飛んできたパケットを元のチェインへ戻します。戻る先がなければチェインのポリシーに従います。
`jump`や`goto`はチェイン名を引数をとりそこへ飛びます。違いはチェインの一番下まで行った時に帰ってくるかそうでないかです。
チェインについては下で述べます。

他のステートメントでできることとして、
- パケットを編集
- ログを取りローカルに書き込む
- パケットを受け入れなかったことを通知
- レート制限
- NAT

などが挙げられます。

よく見かけるのは`counter`で、おおよそ`counter packets xxx bytes xxx`という形になっているかと思われます。
これは単純に通過したパケットとバイト数の合計を保持し、`nft list`コマンドで表示できます。
雑に設定されているため慣れていないと構文をパースしにくいのですが、慣れると雑に設定したくなります。

## チェイン(chain)

チェインはルールを集めたもので2種類に分けられます。
フックに繋げられている方がベースチェイン（base chain）、フックと直接繋がっていないレギュラーチェイン（regular chain）です。

ベースチェインの例を示しましょう。

```
chain INPUT {
    type filter hook input priority filter; policy accept;
    ct state related,established ...
```

`INPUT`がチェイン名です。ここはなんでもよく、フックと同じ名前でなければならないわけではありません。
2行目にベースチェインとしての設定が書いてあります。
細かく見ていきましょう。

- `type filter`ではチェインのタイプ（type）を選択します。
  タイプは"filter"、"route"、"nat"の3つです。筆者はfilterとnatしか触ったことがありません。
- `hook input`はnetfilterのフックを決めており、ここではinputフックを選択します。
- `priority filter`は同じフックの中の優先度を選択します。
  優先度は整数で`filter`はnftのキーワードとして0が割り当てられています。
- `policy accept`はデフォルトのポリシーを定めており、チェインの最後までacceptやdropされなかった時の挙動を設定します。

フックと優先度に関しては[Netfilter hooks - nftables wiki](https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks)がわかりやすくまとまっているかと思われます。

レギュラーチェインは説明が簡単で、ベースチェインのような1行目の定義がないチェインです。
定義がなければフックには繋がらないため、他のチェインからパケットを送ってもらうしかなくなります。
ここで`jump`ステートメントなどが必要になってきます。

## テーブル(table)

テーブルはチェインをファミリーごとに複数個集めたものです。
チェインとルールの関係に近いものがあります。

例を示しましょう。

```
table ip filter {
	chain INPUT {
		type filter hook input priority filter; policy accept;
		ct state related,established ...
```

1行目でテーブルの定義を行っています。`table ファミリー テーブル名`という構文で定義されます。

ファミリーはどのプロトコル（IPv4、IPv6、ARPなど）かを指定しており、`ip`ならIPv4とIPv6両方にまとめて適応されます。

テーブル名は何でも良いです。追加したアプリケーションが設定する際はチェインのタイプごとに分けられていることが多いようです。

## nftablesの設定方法

さて、ここまで構文について解説してきましたが、これを実際に適応する方法を述べていませんでした。

nftablesには2つの設定方法があります。
1. ルールなどを1行の単位でnftを叩き設定する、もしくはnftを叩くコマンドをファイルにし、`nft -f nftファイル`で反映させる
1. nftファイルを書き、`nft -f nftファイル`で反映させる

1つ目の書き方はiptablesの書き方に似ているようです。が、ここでは述べません。筆者はこの方法で触ったことがほぼないためです。

2つ目の書き方は今まで説明した記法で書き、これを読み込ませる方法です。
`/etc/nftables.conf`に書き込んでおくと起動のタイミングで読み込んでくれます。
`nft -f nftファイル`はこれを強制的に置き換えるものです。

どちらの方法で書いても読み込ませる時は「設定前の状態」から「設定後の状態」に直接移行します。
これを[nftables wiki](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page)ではアトミックなルールの置き換えなどと呼んでいます。

## 後書き

備忘録として書いたため、かなり端折っている箇所や全く説明しなかった機能が多々あります。
nftables wikiが様々な機能を網羅的に書いてあるのですが、説明が不十分です。
ここで説明されていない機能や挙動については実際に実験するのが良いかと思います。

## 参考資料

- [nftables wiki](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page)
- [firewalld/firewalld: Stateful zone based firewall daemon with D-Bus interface](https://github.com/firewalld/firewalld)
- [UncomplicatedFirewall - Ubuntu Wiki](https://wiki.ubuntu.com/UncomplicatedFirewall)
- [netfilter/iptables project homepage - The netfilter.org project](https://www.netfilter.org/)
- [Man page of NFT](https://www.netfilter.org/projects/nftables/manpage.html)
- [Linuxにおける新たなパケットフィルタリングツール「nftables」入門 | さくらのナレッジ](https://knowledge.sakura.ad.jp/22636/)
- [nftables - ArchWiki](https://wiki.archlinux.jp/index.php/Nftables)