OpenBSD VPN ゲートウェイで tun0 経由の LAN を NAT する
OpenBSD の tun0 出口から出る LAN トラフィックについて、送信元 NAT と戻りルートのどちらを選ぶかを決め、pf が通過させる位置に nat-to ルールを置きます。
同じ OpenBSD ゲートウェイを渡された二人のエンジニアが、遠隔ネットワークへの到達方法として別々のルールセットを出すことはよくあり、どちらも腕は通ります。送信元変換と戻りルートのどちらを選ぶかは、構文の問題ではまったくありません。遠隔側が 192.168.88.0/24 についてすでに何を知っているか、そしてログに何を残したいかで決まります。ここを間違えると、nat-to 行は完璧に見えるのに応答は遠隔のデフォルトゲートウェイへ消えていきます。
ルーティングと書き換えは別の問いに答える
ルーティングテーブルが 172.20.0.0/16 は tun0 経由で到達可能だと決め、その判断は pf の変換が参照されるより前に下されます。次に送信元変換がパケットヘッダーを変え、遠隔側には LAN ホストではなくゲートウェイのトンネルアドレスが見えます。どちらの仕組みも他方を代替しません。nat ルールはルートを作れず、ルートはあなたのプライベートサブネットを知らない遠隔ルーターを説得できません。遠隔側に戻り経路がなければ、応答は破棄されるか相手のデフォルトゲートウェイへ転送され、ルールをどう調整しても直りません。
平たく言えば、転送はトラフィックがどこへ行くかに答え、変換はどこから来たように見えるかに答えます。ルールセットを設計するときは、両者を別の引き出しに入れておいてください。
決めるのは三つの条件
三つの問いを順に立てれば、設計はたいてい自然に決まります。遠隔ネットワークには 192.168.88.0/24 向けにトンネルアドレスを指す静的ルートがあるか、その経路上のルーターを管理者が握っているか。答えがはいなら、LAN アドレスをそのまま通し、変換は完全に省きます。遠隔側でクライアント単位の可視性が必要か、アクセス制御のためでも監査証跡のためでも構いません。必要なら、送信元を保つ価値はルーティングの手間に十分見合います。トンネルアドレスは遠隔ルートに書けるほど安定しているか、それとも再接続のたびに変わるか。
どれか一つの答えがいいえ、または不明なら、nat-to が現実的な選択で、しかも元に戻せます。遠隔側が適切なルートを追加すれば、変換ルールは一行で外れます。導入されなかった戻りルートは、トンネルが一方向しか動かない理由を誰かに聞かれるたびに、サポートのやり取りを一つ消費します。
pf が実際に評価する位置に断片を置く
以下の断片は、LAN が 192.168.88.0/24、遠隔ネットワークが 172.20.0.0/16、トンネルインターフェースが tun0 であることを前提にしています。この中には次ホップを選ぶものはありません。すでにトンネルから出ていくパケットの送信元を書き換えるだけです。
match out on tun0 inet from 192.168.88.0/24 to 172.20.0.0/16 nat-to (tun0)
pass in on em0 inet from 192.168.88.0/24 to any keep state
最初の行の二つの細部が大半の重みを担います。tun0 を囲む括弧は、状態エントリを構築するたびにインターフェースアドレスを読むよう pf に指示し、再接続後にトンネルが再番号付けされるときに効きます。match という語は、ルールが書き換えたあと下へ抜けることを意味するので、別の pass ルールが依然としてフローを許可しなければなりません。match 行で名指ししたインターフェースは、ルーティングテーブルが選ぶものと同じでなければなりません。そうでなければ pf はパケットが実際に通る経路とは別の外向き経路でルールを評価します。
カーネル側も忘れないでください。IPv4 転送を有効にする必要があり、トンネルと LAN のステートフルな pass ルールが存在しなければ、ここまでの話は意味を持ちません。キーワードの順序については OpenBSD FAQ の PF NAT の節 が適切な参照先です。
トラフィックを信頼する前にルールセットを検証する
まず pfctl -nf /etc/pf.conf — ファイルを読み込まずに解析するコマンドです — で構文を確認します。次にポリシーを読み込み、カーネルが実際に保持している内容を読み返してください。きれいに解析されたルールでも影に隠れたり誤ったインターフェースに付いたりすることがあるからです。pfctl -sn は変換ルールを読み込み後の形で出力し、pfctl -sr はフィルタールールを評価順に出力します。
次にケーブルを観察します。ゲートウェイ上で tcpdump -ni tun0 'host 172.20.0.10' を実行すると、トンネルを出る送信元アドレスがトンネルのものか LAN ホストのものかが分かります。手早く netstat -rn -f inet を打てば、宛先がどのインターフェースに解決されるかを確認できます。トラフィックが流れ始めたら pfctl -ss が状態エントリを一覧します。フローが状態を作っていなければ、そのパケットは状態を作るルールに一度も一致していません。
トンネル側が正しく見えるのに応答が届かないなら、pf の編集をやめてください。残る障害は遠隔ルーターにあり、応答はそちらが到達できないプライベートアドレスへ送られています。
変換が節約より高くつくとき
変換はクライアントを隠し、それがちょうど間違った取引であることもあります。重複するサブネットはその一例です。遠隔側も内部で 192.168.88.0/24 を使っているなら、LAN ホストをゲートウェイとして見せることは衝突を和らげるどころか悪化させます。アドレスペアでトラフィックを選ぶ IPsec ポリシーも別の例です。セキュリティポリシーデータベースは変換より前に照合され、書き換え後のパケットを決して見ません。ユーザーごとに課金し、帯域を制限し、監査する配備も、送信元が一つのアドレスに圧縮された瞬間に必要な身元を失います。
移植版の pf 派生製品は別の罠です。独自のルールファイルを生成する製品は、NAT をそのインターフェース経由で設定することを前提にしており、手で編集した断片は次回の再読み込みで上書きされます。match キーワードが存在する前に書かれた解説からコピーしたルールセットは現在のカーネルでは読み込まれないので、構文を移植し、推測せずにエラーを読んでください。
実際のネットワークと接して生き残る判断則は短いものです。できるときは戻りルートを入れ、できないときは変換し、三つの条件のどれがその選択をさせたかを書き留めてください。そのメモが次の人の半日を節約します。