IPFWの構造

本文では紙面の都合で説明ができませんでしたので,このHTMLドキュメントという形でIPFW+NATDの構造を説明したいと思います.

IPFWの内部構造

IPFWの構造
図1. IPFWの内部構造

壁が外側に出来る

右の図1をご覧下さい.これは,ネットワークインターフェイスカード(NIC)を2枚挿した場合を例にしたIPFWの掛かり方です.中央の「びーえすでぃー」と書かれたノートパソコンが今までのサーバだと思ってください.実際にはもちろんIPFW自体もこのパソコンの中で動いているわけですが,今まで外部と通信していたプログラム(今までの部分)の外側に貼り付いて守るようなイメージになるので便宜的にこのような図にしたわけです.
そして,図では左右と上部に合計3枚の壁が出来上がっていますが,これがIPFWによる壁です.NICを2枚挿した場合は下記のような,3箇所に壁が出来ます. 通常は用意したNICの数+1個の壁が出来ます.NICの略号にethXという表現を用いていて何だかLinuxみたいですが,FreeBSDではNICのドライバ毎に名前が代わるので便宜上このように表記しました.ちなみに当研究所のサーバでは,外部ネットワーク用はNE2000互換のチップを使ったNICでしてed1という名前になっており,内部ネットワーク用はIntelのi8255x互換のチップを使ったNICなためfxp0となっています.

通常(NIC用)の壁

通常,自分以外のコンピューターにデータ(IPパケット)を送ったり,あるいはそこからデータを受け取る場合にはこれら(ethX)の壁を通ります.外へ出て行く場合は"out",逆に入ってくる場合は"in"ということになります.普通通信をやる場合,何かデータを送って(送られて)から応答を受ける(する)のですから,"in"の時と"out"の時の最低2回,同じ壁を通ることになります.この壁を通る時にフィルタリング(不適切なものを破棄)が行われます. 図では矢印が切れていて"in"または"out"の書かれている場所がそうです.
また,図の中央に点線矢印があって分岐・合流している箇所がありますが,点線矢印はそのデータが自分宛だったり自分発である場合に通る矢印です.入ってきたパケットが自分宛でない場合はまっすぐ通り抜けて,反対側のNICへ流れ出ます.この場合も結局"in"の時と"out"の時の2回のフィルタリングを受けます.

もう1つの壁(ループバック用)は何なのか?

最初の2つはすぐ理解できると思いますが,最後の1つは何なのか説明します.これはサーバが自分自身にデータを送る時にぶつかる壁です.サーバにログインしているユーザがそのサーバにアクセスしようとすると,内部のプロセス間でデータがやり取りされるだけなので実際のネットワークにデータは流れ出しませんが,IPFWはこのプロセス間の通信(ただしINETドメイン経由)でも壁を作ります.しかもどこかのプロセスがデータを送る時,そしてまた別のプロセスがデータを受け取る時,というふうに"out"の時と"in"の時で,この場合も結局2回壁を通ることになります.
ちなみに,ここで言うループバック(loop back)とは,INETドメインを経由するプロセス間通信全体を指しています.ループバック用のアドレスと言えばlocalhostや127.0.0.1(127.x.x.x)ですが,自分自身への通信であればそれ以外のアドレスを持つIPパケットもここを通ります.(注*1) 例えば,このサーバ持つ外部ネットワーク用のIPアドレスがo.o.o.oで,自分がそこに対して通信しようとすると…送信元:o.o.o.o,宛て先:o.o.o.oのIPパケットが出来上がりますが,これはethAではなくlo0を通るということです.

NATDと組み合わせた場合

IPFWの構造
図2. IPFW+NATDの場合
IPFWを使いながらさらにNAPTも行わせたい場合に使うのがNATDというデーモンです.図2がNATDと連携された場合の図です.

NATDの位置付け

NATDはNAPTを行うためのデーモンで,ここにパケットを預けると適切なNAPTをして返してくれます.受け渡しをする場所はIPFWの壁でして,通常は外部ネットワークへ繋がっているNICの手前にできている壁で受け渡しをします.というわけで図2のNATDはethAに接続されています.

パケットの流れ方

NATDの接続されたIPFWの壁に到達したパケットはまず,いつも通りにフィルタリングを受けます.フィルタリングとはつまり,フィルタリング対象となるパケットをフィルタリングルールとして記述されている条件に次々と照合(マッチング)していき,条件に一致した時点で何らかの動作(破棄や通過)を行うのですが,ここで行わせる動作として「NATDへ送る」というものを置いておくとNATDへ転送されるようになります.
そしてNATDはパケットを受け取ると変換を行って再びそれをIPFWに戻します….が,IPFWは一回マッチングが終了したからといってそのまま素通ししてしまうわけでもなければ,だからと言って再び壁に通し直すのでもなく,マッチングの続きから行うようにします.

