Netfilter と BPF/XDP

このページの目次

ポートフォワーディングは設定済みだが、INPUT への許可のみで接続が通らない。まずパケットがルーティングとフックをどのように通過するかを特定し、次にルール、トラッキング、NAT について議論する。XDP/TC は異なる処理レイヤーに位置する。

参照目標読書位置
パスとチェーンの制御フロー第 1~2 節
ルールの分離実験と conntrack第 3~4 節
NAT、BPF、オフロードの観測第 5~7 節

1. パケットのパスを判断する

ローカル受信、ローカル送信、フォワーディングは異なるフックを経由する PREROUTING 外部パケットのエントリポイント ルーティング判断 ローカル / フォワーディング INPUT ローカルプロトコルスタックへ FORWARD 転送パケット POSTROUTING 出口処理 OUTPUT ローカル生成パケット OUTPUT の前には通常、初期ルーティングが既に存在する。ルールでアドレスや mark を変更すると、再ルーティングが必要になる可能性がある。

ローカルサービス宛ての外部パケットは、通常、PREROUTING、INPUT を経る。ローカルプロセスから送信されるパケットは、OUTPUT、POSTROUTING を経る。ルーティングによる転送パケットは、PREROUTING、FORWARD、POSTROUTING を経る。ブリッジ、netdev ingress、トンネリング、オフロードには他のパスが存在し、すべてのパケットが必ずすべての 5 つのフックを通過するわけではない。

DNAT はルーティングの前に宛先アドレスを変更するため、ゲートウェイアドレス宛てのトラフィックを内部サーバーへの転送に変えることができる。したがって、ポートフォワーディング後のパケットには、INPUT への許可だけでなく、FORWARD ルールが必要になる場合が多い。

同じフック内では、数値の小さい priority から順に実行される。異なる base chain はフックを共有できるが、同じ priority の相対順序はポリシー依存になってはならない。Netfilter hooks を参照。

2. nftables の table、chain、rule、set

オブジェクト役割
familyプロトコル/処理ドメインを指定。例:ip、ip6、inet、bridge、netdev
tablechain、set、map などのオブジェクトを整理。固定された実行フェーズではない
base chainフック、priority、policy にバインドされ、実行エントリポイントとなる
regular chainjump/goto などのルールによって入り、フックには直接バインドされない
rule順序通りに評価されるマッチ式とアクション
set / map集合クエリまたはキーから値へのマッピングを表現し、重複ルールを削減

nftables は依然としてチェーン内のルールの順序で評価を行う。線形なルールチェックから完全に解放されたわけではない。集合は表現力と特定クエリの効率を改善できるが、「任意の nft ルールセットは一度だけ走査され、iptables より速い」と無条件に主張することはできない。

iptables コマンドは nft バックエンドを使用する場合があり、コマンド名だけでバックエンドが legacy xtables であると判断することはできない。複数のツールで共同管理する場合は、実際のバックエンドと現在の ruleset を確認する必要がある。

accept が常に最終的な通過証ではないわけではない

ある base chain において accept はそのチェーンの処理を終了するが、同じフックの他の base chain や、より後ろのフックでは依然として拒否できる。drop は通常、そのパケットの処理を直ちに終了させる。regular chain の return/jump と base chain の policy は、別の制御フローである。

実行位置送信元に対するアクション最終的に通過したか
input base chain、priority 0SSH を accept未通過。後のチェーンでも処理される
input base chain、priority 10送信元を dropパケットは拒否される

2 つのチェーンが結果を共同で決定する。前のチェーンの末尾に accept を繰り返しても、後のチェーンの drop を上書きすることはできない。nftables chain 設定 を参照。

以下に、異なる priority の base chain を 2 つ固定し、それぞれ宛先ポートと送信元条件を変更する。あるチェーンの accept の後でも、後のチェーンは拒否できる。drop は、後のルールが実行される機会を失わせる。

図を準備しています
前のチェーンで accept しても、なぜ後のチェーンで drop できるのか?

accept は現在の base chain の処理を終了するが、後のチェーンは依然として拒否できる。drop はこのパスを終了させる。同じフックを持つ複数の base chain は、priority の小さい順に実行される。

3. カウントのみを行う学習用ルールセット

observe.nft として保存する。デフォルトの accept ポリシーを変更せず、3 つの主要なパスからこのチェーンを通過するパケットをそれぞれカウントする。完全なセキュリティポリシーではない。

 table inet kb_observe {
     chain input {
         type filter hook input priority 0; policy accept;
         counter
     }
     chain forward {
         type filter hook forward priority 0; policy accept;
         counter
     }
     chain output {
         type filter hook output priority 0; policy accept;
         counter
     }
 }

非特権 user/network namespace をサポートするマシンでは、新しいネットワーク namespace を用いて確認およびロードできる。

unshare --user --map-root-user --net sh -eu -c '
    nft -c -f observe.nft
    nft -f observe.nft
    nft list table inet kb_observe
'

-c はトランザクションを適用せずにチェックするが、カーネルの関連するサポートと権限が必要である。ここでは外部インターフェースに接続していないため、namespace はプロセス終了時に回收される。システムで非特権 namespace が無効化されている場合、この例は失敗する。ホストのファイアウォールに直接ロードしてはいけない。実際のトラフィックのキャプチャには、同じ namespace 内でテストインターフェースと負荷を確立する必要がある。

