はじめに
この連載では仮想的なLANを実装することにより、目に見えないTCP/IPプロトコル群を、手を動かして目で実際に確認しながら習得することを目的としています。ただし、TCP/IPのすべてを解説するのは分量上不可能ですし、余計に読者を混乱させてしまいますので、筆者が重要と考えている部分だけ解説します。またサンプルコードや本記事に出てくるIPアドレスとMACアドレスについては架空のものであり、実際にそのIPアドレスを取得した組織などを意識したものではありません。あらかじめご了承ください。
全4階層のTCP/IPの解説を終えたので、そのまとめとして、仮想的なネットワークの実装をします。前編に当たる今回は、まずは複数の層にまたがる技術に関する事柄を解説します。その後、仮想ネットワーク、およびオリジナルのアプリケーション層のプロトコルの仕様を定義します。
本記事の対象を超えるもの、記述箇所を特定しないもの、読者固有環境に起因するもの、読者の主義思想に関するもの、読者の誤解によるもの、社会人としてのマナーを欠いたものなどは質問/指摘を頂いてもお答えできません。あらかじめご了承ください。
グローバルアドレスとNAT
インターネット上で有効なIPアドレスをグローバルアドレスと呼びます。前回解説したルートサーバに割り当てられているIPアドレスがこれに該当します。IPアドレスを、インターネット上で一意に定めることにより、その機器へ接続できます。
しかし、世界中にあるすべての機器に一意なIPアドレスを割り振ると、IPアドレスの数が足りなくなります。そこで、いろいろな対策法が考えだされました。そのうちの1つがNAT(Network Address Translator:ネットワーク・アドレス変換機能)です。
世界中にはたくさんの機器がありますが、すべての機器がグローバルIPを必要としているわけではありません。そこで、ローカルネットワーク内でのみ使用するIPアドレスを設定し、外部と通信する時だけグローバルIPアドレスを割り当てる方法が考えだされました。それがNATです。なお、ローカルネットワーク内でのみ使用するIPアドレスをプライベートアドレスと呼びます。
NATは、プライベートIPアドレスとグローバルIPアドレスの対応を記憶し、外部にデータを送る際に、ソースアドレスを対するグローバルIPアドレスに変換します。こうすることにより、実在する機器の数よりも少ないアドレスで済みます。
プライベートIPアドレスとグローバルIPアドレスは、何でも良いというわけではなく、プライベートIPアドレスはRFC1918で使用できる範囲が定められており、グローバルIPアドレスはCIDR(Classless Inter-Domain Routing)という方式に従います。
CIDR(サイダーと読む)とは、IPアドレスをクラス分けせずに、可変長のマスク値を設定することにより、ネットワークアドレスとホストアドレスを決める形式のIPアドレスです。ネットワークアドレスに割り振るビット数を指定し、残りのビット数をホストアドレスに使用します。自由にネットワークアドレスに使用するビット数を指定できるところ以外は、サブネット形式のIPアドレスとほぼ同じです。これから、NATを使用した通信を、サンプルプロジェクト「NatTest」を用いて解説します。記事に添付されているサンプルコードをダウンロードして、実行してみてください(C#版とVB.NET版を用意しています)。
このサンプルプロジェクトでは、同じローカルネットワーク内に属するサーバと、他のローカルネットワークに属するホストへ向けてデータを送信しています。サンプルプロジェクトを実行し、最後の送信元で端末AのIPアドレスが「200.200.200.200/28」に変化している点に注目してください。このアドレスがサンプルプログラムで設定しているグローバルIPアドレスです。
NAT機能を持つルータを通してデータを送信すると、宛先は送信元のプライベートIPアドレスではなく、グローバルIPアドレスから送られてきたと判別することになります。これは、クラッカーの身元を隠すことも意味するので、あまり好ましくないと考える専門家もいます。
また、FTPにおけるテキスト形式のアドレス変換において、処理が複雑になるという副作用もあります。NATでグローバルIPアドレスに変換し、それをテキスト形式にすると、データの長さは変わります。その際に送信するパケット数が変化することになります。変化すると、送信順序番号も変更しなくてはなりません。したがって、このサンプルでは行っていませんが、NAT機能を持つルータは、送信元IPアドレス/ポート、送信順序、使用時間などの要素も管理せねばなりません。
このようにNATにも短所がありますが、IPアドレスの数を少なくする効果と、グローバル・ネットワークに接続するルータ以外は何も変更する必要がないという大きな利点があります。
NAT機能を実装するためにサンプルコードのRouter
クラスを変更しました。注目するべき所を抜粋して掲載します。
//Routerクラスからの抜粋 public class Router { public void SendToOtherLAN ( Frame data ) { //データを用意 MacFrame mac = data as MacFrame; if (data == null) return; IPv4 packet = data.Data as IPv4; if ( packet == null ) return; //送るべきルータのアドレスを決定 IPv4Address ruterAddress = null; try { ruterAddress = this.m_table.Find ( delegate ( RoutingElement element ) { if ( element.Destination == packet.DestinationAddress ) return true; else return false; } ).Gateway; //プライベートIPアドレスをグローバルIPアドレスに変換する foreach ( IpAddressPair pair in this.ipList ) { if ( pair.PrivateAddress == packet.SourceAddress ) packet.SourceAddress = pair.GlobalAddress; } } catch ( NullReferenceException ) { //指定されたホストに到達できない事を知らせる this.icmp.SendDestinationUnreachableMessage ( mac ); } //送信する this.Send ( ruterAddress, data ); } }
'Routerクラスからの抜粋 Public Class Router Public Sub SendToOtherLAN(ByVal data As Frame) 'データを用意 If TypeOf data Is MacFrame = False Then Return End If Dim mac As MacFrame = CType(data, MacFrame) If TypeOf mac.UsertData Is IPv4 = False Then Return End If Dim packet As IPv4 = CType(mac.UsertData, IPv4) '送るべきルータのアドレスを決定 Dim ruterAddress As IPv4Address = Nothing ruterAddress = Me.SearchRoute(packet.DestinationAddress) If ruterAddress Is Nothing Then '指定されたホストに到達できない事を知らせる Me.icmp.SendDestinationUnreachableMessage(mac) Else 'プライベートIPアドレスをグローバルIPアドレスに変換する For Each pair As IpAddressPair In Me.ipList If pair.PrivateAddress = packet.SourceAddress Then packet.SourceAddress = pair.GlobalAddress End If Next 'データを送る Me.Send(ruterAddress, data) End If End Sub End Class
察しの良い方は気づいたと思いますが、このNATの動作は、同時にグローバルIPアドレス数のホストしか通信できないことを意味します。そのため、現在ではNATではなく、ポート番号も要素として使用するNATP(Network Address Port Translation)が主に使用されています。NATPは、IPマスカレードとも呼ばれています。
以上で、NATの解説は終わりです。これまでの解説でセキュリティについての基礎を理解する下地ができていると思いますので、次ページからはファイアウォールの基礎を解説します。