まとめ

文章の説明だけだといまいちわかりにくいので,具体例を挙げて説明することにします.表1をご覧下さい.条件がかなりいい加減な指定なのでこれは飽くまでイメージでしかありませんが,とりあえずこんなルールがあったとします.

表1. IPFW+NATDの設定イメージ
行番号 条件 壁の場所 動作
: : : :
1000 何だか知らないがアヤしいパケット どこでも 破棄
1010 コドモ部屋のPCからのパケットで,
宛て先がコドモは見ちゃいけないサイトへのパケット
外部NIC 破棄
: : : :
2000 全てのパケット 内部NIC 通過
: : : :
3000 内部から外部へアクセスするためのパケット 外部NIC NATDへ転送
3010 NATDで変換されて外部へ行けるようになったパケット 外部NIC 通過
: : : :
4000 外部から入ってきたパケット 外部NIC NATDへ転送
4010 NATDで逆変換されて内部へ戻せるようになったパケット 外部NIC 通過
: : : :
65534 これらのどれにもマッチしなかったパケット
(例えばNATDがもともと変換した覚えなどなくて,逆変換できなかったパケット等)
どこでも 破棄&ログ

1. アヤしいパケットが来た場合

1000行で破棄されます.

2. 内部ネットワーク同士のパケット

これはつまり本サーバと内部のPCの間で通信を行うことを意味しますが,その場合は2000行で通過されるので何の規制も受けません.

3. コドモがオトナのサイトを見ようとした場合

(1)コドモ部屋のPCも当然内部ネットワークにあるので,まずは図2でいうethBに"in"します.すると2000行とマッチしてとりあえずはマッチング終了し,通過します.
(2)その後ethAに到達し,そこから"out"しようとします.ところが,そうするともう一度表1の最初からマッチングが始まりますので今度は1010行で引っ掛かってしまい,それで終わります.

4. 内部PCから普通のサイトへアクセスしようとした場合

往路(行き)

(1)内部PCなので,3と同様にethBをすり抜けた後に,ethAから"out"しようとし,再び表1の最初からマッチングが始まります.今度は3000行でマッチするので,NATDへ送られます.
(2)NATDで外部へ行けるように送信元アドレス&ポート変換(自分が発信したことにする)が行われ,IPFWに戻されます.
(3)先程は3000行でマッチしたので,3000行の次の行からマッチングが再開されます.するとすぐ下の3010行でマッチするのでここでethAを抜け,外部へと旅に出ます.

復路(帰り)

(4)外部からの応答が帰ってくると,まずethAに"in"します.1000行を無事かわせば4000行でマッチして,NATDへ送られます.
(5)NATDは,応答パケットを内部へ戻せるようにアドレス&ポートの逆変換(自分宛の返信を内部PC宛に戻す)をしてIPFWに戻します.
(6)往路と同様に,4000行の次の行からマッチングが再開され,やはりすぐ下の4010行でマッチするのでここでethAを抜け,ethBへと向かいます.
(7)ethBに到達して"out"しようとしたパケットは最後のマッチングを受け,2000行でマッチし,内部PCへと戻っていきます.

5. NATD逆変換に失敗したパケット

NATDを使って内部からの外部へアクセスしたパケットに対する応答が忘れた頃(タイムアウト後)に帰ってきた場合や,応答のフリをして訪れたパケットなどは次のように破棄されます.

(1)タイムアウトの場合は上記例4の(1)〜(4)が,偽装の場合は(4)が行われます.
(2)NATDは最初の変換を忘れてしまったか,あるいは覚えが無いのでそのままIPFWに戻します.
(3)すると4000行の次からマッチングが行われますが,4010行ではマッチしないのでずーっとそのまま最終行(65534)まで来ます.ここでは全てのパケットにマッチするのですが,不正なパケットとしてログに記録されます.

IPFW+NATDの構造はこれで理解できたでしょうか?


(注意 *1)
DHCP等ではそうなのですが,PPPでできたNICなどの場合は必ずしもそうではないようです.ただ,自分自分宛の場合はやはり lo0 を通ってもらわないと都合が悪いことがあるため,route コマンドを使って lo0 を通るようにすることができます.