問題のあったネットワーク図は以下。wan 側にある ftps クライアントから nat の内側にある ftps サーバに対してログインできてもディレクトリ一覧を取得するとタイムアウトする。
+-------------+ | ftps client | --- wan ---+ +-------------+ | +-----------------+ | iptables router | +-----------------+ +-------------+ | | ftps server | --- nat ---+ +-------------+
重要なことは、ssl/tsl を使っている以上、暗号化されたパケットの中身を iptables が見ることは出来ない (たとえどんなステートフルパケットインスペクション SPI モジュールを使っても。) ということ。暗号化を伴わない ftp の場合は、制御コネクションを流れるパケットの中に IP アドレスとポート番号が含まれるが、SPI モジュールを使うことでこれを解決できる。ただし、ftp を ssl でラップした ftps は IP アドレスとポート番号を含むパケットの中身を書き換えられないことになる。問題が起こるのは、クライアントからの pasv コマンドの戻りにサーバの IP アドレスとポート番号が含まれるということだ。
クライアント: グローバル IP、サーバ: グローバル IP の場合
まずは一番シンプルなものから。クライアントマシンとサーバマシンにグローバル IP が割り振られて、双方のマシンが NAT の後にいない場合。ネットワーク図とそれぞれのマシンの IP アドレスは以下。
+-------------+ | ftps client | ------------------------ wan ---+ +-------------+ | +-------------+ | | ftps server | ------------------------ wan ---+ +-------------+
CCC.ggg.ggg.ggg | クライアントマシンの IP (グローバル) |
SSS.ppp.ppp.ppp | サーバマシンの IP (プライベート) |
SSS.ggg.ggg.ggg | サーバマシン側のルータ IP (グローバル) |
このときのメッセージのやり取りは以下。passive モードではサーバがポートを開けて待つ。クライアントが接続試行する IP アドレスとポート (SSS.ggg.ggg.ggg:9001) はサーバマシンのグローバルアドレスになるのでデータのやり取りが出来る。パケットのヘッダとデータ部分を書いたものが以下。9000 = 35 * 256 + 40。
CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PASV SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 Entering Passive Mode (SSS,ggg,ggg,ggg,35,41)
また、active モードではクライアントがポートを開けて待つ。サーバが接続試行する IP アドレスとポート (CCC.ggg.ggg.ggg:9001) はクライアントマシンのグローバルアドレスになるのでデータのやり取りが出来る。
CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PORT CCC,ggg,ggg,ggg,35,41 SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 PORT command successful.
クライアント: グローバル IP、サーバ: プライベート IP の場合
つぎに、今回問題のあったネットワークの場合。クライアントマシンにグローバル IP が割り当てられ、サーバマシンが NAT の後にあり、プライベート IP が割り振られている場合。ネットワーク図とそれぞれのマシンの IP アドレスは以下。
+-------------+ | ftps client | ------------------------ wan ---+ +-------------+ | +-----------------+ | | iptables router | --- wan ---+ +-----------------+ +-------------+ | | ftps server | --- nat ---+ +-------------+
CCC.ggg.ggg.ggg | クライアントマシンの IP (グローバル) |
SSS.ppp.ppp.ppp | サーバマシンの IP (プライベート) |
SSS.ggg.ggg.ggg | サーバマシン側のルータ IP (グローバル) |
IP パケットの source と distnation IP を書き換えるのが NAT。基本的にデータグラムは書きえられない。ステートフルパケットインスペクション (SPI) モジュールを組み込むことで可能になる場合もある (nf_conntrack_ftp,ip_conntrack_ftp) が、データグラムが暗号化されている場合それも無理。passive モードではサーバがポートを開けて待つ。クライアントが接続試行する IP アドレスとポート (SSS.ppp.ppp.ppp:9001) はサーバマシンのプライベートアドレスになるのでダメ。
CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PASV CCC.ggg.ggg.ggg:9000 -> SSS.ppp.ppp.ppp:990 PASV SSS.ppp.ppp.ppp:990 -> CCC.ggg.ggg.ggg:9000 Entering Passive Mode (SSS,ppp,ppp,ppp,35,41) SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 Entering Passive Mode (SSS,ppp,ppp,ppp,35,41)
active モードではクライアントがポートを開けて待つ。サーバが接続試行する IP アドレスとポート (CCC.ggg.ggg.ggg:9001) はクライアントマシンのグローバル IP なので問題なし。
CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PORT CCC,ggg,ggg,ggg,35,41 CCC.ggg.ggg.ggg:9000 -> SSS.ppp.ppp.ppp:990 PORT CCC,ggg,ggg,ggg,35,41 SSS.ppp.ppp.ppp:990 -> CCC.ggg.ggg.ggg:9000 PORT command successful. SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 PORT command successful.
クライアント: プライベート IP、サーバ: グローバル IP の場合
先ほどと少し違うが、クライアントマシンが NAT の後にあり、プライベート IP が割り振られ、サーバマシンにグローバル IP が割り当てられている場合。ネットワーク図とそれぞれのマシンの IP アドレスは以下。
+-------------+ | ftps client | --- nat ---+ +-------------+ | +-----------------+ | iptables router | --- wan ---+ +-----------------+ | +-------------+ | | ftps server | ------------------------ wan ---+ +-------------+
CCC.ppp.ppp.ppp | クライアントマシンの IP (プライベート) |
CCC.ggg.ggg.ggg | クライアントマシン側のルータ IP (グローバル) |
SSS.ggg.ggg.ggg | サーバマシンの IP (グローバル) |
passive モードではサーバがポートを開けて待つ。クライアントが接続試行する IP アドレスとポート (SSS.ggg.ggg.ggg:9001) はサーバマシンのグローバルアドレスになるので問題なし。
CCC.ppp.ppp.ppp:9000 -> SSS.ggg.ggg.ggg:990 PASV CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PASV SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 Entering Passive Mode (SSS,ggg,ggg,ggg,35,41) SSS.ggg.ggg.ggg:990 -> CCC.ppp.ppp.ppp:9000 Entering Passive Mode (SSS,ggg,ggg,ggg,35,41)
active モードではクライアントがポートを開けて待つ。サーバが接続試行する IP アドレスとポート (CCC.ppp.ppp.ppp:9001) はプライベートアドレスになるのでダメ。
CCC.ppp.ppp.ppp:9000 -> SSS.ggg.ggg.ggg:990 PORT CCC,ppp,ppp,ppp,35,41 CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PORT CCC,ppp,ppp,ppp,35,41 SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 PORT command successful. SSS.ggg.ggg.ggg:990 -> CCC.ppp.ppp.ppp:9000 PORT command successful.
クライアント: プライベート IP、サーバ: プライベート IP の場合
先ほどと少し違うが、クライアント、サーバマシンの双方が NAT の後にあり、プライベート IP が割り振られている場合。ネットワーク図とそれぞれのマシンの IP アドレスは以下。
+-------------+ | ftps client | --- nat ---+ +-------------+ | +-----------------+ | iptables router | --- wan ---+ +-----------------+ | +-----------------+ | | iptables router | --- wan ---+ +-----------------+ +-------------+ | | ftps server | --- nat ---+ +-------------+
CCC.ppp.ppp.ppp | クライアントマシンの IP (プライベート) |
CCC.ggg.ggg.ggg | クライアントマシン側のルータ IP (グローバル) |
SSS.ppp.ppp.ppp | サーバマシンの IP (プライベート) |
SSS.ggg.ggg.ggg | サーバマシン側のルータ IP (グローバル) |
passive モードではサーバがポートを開けて待つ。クライアントが接続試行する IP アドレスとポート (SSS.ppp.ppp.ppp:9001) はプライベートアドレスになるのでダメ。
CCC.ppp.ppp.ppp:9000 -> SSS.ggg.ggg.ggg:990 PASV CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PASV CCC.ggg.ggg.ggg:9000 -> SSS.ppp.ppp.ppp:990 PASV SSS.ppp.ppp.ppp:990 -> CCC.ggg.ggg.ggg:9000 Entering Passive Mode (SSS,ppp,ppp,ppp,35,41) SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 Entering Passive Mode (SSS,ppp,ppp,ppp,35,41) SSS.ggg.ggg.ggg:990 -> CCC.ppp.ppp.ppp:9000 Entering Passive Mode (SSS,ppp,ppp,ppp,35,41)
active モードではクライアントがポートを開けて待つ。サーバが接続試行する IP アドレスとポート (CCC.ppp.ppp.ppp:9001) はプライベートアドレスになるのでダメ。
CCC.ppp.ppp.ppp:9000 -> SSS.ggg.ggg.ggg:990 PORT CCC,ppp,ppp,ppp,35,41 CCC.ggg.ggg.ggg:9000 -> SSS.ggg.ggg.ggg:990 PORT CCC,ppp,ppp,ppp,35,41 CCC.ggg.ggg.ggg:9000 -> SSS.ppp.ppp.ppp:990 PORT CCC,ppp,ppp,ppp,35,41 SSS.ppp.ppp.ppp:990 -> CCC.ggg.ggg.ggg:9000 PORT command successful. SSS.ggg.ggg.ggg:990 -> CCC.ggg.ggg.ggg:9000 PORT command successful. SSS.ggg.ggg.ggg:990 -> CCC.ppp.ppp.ppp:9000 PORT command successful.
まとめ
つまり、ftps クライアントか ftps サーバのどちらかにグローバル IP が割り当てられている場合は、グローバル IP が割り当てられている側が接続待ちになるべき。クライアント側がグローバル IP もっていれば active モード、サーバ側がグローバル IP もっていれば passive モード。纏めると以下。
クライアント側 IP | クライアント側 NAT | サーバ側 NAT | サーバ側 | クライアント転送モード |
グローバル | 無し | 無し | グローバル | active, passive |
グローバル | 無し | 有り | プライベート | active |
プライベート | 有り | 無し | グローバル | passive |
プライベート | 有り | 有り | プライベート | ?? |
結局双方が NAT の内側にいる場合には PORT の引数 or PASV コマンドの戻りを書き換えるトリックが必要になるわけで、IP パケットのデータグラムを読めない限り接続は無理ということか?