nftables を学び始めると、ついコマンドの細部に入りがちです。ルールをどう追加するか、handle をどう削除するか、ポートマッチをどう書くか。もちろんコマンドは重要ですが、先にフレームワークの概念を整理しておくと、ルールの読解、問題調査、ルールセット設計がかなり楽になります。
nftables は、次のような階層構造として理解できます。
tableはルール空間を分離する。familyはルールが扱うネットワークプロトコルを決める。chainはルールがどの段階で実行されるかを決める。ruleは具体的なマッチ条件とアクションを持つ。set、map、verdict mapは重複ルールを減らし、ルールセットを保守しやすくする。
以下では、概念を階層ごとに整理します。
table:ルールの名前空間
table は nftables で最も外側にあるルールコンテナです。異なる table は互いに分離されるため、関連するルールを同じ table にまとめるのが一般的です。
たとえば、フィルタリングルール、NAT ルール、独自のテスト用ルールを分けて置けます。こうすると境界が明確になります。デバッグ時にはどのルール群を変更しているかが分かりやすく、クリーンアップ時にも無関係な内容を誤って削除しにくくなります。
table 自体はパケットを直接処理しません。実際にパケット処理に関わるのは、table の中にある chain と rule です。
family:ルールが対象にするプロトコル
table を作成するときは family を選びます。これは、その table 内のルールがどの種類のパケットに適用されるかを決めます。
代表的な family は次のように理解できます。
ip:IPv4 のみを処理する。ip6:IPv6 のみを処理する。inet:IPv4 と IPv6 の両方を処理する。arp:ARP を処理する。bridge:ブリッジ層のトラフィックを処理する。netdev:ネットワークデバイス入口に近く、より早い段階でトラフィックを処理する用途に向く。
通常のファイアウォールルールでは、inet がよく使われます。IPv4 と IPv6 のルールを同じ table に置けるため、似た構造のルールを 2 セット維持する必要がなくなります。
chain:ルールが実行される場所
chain は rule のリストです。パケットがある hook に入ると、chain 内のルールを順番に通過します。
chain は大きく 2 種類に分けられます。
- 基本 chain:カーネルのネットワーク経路上の hook に接続され、パケット処理の流れから直接呼び出される。
- 通常 chain:hook には直接接続されず、他のルールから jump されて呼び出される。
基本 chain では、通常いくつかの重要な属性を指定します。
type:この chain の用途。たとえばfilter、nat、route。hook:接続する処理段階。たとえばprerouting、input、forward、output、postrouting。priority:同じ hook に複数の chain があるとき、どれを先に実行するか。policy:どのルールにもマッチしなかった場合のデフォルト動作。一般的にはacceptまたはdrop。
chain を理解するうえで重要なのは、ルールは「どこに書いても同じように効く」わけではないという点です。同じルールでも、input、forward、output のどこに置くかで意味はまったく変わります。
rule:マッチ条件とアクション
rule は、nftables が実際に判断を行う場所です。通常は 2 つの部分で構成されます。
- マッチ条件:送信元 IP、宛先 IP、プロトコル、ポート、インターフェイス、接続状態など。
- アクション:
accept、drop、reject、counter、jump、returnなど。
ルールは順番に評価されます。パケットが処理を終了させるアクションにマッチすると、その後ろのルールは実行されません。マッチしなければ、chain の終端またはデフォルトポリシーに到達するまで処理が続きます。
そのため、ルールの順序は重要です。より具体的なルールは、通常より広い条件のルールより前に置く必要があります。そうしないと、実行される機会がない場合があります。
set:値の集合をまとめる
多数の IP、ポート、インターフェイスをマッチしたい場合、ルールを大量に並べると保守しにくくなります。set は、同じ型の値をひとまとめに管理するための仕組みです。
たとえば、信頼する IP の集合、拒否したいポートの集合、帯域制限したいアドレスの集合を set に入れられます。ルール側では、ある値がその set に含まれるかどうかだけを判定すれば済みます。
set の利点は次のとおりです。
- ルール数を減らせる。
- 可読性が上がる。
- 後から要素を追加・削除しやすい。
ルールの中に同じような条件が大量に出てくる場合は、set の利用を検討するタイミングです。
map:マッチした値を結果に変換する
map は「参照表」として理解できます。入力値に応じて結果を返します。
たとえば、ポートごとに異なる mark を返す、アドレスごとに異なる処理パラメータを返す、といった表現ができます。多くの if/else 風ルールを書くより、map のほうが集中管理しやすく、保守もしやすくなります。
set が気にするのは「集合に含まれるかどうか」です。一方で map が気にするのは「この値に対応する結果は何か」です。
verdict map:マッチした値をアクションに変換する
verdict map は map の重要な使い方の 1 つです。マッチした値を verdict、つまりルールのアクションに変換します。
たとえば、IP 範囲ごとに accept、drop、または別 chain への jump を対応させられます。これにより、多くの分岐判断を 1 つの構造に圧縮できます。
ルールセットが複雑になってきたとき、verdict map はとても便利です。重複ルールを減らし、長い条件分岐の列ではなく、表に近い形でポリシーを表現できます。
概念からルール設計を考える
nftables ルールを設計するときは、次の順序で考えると整理しやすくなります。
- まずルールが属する
familyを決める。 - 次に、どの
tableに入れるかを決める。 - 適切な
hookとchainを選ぶ。 - 具体的な
ruleを書く。 - 重複条件が多い場合は、
set、map、verdict mapを導入する。
この順序で書くと、ルールは保守しやすく、問題も切り分けやすくなります。
まとめ
nftables の概念は複雑ではありませんが、階層が重要です。
- table はルールの境界を管理する。
- family はプロトコル範囲を管理する。
- chain は実行位置を管理する。
- rule はマッチとアクションを管理する。
- set、map、verdict map は複雑さを管理する。
まずこれらの概念を理解してから具体的なコマンドを見るほうが、コマンドを直接暗記するより安定します。特にルールセットが増えてきたあとでは、概念が明確だと、問題がプロトコル範囲、実行段階、ルール順序、またはマッチ条件そのもののどこにあるのかを判断しやすくなります。