2.2. IPの特徴

既に述べたように、 IP プロトコルはインターネット層 に属している。 IP プロトコルは TCP/IP スタックのうちで、コンピュータやルータ、スイッチなどにパケットの行き先を教える役割を担うプロトコルだ。 IP プロトコルこそ、 TCP/IP スタックの心臓部であり、インターネットの土台を支えているプロトコルである。

IP プロトコルはトランスポート層 のパケットをカプセル化 (encapsulate) し、使用されているトランスポート層プロトコルの種別、行き先および送信元のホスト情報をはじめ、その他幾つかの有用な情報を格納する。この処理は全て、微に入り細に入り規格化がなされている。こうした厳格さは、当チャプターで述べていくいずれのプロトコルにも当てはまることだ。

IP プロトコルには、必ず備えなければならない幾つかの基本的機能が定められている。 IP プロトコルは、データグラムつまり、トランスポート層 の作る次の建屋を用意できなくてはならない (トランスポート層 とは例えば TCP, UDP, ICMP のいずれか)。 IP プロトコルはまた、今日我々がお世話になっているインターネットの住所指定の仕組みも司る。つまり、 IP プロトコルはホスト間を往き来する方法を定め、それ故に当然、パケットをルーティングできるのも IP プロトコルのおかげというわけだ。ここで話題にしている住所 (address) とは、いわゆる IPアドレス のこと。通常我々が "IPアドレス" と言う時、ドット区切りの 4つの数字 (例えば 127.0.0.1) を指す。ただし、この形式は専ら人が見て読みやすいようにするためのもので、 IPアドレスの実体は 32 ビット長の 1 と 0 の羅列でしかない (即ち 127.0.0.1 なら、実際のヘッダ上では 01111111000000000000000000000001 と書かれている)。

IP プロトコルがその袖口で演ずるべきマジックはこれだけではない。 IP データグラム (IPデータ) をカプセル化および非カプセル化できる能力、ネットワークアクセス層 とでもトランスポート層 とでもデータを遣り取りできる能力だ。これはことさら目につく働きではないかもしれない。しかしそれ以上に、 IP プロトコルには果たさなければならない更にふたつの重要な機能がある。しかもファイヤーウォール構築とルーティングに関わり合う人なら興味を惹かれずにいられない機能だ。 IP プロトコルは、ホストからホストへのパケットばかりでなく、どこかのホストから自分以外のホスト宛てのパケットが入ってきた際の動作も司っているのだ。ひとつのネットワークにしかアクセスしないホストであれば、処理はほとんどの場合至って単純だ。選択肢はふたつしかない。自分につながっているローカルネットワーク行きのパケットか、デフォルトゲートウェイを通るパケットのどちらかだ。しかし、ひと度、複数のインターフェース、複数のルートを持つマシンでファイヤーウォールやセキュリティポリシーを触り始めると、途端にネットワーク管理者は頭痛がしてくる。 IP プロトコル最後の機能は、既にフラグメントされているデータグラムを再フラグメントしたり再構成したり、自分の近隣のネットワークのハードウェアトポロジーに合わせてフラグメントする能力だ。パケットのフラグメントサイズが目に見えて小さい場合にもやはり、ファイヤーウォール管理者は酷い頭痛に悩まされることとなる。パケットが粉微塵にフラグメントしているがために、肝心のデータグラムばかりか、パケットヘッダを読み取るのにさえ支障を来しているわけだ。

Tip

2.4 シリーズの Linux カーネルおよび iptables では、 Linux でファイヤーウォールを運営する上でこれはもはや問題とはならなくなった。 iptables がステートマッチングや NAT に使用するコネクション追跡メカニズムは、フラグメントされたパケットを読む必要がある。そのため、パケットがカーネルの netfilter/iptables 構造部に届く以前に conntrack が全てのパケットのデフラグメンテーションを行うからだ。

IP プロトコルのもうひとつの特質は、コネクションレスなプロトコルであるという点だ。つまり、 IP は接続に際しての "ネゴシエーション (negotiation = 折衝)" を行わない。これに対して、コネクション指向のプロトコルは、コネクションのネゴシエーション (ハンドシェイク と呼ばれる) を行い、データを全て送り終えたら接続の解消を行う。 TCP はそういったプロトコルの一例だが、 TCPIP プロトコルよりひとつ上の層だ。 IP 層の段階でコネクション指向にしない理由はいろいろあるが、取り立てて言えるのは、このレベルで敢えて不要なオーバーヘッドを伴うハンドシェイクを実装する意味がないからだ。その重荷は他のプロトコルに背負ってもらえばいい。オーバーヘッドは、返答が得られない場合には伝送経路のどこかでパケットが失われたと判断し、当初のリクエストを送信しなおす、という処理から生じる。お察しの通り、この場合、リクエストを送って一定の時間だけ回答を待つほうが賢い。でなければ、まずコネクション開始の要望を知らせるパケットを送り、次にコネクションが開いたと告げる知らせのパケットを受け取り、続いてコネクションが開いたことを確かに承知しましたという承認を行い、ここでやっとこさ当の目的のリクエストを送り、そうしたらコネクションを切断するためのパケットを更に送りその返事を待つ、という手間になってしまうのだ。

IP はまた、低信頼性 の (unreliable) プロトコルとしても知られる。簡単に言えば、パケットが届いたのか届かなかったのかには無頓着なのだ。 IP プロトコルはただ単純にトランスポート層 からパケットを受け、やるべきことだけやったらネットワークアクセス層 へ渡して、それでおしまい。返答のパケットが来るかもしれないが、それがネットワークアクセス層 から IP プロトコルへとやってくると、 IP はまたやるべきことをやり、上のトランスポート層 へと渡す。しかし、返事のパケットが来るのかパケットが行き先まで届いたのかにはお構いなしだ。 IP のこの低信頼性についても、先ほどの "コネクションレスさ" と同様のことが言える。なぜなら、低信頼性を何とかしようとすれば、パケットを送るたびに追加のパケットが必要となるからだ。 DNS 検索を例に採ってみよう。通常やるように、 servername.com を牽こうと DNS リクエストを送信する。返答が得られなければ、何か手違いがあったと考えてルックアップを再要求する。しかし通常は、リクエストを 1回送り、回答を 1回受けて終わりだ。信頼性を高めようと思ったらどうなるか。リクエストには 2回のパケットを要し (リクエスト 1回と、パケット到着確認 1回)、返答も 2回要る (回答 1回と、回答の領収の通知で 1回)。つまり、送らなくてはならないパケットの数は倍になり、流れるデータの量もほぼ 2倍になってしまう。