11.6. DNATターゲット

DNAT ターゲットは宛先ネットワークアドレス変換 (Destination Network Address Translation)、つまり、パケットの宛先 IPアドレスを書き換えるために用いる。パケットがマッチした時、ルールにこのターゲットが指定されていると、当該のパケットとその同じストリームに乗っている後続パケットは変換され、しかるべきデバイスあるいはホスト、ネットワークへルーティングされる。このターゲットは非常に便利だ。例えば、 WEB サーバを走らせているホストが LAN 内にあるが、インターネットで有効な本物の IPアドレスを与えられていない場合だ。その時、ファイヤーウォールに対して、 HTTP ポート宛てに送られてきたパケットを LAN 内の WEB サーバへ転送するよう指示しておけばいいのだ。また、 IPアドレスの範囲を指定することも可能で、そうすると、 DNAT メカニズムはその中から IPアドレスをランダムに選ぶ。これによって、一種の負荷分散 (load balancing) が可能となる。

以下、注意点だが、 DNAT ターゲットは nat テーブルの PREROUTINGOUTPUT チェーンおよび、それらのチェーンから呼ばれたチェーンでのみ有効だ。また、 DNAT ターゲットを含むチェーンは、 POSTROUTING をはじめとする他のチェーンから呼ばれて使われることはない。

Table 11-5. DNATターゲットオプション

オプション--to-destination
iptables -t nat -A PREROUTING -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.1.1-192.168.1.10
説明--to-destination オプションは、 DNAT メカニズムに対して、 IP ヘッダに設定したい宛先 IP と、マッチしたパケットの行き先を指示する。上記の例は、 IPアドレス 15.45.23.67 に宛てられたすべてのパケットを、LAN の IP 192.168.1.1 から 10 の範囲へ送る。既に述べたように、ひとつのストリーム中では常に同じホストが使用されるが、ストリームが異なれば、各ストリームが使うべき宛先 IPアドレスはランダムに選択される。また、単一の IPアドレスを指定することも可能で、そうすれば、接続は常にそのホストへ導かれる。さらに、トラフィックをリダイレクトする宛先ポートまたはポート範囲を追加指定することも可能だ。これは、パケットの DNAT 先 IPアドレスに、例えば :80 の記述を加えることによって指定できる。その場合の記述は --to-destination 192.168.1.1:80 のようになるし、ポートを範囲で指定した場合なら --to-destination 192.168.1.1:80-100 のようになる。お気づきの方もおられようが、行う事柄は決定的に異なっていながら、 SNAT ターゲットは DNAT ターゲットとほとんど同じ書式を採る。ポート指定は、 --protocol オプションで TCPUDP を指定したルールでしか有効でないという点に注意しよう。

DNAT を適正に動作させるためには、かなりいろいろな準備をしなければならない。そこで僕は、 DNAT への取り組み方について、詳しい説明を加えることにした。通常行うことになる事柄を、簡略な例で見てみることにしよう。我々は今、 WEB サイトを、インターネット接続を利用して公開しようとしているとする。持っている IPアドレスはひとつだけで、 HTTP サーバはローカルネットワーク内にある。ファイヤーウォールの外部 IPアドレスを $INET_IPHTTP サーバの持つ内部向け IPアドレスを $HTTP_IP、ファイヤーウォールの内部向け IPアドレスを $LAN_IP とする。最初にやらなければならないのは、 nat テーブルの PREROUTING チェーンに下記のシンプルなルールを追加することだ。

iptables -t nat -A PREROUTING --dst $INET_IP -p tcp --dport 80 -j DNAT \
--to-destination $HTTP_IP
    

これで、ファイヤーウォールの 80番ポートに宛てて送られてきたパケットは全部、内部の HTTP サーバへリダイレクト (つまり DNAT) されるようになった。インターネット側からテストしたら、完璧に動作するはずだ。では、 HTTP サーバと同じローカルネットワーク内にいるホストが接続してこようとしたらどうなるか。ずばり、動作しない。ここがまさに、ルーティングの難しいところだ。では、ことの成り行きを、まず、通常の場合から見てみよう。混乱を避けるために、外界のマシンが持っている IPアドレスを $EXT_BOX としておこう。

  1. パケットが外部ホストを離れ $INET_IP へ向かう。送信元は $EXT_BOX

  2. パケットがファイヤーウォールに到着。

  3. ファイヤーウォールがパケットを DNAT し、その他のチェーンへ流す、など。

  4. パケットはファイヤーウォールを離れ $HTTP_IP へ向かう。

  5. パケットが HTTP サーバに到着。 $EXT_BOX へのデフォルトゲートウェイとしてファイヤーウォールの内部用 IP がルーティングデータベースに記録されていれば、 HTTP サーバは、ファイヤーウォールを介して返事をよこす。普通なら、ファイヤーウォールの内部用 IP は HTTP サーバのデフォルトゲートウェイになっているだろう。

  6. ファイヤーウォールはパケットを 逆DNAT する。これによって、このパケットはあたかもファイヤーウォール自体が発した返答のように見える。

  7. 返答パケットは平常通りにクライアント $EXT_BOX へ戻る。

