nftablesのあれこれ
Oct 10, 2025
TL;DR
- nftables はルールを命令として変換し、専用 VM 内でパケット評価を行っている。
- –debug=netlink オプションを使うことで、変換後の命令コードを見ることができる。
- たとえばポート番号を評価する時、{80-80}と{80}では異なる命令コードになる。
- netlink 経由で流し込まれる命令コードは nft monitor で見ることができる。
- NFTA_GEN_PROC_PID 属性により、どのプロセス(PID)が流し込んだかもわかる。
nftables VM
https://wiki.nftables.org/wiki-nftables/index.php/Ruleset_debug/VM_code_analysis
> $ sudo nft --check --debug=netlink add rule ip filter FORWARD tcp sport 80 accept
ip filter FORWARD
[ meta load l4proto => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 0 => reg 1 ]
[ cmp eq reg 1 0x00005000 ]
[ immediate reg 0 accept ]
たとえば上記のコマンドから nftables vm の命令コードを受け取った場合、以下のように解釈できる
- meta load l4proto => reg 1
- メタデータから L4 プロトコルを読み込み、レジスタ 1 に格納
- cmp eq reg 1 0x00000006
- レジスタ 1 の値が 0x00000006(TCP)と等しいか比較
- payload load 2b @ transport header + 0 => reg 1
- トランスポートヘッダのオフセット 0 から 2 バイトを読み込み、レジスタ 1 に格納
- cmp eq reg 1 0x00005000
- レジスタ 1 の値が 0x00005000(ポート 80)と等しいか比較
- host cpu のエンディアンに依存するため、0x5000 になる
- immediate reg 0 accept
- 条件がすべて満たされた場合、パケットを受け入れる
命令コードの例・違い
- 80-80 のように同じ値を指定しても律儀に範囲チェックを行う
> $ sudo nft --check --debug=netlink "add rule ip filter FORWARD tcp sport {80-80} accept"
ip filter FORWARD
[ meta load l4proto => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 0 => reg 1 ]
[ cmp gte reg 1 0x00005000 ]
[ cmp lte reg 1 0x00005000 ]
[ immediate reg 0 accept ]
- {80,80} のように同じ値を複数指定しても set を作成してから評価する
> $ sudo nft --check --debug=netlink "add rule ip filter FORWARD tcp sport {80,80} accept"
__set%d filter 3 size 2
__set%d filter 0
element 00005000 : 0 [end] element 00005000 : 0 [end]
ip filter FORWARD
[ meta load l4proto => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 0 => reg 1 ]
[ lookup reg 1 set __set%d ]
[ immediate reg 0 accept ]
- 単一の値の場合は{}で括っていても eq チェックとして最適化される
> $ sudo nft --check --debug=netlink "add rule ip filter FORWARD tcp sport {80} accept"
ip filter FORWARD
[ meta load l4proto => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 0 => reg 1 ]
[ cmp eq reg 1 0x00005000 ]
[ immediate reg 0 accept ]
- raw packet match の場合でもやっていることは同じ。
> $ sudo nft --debug=netlink "add rule ip filter FORWARD ip protocol ip @th,0,8 0x45 accept"
ip filter FORWARD
[ payload load 1b @ network header + 9 => reg 1 ]
[ cmp eq reg 1 0x00000000 ]
[ payload load 1b @ transport header + 0 => reg 1 ]
[ cmp eq reg 1 0x00000045 ]
[ immediate reg 0 accept ]
nft monitor
nft monitor コマンドを使うことで、netlink 経由で流し込まれた命令コードをリアルタイムで拾うことができる
> $ sudo nft monitor
add rule ip filter FORWARD tcp sport 80 accept
# new generation 90 by process 823461 (nft)
Q. netlink 経由では命令セットで流れているはずだが、なぜ human readable な形で出力されるのか? A. nft monitor は流れてきた命令コードを解釈し、human readable な形に変換して出力している
そのため、nft monitor の出力結果は必ずしも元の命令コードと完全に一致するわけではない。 (nft list ruleset などの表示系も同様のことを行なっている)
NFTA_GEN_PROC_PID / NFTA_GEN_PROC_NAME
Linux 4.14 からは netlink メッセージに NFTA_GEN_PROC_PID / NFTA_GEN_PROC_NAME 属性が追加され、どのプロセスがルールを流し込んだかを知ることができる。
# new generation 90 by process 823461 (nft)