nft のバッチトランザクションは、ユーザー空間でコマンドを逐条実行することによる部分的な更新ウィンドウを回避できるが、conntrack/NAT の状態がルール変更とともに再構築されることを意味するものではない。

4. Conntrack の状態は TCP socket 状態ではない

conntrack はプロトコル、双方向タプル、zone、状態、タイマーなどを記録し、TCP を処理できるだけでなく、UDP などのコネクションレスプロトコルに対してもフロートラッキングを作成できる。ct state established と ss で表示される TCP ESTAB は、同じ状態マシンではない。

NEW、ESTABLISHED、RELATED、INVALID、UNTRACKED はトラッキングの視点で記述される。RELATED には、関連する ICMP エラーや制御された helper によって確立された関係が含まれる可能性がある。notrack は十分に早い位置で実行する必要があり、トラッキングに依存する NAT/ルールの動作を変更する。

エントリの作成、確認、タイムアウト、破棄は、アプリケーションソケットのオープン/クローズとは異なる。テーブルが満杯の場合、新しいフローのレート、タイムアウト、zone、メモリ、異常トラフィックを確認する必要がある。nf_conntrack_max を引き上げるだけで解決すると仮定してはならない。count/max は容量の観測であり、エンドツーエンドの健康指標ではない。

5. NAT はマッピングを確立し、後のパケットは状態を再利用する

典型的なステートフル NAT は、フローの最初のパケットで binding を確立し、後のパケットは conntrack に保存されたマッピングに基づいて変換を行う。各パケットが NAT チェーンを再走査して新しいマッピングを選択するわけではない。NAT ルールを変更した後、既存のフローは古いマッピングを引き続き使用する可能性がある。

方向アドレスの変更
クライアント発信10.0.0.8:40000 が 198.51.100.20:443 にアクセス
ゲートウェイソース変換ソースアドレスが 203.0.113.5:51000 に変更
戻りトラフィックゲートウェイの 51000 宛てに送信され、逆方向の状態に基づいてクライアントの 40000 に復元

これらのアドレスは推論のためだけに使用される。戻りパケットの復元は既存のマッピングに依存しており、NAT を単方向の文字列置換として理解してはならない。

SNAT は通常、出口でソースマッピングを選択する。MASQUERADE は出口インターフェースのアドレスに基づいて動作し、インターフェースの状態変化に関連する処理を行う。DNAT は通常、PREROUTING/OUTPUT で宛先を変更する。nat INPUT チェーンを「ローカル宛ての DNAT」の汎用的な位置として記述してはならない。ソース変換の用途は別途理解する必要がある。

マッピングの外側:フォワーディングの許可と戻りルーティングは依然として成立する必要がある

NAT は IP forwarding を自動的に有効にせず、戻りルーティングを修復したり、FORWARD を許可したりしない。hairpin アクセスには、戻りトラフィックが同じステートデバイスを経由するように、対応するソース変換が必要になる場合もある。NAT ドキュメント を参照。

6. XDP と TC:処理位置が利用可能な情報を決定する

ネイティブ XDP はドライバの早期受信パスに位置し、少ないプロトコルスタックのオーバーヘッドでパケットの破棄、送信元への返送、またはリダイレクトを行うことができる。ジェネリック XDP はすでに skb パスにあり、ハードウェアオフロードは NIC のサポートに依存する。これら 3 つを固定の「skb 前フック」として混同してはならない。

XDP_PASS は、後のスタック処理を継続することを示す。XDP_TX は、受信デバイスから送信元へ返送することを示す。XDP_REDIRECT は、正しいリダイレクトターゲットと関連する準備に依存する。戻りアクションは、ルーティングの作成、MAC の変更、チェックサム更新、接続トラッキングの維持の完全な代替手段ではない。

TC は skb を処理し、キュー、分類、アドレス変更、リダイレクトなどの機能と組み合わせて適している。direct-action は TC_ACT_* を使用し、XDP の定数を戻り値としてコピーしてはならない。socket filter は、socket への受信データへの制御を委ねるものであり、「socket 作成フック」ではない。

BPF helper は conntrack、ルーティングなどに対して制限付きのインターフェースを持つが、可用性と責任はタイプ/バージョンによって異なり、プログラムがデフォルトで Netfilter の状態とルール全体を継承すると考えることはできない。プログラムのタイプについては eBPF プログラムタイプ を参照。

7. トラフィックのオフロードは観測可能なパスを変更する

Netfilter flowtable とハードウェアオフロードにより、確立されたフローはより短いパスを通過できる。最初のパケットでのポリシー確立と、後のトラフィックの実際の通過位置とカウントは、一緒に分析する必要がある。ある FORWARD カウンタがパケットごとに増加しないからといって、すべてのトラフィックが停止したわけではない。

パケットキャプチャが空の場合:まず観測点を特定し、パケットの到達を判断する

同様に、XDP による早期パケットドロップは、通常の packet socket によるキャプチャポイントよりも前で発生する可能性がある。ローカル tcpdump で見えない場合、パケットが来なかったのか、それとももっと前で処理されたのかは判断できない。調査時には、物理ポートのカウント、XDP/TC の状態、Netfilter trace/counter、socket 状態を組み合わせ、レイヤーごとに範囲を狭めていく。flowtable ドキュメント を参照。