次に、パケットが、 HTTP サーバと同じネットワーク内にいるホストが発行したものだった場合の成り行きを考えてみよう。クライアントの IPアドレスは $LAN_BOX、その他のマシンの条件は上記と同じとする。

  1. パケットが $LAN_BOX を離れ $INET_IP へ向かう。

  2. パケットがファイヤーウォールに到着。

  3. パケットは DNAT され、その他様々な処理を施される。ただし、SNAT はされず、故にパケットの送信元アドレスは変わらない。

  4. パケットはファイヤーウォールを離れ HTTP サーバに到着する。

  5. HTTP サーバはパケットに返答しようと、ルーティングデータベースを照合する。それによると、このパケットは同じネットワークのローカルマシンから来たことが判明する。よって、 HTTP サーバはパケットをそのままずばりの IPアドレス (この時は宛先 IPアドレスとして) に直接送ろうとする。

  6. パケットがクライアントに届く。だがここで、返答のパケットが元々自分の指定したリクエスト先とは違うところから来ているので、クライアントは困惑する。結果、クライアントはこのパケットを破棄し、"本物" の返事を待ち続ける。

この問題の手っ取り早い解決策は、ファイヤーウォールへ入ってきたパケットの行き先が、これから DNAT で振り向けようと目論んでいるホストや IP になっていた場合には、それらのパケットにもれなく SNAT を掛けることだ。上記のルールを考察してみよう。ファイヤーウォールに入ってくる、宛先が $HTTP_IP80番ポートであるパケットには、それらが $LAN_IP から来たように見せるために SNAT を掛ける。すると、 HTTP サーバに対して、パケットをファイヤーウォールへと返信するよう強制でき、ファイヤーウォールはそのパケットを 逆DNAT するので、パケットは最初のクライアントへ送られることとなる。ルールはこういったものになるだろう:

iptables -t nat -A POSTROUTING -p tcp --dst $HTTP_IP --dport 80 -j SNAT \
--to-source $LAN_IP
    

POSTROUTING チェーンが処理されるのは、すべてのチェーンのうちで最後だということを頭に入れておこう。つまり、当該のチェーンに入ってくる時には、パケットは既に DNAT されている。内部向けアドレスでパケットをマッチさせているのは、そのためだ。

Warning

最後に挙げたルールはロギングに多大な悪影響を与えるため、この方法を用いるのは決してお勧めできない。とはいえ、上の例全体は働かないわけではない。影響とは以下のようなものだ: パケットがインターネット側からやってくると、 SNAT および DNAT を受け、 HTTP サーバ (例えば) に到達する。今や、 HTTP サーバはこのパケットはファイヤーウォールから来たと思い込んでいるので、インターネットから入ってきたリクエストを、どれもこれも、すべてファイヤーウォールから来たものとしてログしてしまうのだ。

これには更にもうひとつ、重大な副作用がある。LAN 内に SMTP サーバがあり、ローカルネットワーク内からのリクエストは受け入れるように設定してあるとする。そして、 SMTP トラフィックを SMTP サーバへ転送するよう、ファイヤーウォールを設定する。すると、見事なオープンリレーが出来上がってしまうのだ。しかも、ログは身の毛もよだつような代物となる。

ひとつの解決策は、早い話が上記の SNAT ルールをもっと限定的にして、内側の LAN インターフェイスから入ってくるパケットにだけ作用するようにすることだ。つまり、どのコマンドにも --src $LAN_IP_RANGE を加えるわけだ。こうすれば、ルールは LAN からやってくるストリームだけに作用するようになり、よって、送信元IPアドレスに影響を与えることは無くなる。その結果、ログは正しく書かれる、ただし LAN 内からのストリームは別だ。

言い換えれば、LAN のための専用 DNS サーバを置くか、本格的に DMZ を設けて切り離すほうが、解決策としては遙かに上だ。お金に余裕があるなら、後者のほうがいい。

取り敢えずこれで充分だと思っているだろう。確かにそうだ。ただし、このシナリオを仕上げるには、まだひとつ視点が抜けている。もし、ファイヤーウォール自体が HTTP サーバにアクセスしようとしたら? 今の状態では残念ながら、ファイヤーウォール自身の HTTP サーバに接続しようとするばかりで、 $HTTP_IP である本当のサーバへ向かおうとはしない。この問題を切り抜けるには、さらに、 OUTPUT チェーンに DNAT ルールを加える必要がある。上記の例に従えば、ルールはこのようなものになる:

iptables -t nat -A OUTPUT --dst $INET_IP -p tcp --dport 80 -j DNAT \
--to-destination $HTTP_IP
    

このルールの追加で、すべての体制は整った。 HTTP サーバの含まれるネットワーク以外のネットは何の滞りもなく動作する。 HTTP サーバと同じネットワークにいるホストも接続できる。そして、ファイヤーウォールも正常な接続が行える。すべてはうまくいき、問題はなにもない。

Note

上に挙げたルールには、パケットを適切に DNATSNAT するという作用だけしかないという点を認識しておいていただきたい。実際には、パケットにこれらのチェーンを巡回させるために、上記のルール以外にも、 filter テーブル (FORWARD チェーン) に幾つかのルールが必要となるだろう。その際には、パケットは既に PREROUTING を通過済みであり、 DNAT によって宛先アドレスが書き換えられているということを忘れないようにしよう。

Note

Linux カーネル 2.3, 2.4, 2.5, 2.6 で機能する。