iij-pppをNAT対応にする技術情報と詳細
目次
ここでは,iij-pppでNATを実現するまでの道のりと,そこでの技術情報を示します.
NATは,The IP Network Address Translator の略で,字の通りアドレス変換を
するためのものです.詳細はrfc1631に述べられています.
また,以下に示す説明を理解する上で,TCP/IP関係の本とかを参照するとよりわかりやすいでしょう.
最近は,単にdefaultrouteをルータに向けて設定
すれば,ルータがアドレスの付け替えをやってくれるという機能を持ったISDNルータが出始めました.
安くなってきたとはいえ,まだまだ気軽に手が出せる値段ではないですので,
是非ともこの機能を iij-ppp で実現したいと思ったわけです.
私が,ISDN57.6K非同期でプロバイダに接続していた(現在は,普段使っている大宮APが同期64K対応になったので,64Kで接続していますが...)ということも理由の1つです(ISDNルータは同期接続).
しかし,今までsocksなどを使ってLAN上のマシンからインターネットに出ていく
ということをしていましたので,あまりNATに関しては考えていませんでした.
そんなとき,NOROPさんにより,iij-pppをautoモードで使ったときに,
ダミーパケットを投げなくてすむようにするパッチが公開されました.
で,ふと思い出しました...アドレス付け替えといえば,NAT...
「そうだ,NAT作ろう!」(某鉄道会社のキャッチぢゃないっす)
それに,NOROPさんのアドレス付け替えルーチンは流用していますので,
その部分の苦労はせずにすんでしまいました.感謝感謝.
が,しかし,それ以外は簡単と思っていたのがあまかったようで...
なかなか苦労させられました.
以下に内部処理はどうなっているかという話と共に,説明しましょう.
最初の問題として,まず外部から受けとったパケットは,自宅LAN上のどのマシンのアドレスに変換すればいいのでしょう??
なにげに,結構困りました.
結論をいいますと,ソケットのリンクは,必ずLAN上のマシンからだろうという前提のもと,本パッチでは以下のようにしました.
- TCP
- LAN上のマシンからSYNパケット送出時に,宛先のIPアドレスと,発信元のポート番号の組を記録する.
- 外からのパケットを受信した場合は,その発信元IPアドレスと宛先のポート番号を上記で記録したものと比較して,LAN上の送るべきマシンのIPアドレスに変換する.
- FINパケットが流れたら,記録を削除する.
- UDP
- LAN上のマシンから何らかのパケットが送出されたら,宛先のIPアドレスと,発信元のポート番号の組を記録する.
この場合ですと,たまたまLAN上の別のマシンの発信元ポート番号と,宛先IPアドレスが同一になってしまうと判断できなくなってしまいます.
そこで本パッチでは,
- TCPでは,先にリンクした方が優先される
- UDPでは,常に新しい宛先情報に更新される
としています.
ただ,LAN上に2台マシンがある場合は,単純計算で,ポート番号の数に関連して,
65536分の1の確率で,重複することになります.
まあ,個人で使う分には,そう簡単には重複しませんので,
問題になることはないでしょう.
#なお,この問題は現在のNATパッチでは解決してあり(「もっと賢く,ポート番号を自動マッピング !!」参照),重複することはありません.
実際に私自身,telnetやhttpなど非常に快適に使っています.
宛先が正しく判断できて,アドレスを付け替えればそれでもうOKというわけではありません.
困ったちゃんの代表格がftpです.NATを実現する上で,ftpでは以下の点を解決しなくてはなりません.
- TCPパケットのデータフィールドに,ソースIPアドレスとポート番号がASCIIで入るので,データパケットの文字列書換えが必要
- クライアントからサーバだけでなく,データ転送時には,サーバからクライアントに対してコネクションを張ってくるので,外部からのコネクションをうまく変換してあげる必要がある.
- データパケット内のIPアドレスなどを書き換えてデータパケット長が変わった場合は,シーケンス番号の付け替えが必要
本パッチの以前のバージョンでは,上の2つのみ対応していました.
まず,ソースIPアドレスとポート番号は,ftpのPORTコマンドと共に送られてくるので,ftp-ctrlリンク時には,PORTコマンドを監視します.
そして,PORTコマンドのデータパケットがみつかったら,その中のアドレスなどのデータを書き換えます.
さらに,ここで書かれているポート番号には,サーバからのコネクションが張られますので,あらかじめ,「さて宛先はどうやって決める?」で述べたようにアドレスとポート番号の組を記録しておきます.
これで,アドレスを付けかえた時のアドレスの長さが同じ場合に限ってftpが通るようになりました.
その後,新たに完成したパッチによって,ftpにも完全対応になりました.
どのようなことを行なっているかということをちょっと説明します.
上にあるようにPORTコマンドを監視しているわけですので,
その場合のデータパケットの長さの変化までも記録するようにします.
例えば,1バイトパケットが長くなったとすれば,ftpサーバから返ってくるACK番号が1大きくなるわけです.
そこで,実際にftpを実行したクライアントへは,ACK番号を1引いて渡してあげます.
すると,正常にパケットが戻ってくるようになります.
これでOKかと思ったら,まだ早いです.最初のパケットのACK番号のつじつまは合わせることが出来ましたが,次にクライアントから新たなコマンドが送られた場合,今度はSEQ番号のつじつまが合わなくなってしまいます.
そこで,PORTコマンドで1バイト長さが増えたので,SEQ番号に1を加えて送ります.
考え方としては,PORTコマンドが実行されるごとに,SEQ,ACKに足したり引いたりする値を変化させるというものです.
このSEQ,ACK番号の付けかえは,ftp-ctrlリンクが終了するまで常に行なうことになります.
これで,みごとにftpが通るようになりました.
普段良く使うプロトコルで通らないものはまずなくなったと思います.
ftpを使っても幸せになれます.Hi
ftpが通るようになって1週間.
もうsocksはいらないやーと思いつつ,快適にNATを使っていたのです.
ところが,ftpが通らないという状況にぶちあたりました.
何だろう?今まで何の問題もなかったのにぃ...
パケットを観察していると,普段は気づかないような盲点が見つかりました.
IPでは,パケットが目的のホストに届くということは保証されていませんので,その上のTCPではパケット到達を保証するために届かなかった場合は再送をします.
そう,この再送が原因でした.
たまたまftpでPORTコマンドが実行されたときに,そのパケットが届かず再送されると,SEQ,ACK番号の付けかえを間違えるというものです.
NATの開発時には,ローカルでシリアルでつないだネットワークを作ってテストしてたので,見つからないはずです(これだと,再送は発生しない).
しかたないので,iij-pppで無理に再送が発生するようにテストプログラムを作ってこの問題をシミュレートして実験してみました(実際にダイヤルアップしてたら,電話代がたまらない).
何度もやり直して,無事再送時の問題が解決できました.
パケット再送問題時にもう一つ問題をみつけてしまいました.ftpコネクション時に回線を切ってしまうと,次回の接続時にiij-pppのプロセスが落ちるという現象です.
まあ,ftpコネクションしたままで回線切る人はあまりいないでしょうから,
そうは問題ないのかも知れませんが,こちらも直しておきました.
原因は,ftp使用時のNATのエラーチェックがちょっと甘かったようで...
これで,さらに完璧な(何をもって完璧っていうんだろう?)NATに近付いたでしょう.
快適なLAN環境まちがいなしです.
ほとんどのプロトコルが問題なく通るようになったのですが,まだ問題は残ってました.
タイトルからもわかると思いますが,RealAudioです.
もちろん,RealAudio対応する前のNATでも,RealAudioの設定で,データ転送にTCPを使うようにすれば使えていたのですが,やっぱり,RealAudioのデータは,UDPで受信したいですよねえ.
そう,TCPにしてしまうと,ブラウザで表示しているページ切替えと音が合わないっていう問題があるからです.
私の作ったNATではUDPパケットも対応しているのですが,RealAudioのUDPパケットは宛先アドレスの変換ができませんでした.
なぜかといいますと,最初にパケットをこちらから投げないと,IPアドレス変換テーブルが作成されないからです.
そう,RealAudioはサーバからクライアントへしかUDPパケットを投げないのです.
いきなりUDPパケットをなげられても,自宅LAN上のどのマシンに送り込めばいいのか困ってしまうわけです.
そこで,あらたに作ったNATでは,RealAudioのTCPリンクに注目しました.
RealAudioでは,データ転送はUDPでも,制御にはTCPリンクが使われます.
で,RealAudioのTCPリンクを監視し,それをみつけたら,外部から入ってくるUDPパケットに備えるようにしました.
これでみごとRealAudioがUDPで問題なく使えるようになりました.
もちろん,特別なポート指定なんか必要ないです.
もっと簡単にいうと,RealAudioをインストールした直後のデフォルトの設定で
使えてしまうのです.なーんにも設定がいらないってのは楽です.
あー,やっぱり,NAT使うとクライアント側で特別な設定が必要なくなるので,幸せになれます.
TCP,UDPを使うアプリケーションに関しては,普段使ってて何不自由なくなりました.
ここまでやったのなら,もう,完璧を目指すしかありません!?.
残るはICMPです.
とうわけで,ICMP対応に取り組みました.
最初の問題は,port番号が存在しないICMPパケットをどうやってLAN上のマシンに振り分けるかです.
pingに関しては,ECHOパケットを監視して,始点アドレスと終点アドレスの組を記録し,ECHO_REPLYが返ってきたら,記録しておいたアドレスに変換することにしました.
これですと,同じホストに向かって,LAN上の複数のマシンから同時にpingすると問題が生じますが,まあよしとしました.
どうせpingですし,同じマシンに同時にpingかけるなんてこと滅多にないでしょうから,問題ないでしょう.
もちろん,他のマシンでpingを実行していなければ,いつでもpingを実行することができます.
port番号が存在しないICMPパケットですが,幸いなことにICMPの中でもUNREACHパケットとTIME_EXCEEDEDパケットの中には,そのエラーが発生した元となったパケットのヘッダが含まれています.
つまり,そのヘッダを見れば,UNREACHの発生源のパケットがわかるわけです.
当然この中にはポート番号が含まれていますので,これを利用して変換テーブルを参照し,アドレス変換を行ないます.
これで,UNREACHとTIME_EXCEEDEDパケットは原因となったマシンに戻ってくるようにできるのです.
で,これらのパケットをうまく使っているのが,tracerouteです.
というわけで,tracerouteが使えるようになりました.
12月よりも前に公開したNATパッチでは,ローカルネットワーク上の複数のマシンで
ソースポート番号が同じパケットが発信され,
なおかつそのパケットの宛先のIPアドレスが同じ場合は,
どちらかの通信ができなくなるという問題がありました.
(といっても,まずこんなことには遭遇しないのですが...)
そこで,これを解決するために,宛先IPアドレスと送信元ポート番号が同じに
なってしまった場合には,ポート番号を書き換えるようにしました.
しかし,ただ単にきまったポート番号にマッピングしていたのでは,
おもしろくありません.それでは IP filter だとかと同じです.
これでは,設定が繁雑になりますし,LAN上のマシン数の制限ができてしまいます.
さらに,well knownポートでないと駄目なプロトコルもあります.
そこで,私のNATでは,自動的にあいているポート番号をオリジナルの番号に近いところから探していき,割り当てるという方法をとっています.
これで,well knownポートでないと駄目な rlogin でも問題ありませんし,
設定は今までのままで,使う方法をかえる必要がありません.
これで,かなり賢いNATになりました.
普通ダイアルアップ接続で,自宅のマシンに向けてコネクションを張る必要があるプロトコルは少ないですが,ふと,思いつきました...そう,Xはどうするんだぁ??
めったに外部のXアプリケーションを自宅のXサーバに接続させるってことはないとは思いますが(そういうことしている人,実は結構いる?)必要になったら...困る!!
そう,私のうちでは,NATを動かしているマシンは,ファイルサーバ&ルータ専用マシンと化しているのです.コンソールはVT100です.
こりゃ困った.Xクライアントを接続したくなったらどうるんだ??
というわけで,自宅LAN上のマシンのXサーバにクライアントが接続できるように
する機能もつくってしまいました.うん,これでばっちりだ.
ふと,昔某BBSで知っていた人から,NATを使ってるというメールが飛んできました.
どうもうまく動いていないようです.
原因はシリアルポートにありました.
シリアルでオーバーランエラーが出るなどすると,NATもこけるのです.
シリアルからのデータが不完全だった場合、普通TCP/IPでは、
壊れたパケットとみなして、再送されます。
しかし、NATではこれが発生しないのです。
なぜかといいますと、NATではアドレス変換の過程でチェックサムを計算しなおしているためです。
そう、データが壊れていようがチェックサムを合わせてしまうために、
壊れたデータも正しいと思い込んで受けとってしまっていたわけです。
これは、大きな盲点でした。そこで、一旦チェックサムをチェックする処理を加えることによって対処しました。
これで,シリアルでエラーが出る人も"とりあえず"幸せになれます.
もちろん,本質的な解決は,
シリアルでエラーが出ないようにすることなんですが...
はやりなのか何だか知りませんが,
最近は,ネットワークを通して,音や映像を扱うアプリケーションが増えてきました.
ふと VDO Live Videoが見たくなり、Windows95に player をインストールして、
さーて見るか...とやってみたら,通らないぃぃぃ...
状況は RealAudio の場合と同じでした。
そう、サーバ側からガシガシUDPパケットを投げてくるのです。
うーーーーー。
通らないとわかると、よけいに通したくなります。
よし、NATをVDOにも対応させよう!
ってことで、対応させました。
方法は、RealAudioの場合と同じで、VDOのコントロールコネクションは、
TCPでクライアント側から張られますので、それを監視することにしました。
これで、めでたく VDO が見れるようになりました。
私は、ネットワークゲームには疎いんですが、
Diabloというゲームに対応させたという連絡をいただきました。
しかも、パッチ付きです。
これも、RealAudioと同じで、UDPパケットをサーバ側からがしがし投げて来るようで、NATが対応しないと、通らないようです。
いやー、本当に感謝感謝です。
ありがとうございました。>宮下@NECさん。
てなわけで、Diabloに対応しました。
好きな方はどんどんゲームにはまってください。
FreeBSD 2.2.1-RELEASE に付属のiij-pppでは、2.1.xからかなり変更が加えられています。
で、NATパッチが必要なのかちょっと考えました。
なぜって?
2.2.1Rのiij-pppには,PacketAliasingという別のNAT機能が含まれているからです。
でも,PacketAliasingは,RealAudioなど特殊なプロトコルには全く対応していません。
rloginも使えません。Xクライアント張り付けられないしぃ...
これでは,私のニーズを満たしてくれません。
PacketAliasingに私のNAT patchの機能を加えようかとも思いましたが、
port番号のマッピングが PacketAliasingでは、固定なのに対して、
私のNAT patchでは、動的割り当てなので、そう簡単にはいきません。
それに、NOROPさんのOnDemand patch が含まれないのはとても悲しいです。
てなわけで、やっぱり自前で用意するかぁーーってことに...
結局2つのNAT機能がiij-pppに実装されるという、変な状況になってしまいますが、
ま、よしとしましょう。
で、NAT patch の2.2.1R対応ができました。しかも OnDemand patch
の統合まで実現できました。NOROPさんに感謝します。
これで、2.2.1Rでも満足のいくダイアルアップ環境が作れます.
PacketAliasing以上の環境になることまちがいなしです。
以前から、NATで対応してほしいという要望をもらっていたものに、CU-SeeMeがありました。
CU-SeeMe も NAT 泣かせのプロトコルなんです。パケットの中に IP アドレスが書かれています。
そう、これも書き換えてあげなくてはいけません。
でも幸なことに、ftp とは違い、ASCII 文字列で入っているわけではありません。
というわけで、パケット長は変わらずにすみます。
さて、CU-SeeMe対応はやってみたいとは思っていたのですが、私が普段 CU-SeeMe を使わないので、なかなかできないでいました。
CU-SeeMe のプロトコルを通す方法は分かって、どうにコーディングしていけばいいかわかっていたのですが、テストができません。
そんなとき、とてもありがたいことに、テストして頂けるという方があらわれました。
おかげで CU-SeeMe の対応が実現でたのです。
テストありがとうございました。>河井 聡 さん
では最後に、まとめとして....
といきたいところですが、終りません。
まだまだNATで対応させたいプロトコルは、きっとたくさんあるでしょう。
どんどん増やしていくつもりでいます。
そう,この文章もきっと終らない...
通したいプロトコルについての御要望お待ちしております。(^o^)
[トップページに戻る]
[注意書き]
佐藤 淳一
junichi@configure.sh