この記事で説明すること・しないこと
説明すること
- nftablesの概要
- netfilterの概要
- nftablesでのパケットフィルタリングの記法
- nftablesの設定方法(nftファイル)
説明しないこと
- TCPのようなプロトコルの概要
- nftablesの有効化(いわゆるsystemdに登録するといった話)
まずnftablesの概要に触れ、構文の解説を行い、最後に設定を反映させる方法を述べます。
簡易のため、必ずしも正確な説明がなされていない箇所がいくつかあるかと思います。
また、この記事でnftablesのすべての表現を網羅しているわけではありません。
必要に応じてmanページや参考資料を確認してください。
nftablesとは
2023年時点で最新のLinuxカーネルにおけるパケットの制御ツールです。
iptablesなどのxtablesを置き換えるために開発され、FirewallDやUncomplicatedFirewall1のバックエンドにあたります。
nftablesなどの下のレイヤーにはnetfilterというLinuxカーネルの機能があり、このラッパーとして機能します。
ざっくりnetfilter
netfilterはLinuxカーネル用のパケットフィルタリングソフトであり、パケットやルーティングに関する様々な機能を提供します。
フック(Hook)という形でLinux内部でのパケットの流れに対して関数を設定でき、パケットが通過するたびに設定した関数を適用することができます。2
nftablesはこのフックに対して設定の枠組みを提供します。
フックについて、例えばローカルで動作するアプリケーションへパケットを流すIPレイヤーでは、
prerouting hook
input hook
- アプリケーション
output hook
-
postrouting hook
の順にパケットが流れていき、それぞれのフックで処理されていきます。
フィルタリングの例
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に書かれているものと対応していることが基本となっています。
ルールとは、nft(nftablesのフロントエンドツール)を通じて表示した時に表示される1行1行のことです。
これは式とステートメントの組み合わせで表現されます。
つまり式を<e>
、ステートメントを<s>
で表すなら、おおよそrule := (<e> | <s>)+
のように書けます。
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> | <s> | <s>
となります。
式とステートメントの違いを以下で具体例を挙げつつ述べましょう。
式(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がわかりやすくまとまっているかと思われます。
レギュラーチェインは説明が簡単で、ベースチェインのような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行の単位でnftを叩き設定する、もしくはnftを叩くコマンドをファイルにし、
nft -f nftファイル
で反映させる - nftファイルを書き、
nft -f nftファイル
で反映させる
1つ目の書き方はiptablesの書き方に似ているようです。が、ここでは述べません。筆者はこの方法で触ったことがほぼないためです。
2つ目の書き方は今まで説明した記法で書き、これを読み込ませる方法です。
/etc/nftables.conf
に書き込んでおくと起動のタイミングで読み込んでくれます。
nft -f nftファイル
はこれを強制的に置き換えるものです。
どちらの方法で書いても読み込ませる時は「設定前の状態」から「設定後の状態」に直接移行します。
これをnftables wikiではアトミックなルールの置き換えなどと呼んでいます。
後書き
備忘録として書いたため、かなり端折っている箇所や全く説明しなかった機能が多々あります。
nftables wikiが様々な機能を網羅的に書いてあるのですが、説明が不十分です。
ここで説明されていない機能や挙動については実際に実験するのが良いかと思います。
参考資料
- nftables wiki
- firewalld/firewalld: Stateful zone based firewall daemon with D-Bus interface
- UncomplicatedFirewall - Ubuntu Wiki
- netfilter/iptables project homepage - The netfilter.org project
- Man page of NFT
- Linuxにおける新たなパケットフィルタリングツール「nftables」入門 | さくらのナレッジ
- nftables - ArchWiki
-
firewalldやufwと呼ばれているツール。公式っぽいサイトを見に行ったら本文中の表記で驚いた。firewalld/firewalld: Stateful zone based firewall daemon with D-Bus interface, UncomplicatedFirewall - Ubuntu Wiki ↩
-
netfilter/iptables project homepage - The netfilter.org project ↩
-
ctはconnection trackingの略 ↩